Подтвердить что ты не робот

Почему DbSet.Add работает так медленно?

Те же темы обсуждались здесь 8 месяцев назад: Как ускорить DbSet.Add()?. Не было предложено никаких решений, кроме использования SqlBulkCopy, что неприемлемо для нас. Я решил снова поднять его, надеясь, что могут возникнуть новые мысли и идеи вокруг этой проблемы, и предлагаются другие обходные пути. По крайней мере, мне просто интересно, почему эта операция занимает очень много времени.

Ну, проблема в том, что я должен обновить 30 тыс. объектов в базе данных (EF 4.1, POCO). Тип сущности довольно простой, содержащий целочисленные id + другие 4 целочисленные свойства без каких-либо отношений с другими типами. 2 случая:

  • все это новые записи. Запуск context.Entities.Add(сущность) один за другим для каждого объекта занимает 90 секунд с Cntx.Configuration.AutoDetectChangesEnabled = false (истинное значение заставляет его работать вечно). Затем SaveChanges занимает всего секунду. Другой подход: привязка его к контексту, подобному этому, занимает те же 90 секунд:

    Cntx.Entities.Attach(entity);
    Cntx.Entry(entity).State = EntityState.Added;
    
  • все это существующие записи с некоторыми изменениями. В этом случае требуется всего несколько миллисекунд, чтобы привязать его к существующему контексту данных следующим образом:

    Cntx.Entities.Attach(entity);
    Cntx.Entry(entity).State = EntityState.Modified;
    

    См. разницу?

Что стоит за сценой метода Add, которая заставляет его работать так невероятно медленно?

4b9b3361

Ответ 1

У меня есть интересные результаты тестирования производительности, и я нашел преступника. Я не видел такой информации в любом источнике EF, который я когда-либо читал.

Оказывается, что он равен нулю в базовом классе. Базовый класс должен содержать свойство Id, разделяемое всеми типами конкретных объектов. Этот подход рекомендован многими EF-книгами и довольно хорошо известен. Вы можете найти его здесь, например: Как наилучшим образом реализовать Equals для пользовательских типов?

Более точно, производительность убита операцией unboxing (преобразование объекта в конкретный тип), что заставило его работать так медленно. Когда я прокомментировал эту строку кода, потребовалось 3 секунды для запуска против 90 секунд раньше!

public override bool Equals ( object obj )
{
    // This line of code made the code so slow 
    var entityBase = obj as EntityBase;
    ...
}

Как я понял, я начал думать о том, что может быть альтернативой этому Равновению. Первая идея заключалась в том, чтобы реализовать IEquatable для EntityBase, но это случилось вовсе не с запуском. Итак, в конечном итоге я решил реализовать IEquatable для каждого конкретного класса сущности в моей модели. У меня их мало, поэтому для меня это небольшое обновление. Вы можете поместить всю функциональность Equal operation (обычно это сравнение объектов 2 объекта) в метод расширения для совместного использования между конкретными классами сущностей и запустить его следующим образом: Equal ((EntityBase) ConcreteEntityClass). Самое интересное, этот IEquatable ускоряет EntitySet.Add 6 раз!

Итак, у меня больше нет проблем с производительностью, тот же код работает для меня менее чем за секунду. Я получил прирост производительности в 180 раз! Потрясающе!

Заключение

  • Самый быстрый способ запуска EntitySet.Add - иметь IEquatable для конкретной сущности (0.5 сек)
  • Отсутствует IEquatable заставляет его запускать 3 сек.
  • Имея Equals (объект obj), который рекомендует большинство источников, он запускает 90 секунд