Основы сущность параллельного метода SaveChanges тупик
Я пишу довольно простое многопользовательское приложение WPF, где каждый пользователь может изменять / удалять свои собственные данные. Означает, что каждый пользователь может видеть все данные других пользователей, но не имеет права изменять или удалять их. Все приложение работает хорошо до тех пор, пока 2 или более клиентов не нажмут метод savechanges одновременно, а затем я иногда сталкиваюсь с тупиком базы данных.
Transaction (Process ID 57) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction.
Я понятия не имею, почему он заходит в этот тупик, поскольку невозможно, чтобы одни и те же данные были изменены или удалены одновременно разными пользователями.
Я написал короткую программу, которая демонстрирует это поведение. Метод delete в конце имитирует одновременное удаление двумя пользователями своих собственных данных.
Вот мой DbContext и Datamodel:
public class Context : DbContext { public DbSet<City> dbsCities { get; set; } public DbSet<House> dbsHouses { get; set; } public DbSet<Person> dbsPersons { get; set; } public Context() : base(@"Server=.\SQLExpress;Database=test;Trusted_Connection=Yes;Connection Timeout=5") { Configuration.AutoDetectChangesEnabled = true; Configuration.LazyLoadingEnabled = false; Configuration.ProxyCreationEnabled = false; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<City>().HasMany(c => c.Houses).WithRequired(c => c.City).WillCascadeOnDelete(true); modelBuilder.Entity<House>().HasMany(p => p.Residents).WithRequired(p => p.House).WillCascadeOnDelete(true); } } public class City { public Guid Id { get; set; } public string Name { get; set; } public virtual ICollection<House> Houses { get; set; } public City() { Id = Guid.NewGuid(); Houses = new List<House>(); } } public class House { public Guid Id { get; set; } public int Number { get; set; } public string Code { get; set; } public virtual City City { get; set; } public virtual ICollection<Person> Residents { get; set; } public House() { Id = Guid.NewGuid(); Residents = new List<Person>(); } } public class Person { public Guid Id { get; set; } public string Firstname { get; set; } public string Lastname { get; set; } public virtual House House { get; set; } public Person() { Id = Guid.NewGuid(); } }
А вот и код для генерации тестовых данных:
using (Context ctx = new Context()) { List<City> cities = new List<City>() { new City() { Name = "New York" } }; for (int h = 1; h <= 50; h++) { string code = "A"; if (h % 2 == 0) code = "B"; House house = new House() {Number = h, Code = code }; cities[0].Houses.Add(house); for (int i = 0; i <= 100; i++) house.Residents.Add(new Person() { Firstname = "A", Lastname = "B" }); } ctx.dbsCities.AddRange(cities); ctx.SaveChanges(); }
Наконец метод который вызывает тупик
private void Delete(object sender, RoutedEventArgs e) { string[] to = new string[] {"A", "B"}; Parallel.ForEach(to, code => { DeleteHouses(code); }); } private void DeleteHouses(string code) { using (var ctx = new Context()) { ctx.Database.Log += Console.WriteLine; var todel = ctx.dbsHouses.Where(d=>d.Code == code); if (todel != null) { ctx.dbsHouses.RemoveRange(todel); ctx.SaveChanges(); } } }
Что я уже пробовал:
Я попробовал использовать TransactionScope с использованием моментального снимка уровня изоляции, который был
рекомендуется в некоторых других сообщениях форума без успеха.
Во всяком случае я думаю что это не имеет никакого отношения к уровню изоляции так как мои запросы удаляются
разные строки.