m.bleimuth Ответов: 1

Основы сущность параллельного метода 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 с использованием моментального снимка уровня изоляции, который был
рекомендуется в некоторых других сообщениях форума без успеха.
Во всяком случае я думаю что это не имеет никакого отношения к уровню изоляции так как мои запросы удаляются
разные строки.

1 Ответов

Рейтинг:
1

Gerry Schmitz

Кто сказал вам использовать "параллельное" программирование?

Теперь я понимаю, почему эф получает плохую репутацию.

Долбить систему с помощью "сохранения изменений" и т. д.

Тебе нужно вернуться к основам.

c# - Parallel не работает с Entity Framework - переполнение стека[^]


m.bleimuth

Почему у него есть что-то todo с параллельным программированием? Если я запускаю программное обеспечение дважды и одновременно нажимаю savechanges (один клиент удаляет все дома A, другой-все дома B), он также попадает в тупик.
Я думаю, что это не редкость, что клиенты удаляют записи в то же время, которые не имеют никаких отношений друг с другом.
Мой параллельный метод просто имитирует два клиента, удаляющих разные записи одновременно. Самое странное, что если я удаляю только людей, то это работает без тупика, так что я думаю, что у него есть что-то с каскадным удалением домов.