Почему DbSet.Add работает так медленно? - PullRequest
33 голосов
/ 13 августа 2011

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

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

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

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

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

    Видите разницу?

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

1 Ответ

27 голосов
/ 13 августа 2011

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

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

Точнее говоря, производительность снижается из-за операции распаковки (преобразования объекта в конкретный тип), которая заставила его работать такмедленный.Когда я прокомментировал эту строку кода, потребовалось 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 (обычно это сравнение двух идентификаторов объектов) в метод расширения для совместного использования между конкретными классами сущностей и запустить его следующим образом: Equal ((EntityBase) ConcreteEntityClass).Самое интересное, этот IEquatable ускоряет EntitySet.Add в 6 раз!

Так что у меня больше нет проблем с производительностью, этот код выполняется для меня менее чем за секунду. Я получил увеличение производительности в 180 раз !Удивительно!

Заключение :

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