Как ускорить DbSet.Add()?
Мне нужно импортировать около 30 тыс. строк из файла CSV в мою базу данных SQL, это, к сожалению, занимает 20 минут.
Устранение неполадок с профилировщиком показывает, что DbSet.Add занимает больше всего времени, но почему?
У меня есть классы Entity Framework Code-First:
public class Article
{
// About 20 properties, each property doesn't store excessive amounts of data
}
public class Database : DbContext
{
public DbSet<Article> Articles { get; set; }
}
Для каждого элемента в моем цикле for я делаю:
db.Articles.Add(article);
За пределами цикла for я делаю:
db.SaveChanges();
Он связан с моим локальным сервером SQLExpress, но я думаю, что ничего не написано до тех пор, пока SaveChanges не будет вызван, поэтому я думаю, что сервер не будет проблемой....
Ответы
Ответ 1
Каждый элемент в единице работы имеет накладные расходы, так как он должен проверять (и обновлять) диспетчер идентификации, добавлять в различные коллекции и т.д.
Первое, что я попробую, это доработать, скажем, группы из 500 (измените это число), начиная со свежего (нового) объекта-контекста каждый раз, так как в противном случае вы можете разумно ожидать телескопической производительности. Разбивание его на партии также мешает мегалитической сделке, приносящей все к остановке.
Помимо этого; SqlBulkCopy. Он предназначен для большого импорта с минимальными накладными расходами. Это не EF, хотя.
Ответ 2
По словам Кевина Рамена (29 марта)
Я могу подтвердить, что установка db.Configuration.AutoDetectChangesEnabled = false
делает огромную разницу в скорости
Запуск Add()
по 2324 элементам по умолчанию выполнялся 3мин 15 секунд на моей машине, отключив автообнаружение, завершив операцию в 0.5 сек.
http://blog.larud.net/archive/2011/07/12/bulk-load-items-to-a-ef-4-1-code-first-aspx
Ответ 3
Я добавлю к замечанию Кервина Рамена, сказав, что если вы делаете только вставки (никаких обновлений или удалений), вы можете, в общем, безопасно установить следующие свойства перед выполнением любых вставок в контексте:
DbContext.Configuration.AutoDetectChangesEnabled = false;
DbContext.Configuration.ValidateOnSaveEnabled = false;
У меня возникла проблема с однократным массовым импортом на моей работе. Не устанавливая вышеуказанные свойства, добавление около 7500 сложных объектов в контекст занимало более 30 минут. Установка вышеуказанных свойств (так что отключение проверки EF и отслеживание изменений) уменьшало импорт до секунд.
Но, опять же, я подчеркиваю, что использую это только в том случае, если вы делаете вставки. Если вам нужно смешать вставки с обновлениями/удалениями, вы можете разделить свой код на два пути и отключить проверки EF для части вставки, а затем снова включить проверки пути обновления/удаления. Я использовал этот подход, чтобы обойти медленное поведение DbSet.Add()
.
Ответ 4
Здесь очень простое и быстрое расширение:
https://efbulkinsert.codeplex.com/
Он называется "Массивная вставка Entity Framework".
Внутреннее расширение находится в пространстве имен EntityFramework.BulkInsert.Extensions. Поэтому, чтобы выявить метод расширения, добавьте
using EntityFramework.BulkInsert.Extensions;
И тогда вы можете сделать это
context.BulkInsert(entities);
BTW. Если вы не хотите использовать это расширение по какой-либо причине, вы также можете попробовать вместо запуска db.Articles.Add(статьи) для каждой статьи, чтобы каждый раз создавать список из нескольких статей, а затем использовать AddRange (новый в EF версии 6 вместе с RemoveRange), чтобы добавить их вместе в dbcontext.
Ответ 5
Я действительно не пробовал это, но моя логика заключалась бы в том, чтобы поддерживать драйвер ODBC для загрузки файла в datatable, а затем использовать хранимую процедуру sql для передачи таблицы в процедуру.
Для первой части попробуйте:
http://www.c-sharpcorner.com/UploadFile/mahesh/AccessTextDb12052005071306AM/AccessTextDb.aspx
Для второй части попробуйте это для процедуры SQL:
http://www.builderau.com.au/program/sqlserver/soa/Passing-table-valued-parameters-in-SQL-Server-2008/0,339028455,339282577,00.htm
И создайте объект SqlCommnand в С# и добавьте в его набор параметров SqlParameter, который является SqlDbType.Structured
Ну, надеюсь, это поможет.