Entity Framework и производительность, попытка вторая
В первой своей попытке закрыть дыру в производительности Entity Framework’а я рассматривал только материализацию. Но дальше в процессе работы, как того и следовало ожидать, я наткнулся и на другое, более весомое ограничение. Операции вставки, модификации и удаления записей происходят тоже медленно. На 100 вставок EF посылает в базу 100 запросов на вставку, никак не пытаясь их сгруппировать. Кроме этого, в одном из проектов была обнаружена одна неприятная ошибка: EF версии 5.0.0, при работе с Oracle, в Clob/Xml поля не позволяет вставлять строки более 2000 символов.Для решения был создан компонент, который я назвал Context Items, со следующими возможностями:1) Bulk Insert (MS Sql): в таблицы, не имеющие Identity в качестве первичного ключа возможно осуществить вставку методом Bulk Insert, который поддерживается базой данных Ms Sql Server. В случае с Identity нет способа надежно получить назад ключи, сгенерированные базой при вставке с помощью Bulk Insert, поэтому для таблиц имеющих такие ключи используется группировка нескольких обычных Insert-запросов в один запрос. Это работает существенно медленнее, чем Bulk Insert, но все же быстрее, чем через EF.
2) Sequenced Bulk Insert (MS Sql): альтернативой Identity обычно служит Guid, это решает проблему вставки, но создает другую проблему — в силу большей длины ключа операции Join начинают работать медленнее, кроме этого Guid непоследователен, и поэтому Clustered индексы не приносят своих преимуществ. Как решение данной проблемы начиная с MS Sql Server 2012 есть возможность использовать Sequence для создания первичных ключей. Это позволяет использовать целочисленные последовательные ключи, что позволяет использовать Clustered индексы, аналогично Identity, и одновременно позволяет использовать Bulk Insert для вставки. Компонент поддерживает только ацикличные Sequence с инкрементом 1.
3) Bulk Update (MS Sql): самой по себе в базе данных такой операции не существует, компонент воплощает ее последовательно выполняя следующие 4 операции:
a) Создается временная таблица, имеющая тот же набор полей, что и целевая таблица b) Производится Bulk Insert данных во временную таблицу c) Выполняется Join-Update операция, которая переносит данные из записей временной таблицы в записи целевой таблицы, имеющие совпадающие первичные ключи. d) Временная таблица удаляется По причине того, что операция не атомарная, ее желательно исполнять в транзакции.4) Bulk Delete (MS Sql): также как и Bulk Update, эта операция происходит в 4 шага:
a) Cоздается временная таблица имеющая набор полей совпадающий с первичным ключом целевой таблицы b) Производится Bulk Insert во временную таблицу первичных ключей для записей, которые надо удалить c) Выполняется Join-Delete операция d) Временная таблица удаляется 5) Материализация: Функция перекочевала из предыдущего варианта, дополнительно я добавил в репозиторий тестовый проект, включающий в себя сравнение производительности материализации с micro-ORM Dapper. Context Items выполняет эту операцию примерно на 3–5% быстрее, чем Dapper, и на 40% быстрее, чем EF, при использовании AsNoTracking, без этого EF работает еще в несколько раз медленнее.6) Array-bound Insert, Update and Delete (Oracle): Bulk Insert компонент, реализованный в ODP.Net не принимает во внимание ни триггеры ни constraint-ы, ни даже первичные ключи. Поэтому для вставки непосредственно в таблицу он не годится. Кроме того, он не поддерживает транзакции. Конечно можно было попытаться использовать временную таблицу как и в случае с MSSql, но я решил использовать метод, которые рекомендуется самим Oracle. Метод называется array-binding. Вкратце — запрос, посылаемый в базу, выглядит так, как будто мы вставляем одну запись, но в качестве параметров мы передаем не набор полей, а набор массивов полей, и таким образом вставляем, обновляем либо удаляем массив записей, а не одну запись. О методе можно почитать здесь
7) Equality members: Для всех сущностей генерируются методы GetHashCode и Equals, работающие с первичным ключом.
Компонент Context Items поддерживает EF версии 5.0.0 — 6.1.3, а также базы данных Oracle и MS Sql Server.Поддерживается только database-first подход, имеется ограничение — имена сущностей должны совпадать с именами таблиц. Руки не дошли, чтобы это ограничение починить.
Компонент опубликован на nuget.org, с идентификаторами contextitemsmssql и contextitemsoracle. Подробнее об установке и использовании можно прочитать в репозитории на GitHub.com: github.com/repinvv/ContextItems