Самый быстрый способ вставки в инфраструктуру Entity


509

Я ищу самый быстрый способ вставки в Entity Framework.

Я спрашиваю об этом из-за сценария, в котором у вас активная TransactionScope, и вставка огромна (4000+). Он может длиться более 10 минут (время ожидания по умолчанию), и это приведет к неполной транзакции.

  0

Как вы сейчас делаете это? 09 май. 112011-05-09 17:16:23

  0

Создание TransactionScope, создание экземпляра DBContext, открытие соединения и для каждого оператора, выполняющего вставки и SavingChanges (для каждой записи). ПРИМЕЧАНИЕ. TransactionScope и DBContext используют операторы, и я закрываю соединение в finally block 09 май. 112011-05-09 17:31:45

  0

Еще один ответ для справки: http://stackoverflow.com/questions/5798646/entity-framework-algorithm-for-combining-data 09 май. 112011-05-09 21:35:33

  0

Настройка AutoDetectChangesEnabled на false может отрицательно сказаться на приложениях, которые в значительной степени зависят от сторонних элементов управления. Что-то нужно иметь в виду, прежде чем бросаться и ломать другие части приложения. 03 дек. 152015-12-03 18:36:16

  0

это ядро ​​ASP NET. Быстрая вставка. Http://stackoverflow.com/a/43499016/3752172. 19 апр. 172017-04-19 14:47:13

792

К вашему замечанию в комментариях к вашему вопросу:

"... SavingChanges (для каждого записи) ..."

Это самое худшее ты можешь сделать! Вызов SaveChanges() для каждой записи замедляет объемные вставки чрезвычайно вниз. Я бы сделал несколько простых тестов, которые, скорее всего, улучшат производительность:

  • Позвоните SaveChanges() один раз после ВСЕГО записей.
  • Звоните SaveChanges() после, например, 100 записей.
  • Звоните SaveChanges() после, например, 100 записей и удалите контекст и создайте новый.
  • Отключить изменение обнаружения

Для оптовых вставок я работаю и экспериментируя с рисунком, как это:

using (TransactionScope scope = new TransactionScope()) 
{ 
    MyDbContext context = null; 
    try 
    { 
     context = new MyDbContext(); 
     context.Configuration.AutoDetectChangesEnabled = false; 

     int count = 0;    
     foreach (var entityToInsert in someCollectionOfEntitiesToInsert) 
     { 
      ++count; 
      context = AddToContext(context, entityToInsert, count, 100, true); 
     } 

     context.SaveChanges(); 
    } 
    finally 
    { 
     if (context != null) 
      context.Dispose(); 
    } 

    scope.Complete(); 
} 

private MyDbContext AddToContext(MyDbContext context, 
    Entity entity, int count, int commitCount, bool recreateContext) 
{ 
    context.Set<Entity>().Add(entity); 

    if (count % commitCount == 0) 
    { 
     context.SaveChanges(); 
     if (recreateContext) 
     { 
      context.Dispose(); 
      context = new MyDbContext(); 
      context.Configuration.AutoDetectChangesEnabled = false; 
     } 
    } 

    return context; 
} 

У меня есть тестовая программа, которая вставляет 560.000 объектов (9 свойства скалярного, нет навигационных свойств) в БД. С помощью этого кода он работает менее чем за 3 минуты.

Для исполнения важно называть SaveChanges() после «многих» записей («много» около 100 или 1000). Он также улучшает производительность, чтобы избавиться от контекста после SaveChanges и создать новый.Это очищает контекст от всех entites, SaveChanges этого не делает, сущности все еще привязаны к контексту в состоянии Unchanged. Это растущий размер прикрепленных объектов в контексте, что постепенно замедляет вставку. Таким образом, полезно очистить его через некоторое время.

Вот несколько измерений для моих 560.000 лиц:

  • COMMITCOUNT = 1, recreateContext = ложь: много часов (Это текущая процедура)
  • COMMITCOUNT = 100, recreateContext = ложное: более 20 минут
  • COMMITCOUNT = 1000, recreateContext = ложь: 242 сек
  • гр ommitCount = 10000, recreateContext = ложь: 202 сек
  • COMMITCOUNT = 100000, recreateContext = ложь: 199 сек
  • COMMITCOUNT = 1000000, recreateContext = ложь: из исключения памяти
  • COMMITCOUNT = 1 , recreateContext = верно: более 10 минут
  • COMMITCOUNT = 10, recreateContext = верно: 241 сек
  • COMMITCOUNT = 100, recreateContext = верно: 164 сек
  • COMMITCOUNT = 1000, recreateContext = верно: 191 сек

Поведение в первом тесте выше является то, что производительность очень нелинейна и с течением времени значительно уменьшается. («Много часов» - это оценка, я никогда не заканчивал этот тест, я остановился на 50 000 объектов через 20 минут.) Это нелинейное поведение не так важно во всех других тестах.

  0

Я не знал о штрафе при выполнении вызова SaveChanges после каждой вставки, я попробую с предоставленными вами параметрами. Спасибо за ваш комментарий, я дам вам знать мои результаты. 09 май. 112011-05-09 21:48:08

  0

Я оставил загрузочный ход с таким подходом, я расскажу о его результатах утром, но до сих пор я заметил ОГРОМНОЕ улучшение, выполнив SaveChanges каждые 100 записей. 10 май. 112011-05-10 06:43:47

+68

@Bongo Sharp: не забывайте установите «AutoDetectChangesEnabled = false;» в DbContext. Он также обладает большим дополнительным эффектом производительности: http://stackoverflow.com/questions/5943394/why-is-inserting-entities-in-ef-4-1-so-slow-compared-to-objectcontext/5943699#5943699 10 май. 112011-05-10 09:03:02

+2

Да, проблема в том, что я использую Entity Framework 4, а AutoDetectChangesEnabled является частью 4.1, тем не менее, я сделал тест производительности, и у меня были УДИВИТЕЛЬНЫЕ РЕЗУЛЬТАТЫ, он пошел с 00:12:00 до 00:00:22 SavinChanges на каждом объекте делал olverload ... СПАСИБО, так много для вашего answare!это то, что я искал 10 май. 112011-05-10 16:24:09

+8

Спасибо за контекст.Configuration.AutoDetectChangesEnabled = false; наконечник, он делает ** огромную ** разницу. 08 май. 122012-05-08 23:14:56

  0

Привет, я пытаюсь использовать этот код. Но это дает мне ошибку, поскольку AutoDetectChangesEnabled не может быть найден. 05 июл. 122012-07-05 21:26:13

+1

@ dahacker89: Вы используете правильную версию EF> = 4.1 и 'DbContext', а не' ObjectContext'? 05 июл. 122012-07-05 21:48:09

  0

Спасибо, что исправил меня. Да, я использую ObjectContext. Просто преобразовал мою модель в DBContext. У меня другая проблема. Эта строка: использование (TransactionScope scope = new TransactionScope()) дает мне сообщение о том, что TransactionScope, используемый в операторе using, должен быть неявно конвертируемым в System.IDisposable. Любая идея, как это решить ??? 05 июл. 122012-07-05 22:04:46

  0

@ dahacker89: Проверьте правильное пространство имен. Вы должны использовать класс 'TransactionScope' в' System.Transactions', который является одноразовым: http://msdn.microsoft.com/en-us/library/system.transactions.transactionscope.aspx 06 июл. 122012-07-06 10:05:59

  0

@Slauma Я включил это пространство имен. Он распознает, но продолжает давать мне эту ошибку. А также он говорит scope.Complete() не может быть найден или что-то еще. Поэтому я взял TransactionScope и запустил свой код. Но я все еще хочу проверить это :( 06 июл. 122012-07-06 23:56:41

+1

@ dahacker89: Я предлагаю вам создать отдельный вопрос для вашей проблемы, возможно, более подробно. Я не могу понять, что не так. 07 июл. 122012-07-07 11:13:53

  0

Я установил 'IsolationLevel 'для моей транзакции следующим образом:' using (TransactionScope trans = new TransactionScope (TransactionScopeOption.Required, новый TransactionOptions {IsolationLevel = IsolationLevel.ReadUncommitted})) - Я не уверен, что это необходимо, но я действительно надеюсь избежать любые блокировки или длительное время ожидания при вставке данных. 03 окт. 122012-10-03 05:57:58

  0

Извините ... это должно быть 'TransactionScopeOption.Suppress' - см. также: http://stackoverflow.com/questions/2921920/transactionscope-prematurely-completed 03 окт. 122012-10-03 06:56:33

  0

Просто быстро, фантастично информация здесь. Спасибо Sluama! Что же касается TransactionScope? Я никогда раньше не использовал его при работе с EF и вставкой/обновлением/удалением. 02 апр. 132013-04-02 07:38:21

  0

@FizzBuzz: он переносит несколько вызовов 'SaveChanges' в одну транзакцию базы данных. В большинстве случаев это не нужно. 02 апр. 132013-04-02 10:38:15

  0

О создании контекста: если вы создаете объекты в новом контексте и устанавливаете их ссылки на объекты, созданные там, где они созданы и сохранены в предыдущем контексте, объекты ссылки будут добавлены в новый контекст с EntityState.Added и в контексте SaveChanges будет пытаться сохранить объекты во второй раз вне зависимости от primarykeyid> 0 16 апр. 132013-04-16 07:19:00

+1

Strewth - просто отключив AutoDetectChangesEnabled, он сделал МАССИВНОЕ отличие от моего приложения, которое добавляет 200 000 записей в коллекцию, прежде чем нажать один вызов, чтобы сохранить изменения! Я ранее останавливал приложение через 30 минут, теперь он пронзает их всех в самое короткое время! 20 апр. 132013-04-20 20:05:33

  0

Ничего себе, какая хорошая работа. Это улучшило мое добавление с помощью lightningspeed. 23 июл. 132013-07-23 13:03:53

  0

Можете ли вы, пожалуйста, просмотреть мой вопрос: http://stackoverflow.com/questions/18645557/entity-framework-5-poor-performance?noredirect1_comment27454411_18645557. Благодаря! 05 сен. 132013-09-05 21:47:51

  0

Тип Entity в AddToContext() - это какой-то тип базового объекта? То, что я ищу, является общим AddToContext(), где я могу передавать разные типы объектов и использовать gettype() или что-то в DBSet. Я использую первую модель EF6 Db. В любом случае это отличный ответ. 11 фев. 142014-02-11 15:53:04

  0

@Neel: должно быть возможно использовать 'Entity' непосредственно как общий параметр общей версии 'AddToContext <Entity> (...) где Entity: class'. 12 фев. 142014-02-12 00:59:50

  0

@Slauma да, вот как я это сделал, дженерики и некоторая помощь от Reflection. 12 фев. 142014-02-12 01:40:48

  0

@ Слаума будет ли этот код и результаты быть похожими, если бы были вовлечены вложенные/реляционные сущности? Связано ли отношение «AutoDetectChangesEnabled» к отношениям? 25 июн. 142014-06-25 11:56:12

+1

@Cristi: 'AutoDetectChangesEnabled' также влияет на связанные объекты, да и, вероятно, также с лучшей производительностью. Однако у меня нет конкретных цифр. Тем не менее, вы должны быть осторожны с отключением обнаружения изменений и более тщательным тестированием кода, если связаны отношения: http://stackoverflow.com/questions/23636920/what-are-the-bugs-that-can-be-caused-in -ef-by-disabling-automatic-change-detecti 25 июн. 142014-06-25 13:17:19

  0

Внесены ли все записи в одну огромную транзакцию в примере? Так как использование «TransactionScope» находится в самом внешнем? 29 авг. 142014-08-29 14:06:34

  0

@ dc7a9163d9: Да, это одна транзакция. 31 авг. 142014-08-31 11:42:11

  0

@slauma, поэтому может возникнуть проблема, если код используется для вставки миллионов строк. 31 авг. 142014-08-31 15:30:43

  0

Хотелось бы, чтобы я узнал об этом, как два года назад, когда мы начали работу над проектом, который у меня есть, а не сейчас, когда очень больно учитывать Это. 09 апр. 152015-04-09 14:36:49

  0

Этот код отлично подходит для 'AddOrUpdate'. Это не здорово, но это лучше, чем ничего. Долгосрочно, я думаю, я хотел бы изменить его на MERGE. 29 июн. 152015-06-29 19:30:17

  0

К сожалению, в ответе не указаны отрицательные значения для установки параметра AutoDetectChangesEnabled в значение false. Установка его на false может привести к поломке приложений, и именно поэтому это плохая практика; в противном случае по умолчанию было бы установлено значение false. Возможное нарушение может произойти, если приложение использует сторонние элементы управления. 03 дек. 152015-12-03 18:30:49

  0

Поскольку Bulk insert является методом расширения DbContext, а не DbSet Может ли он обновлять несколько наборов одновременно? 24 ноя. 162016-11-24 08:09:58

  0

Кто-нибудь заметил очень недавнее улучшение производительности в сценарии вставки данных через E.F. в Azure SQL? Недавно я заметил лучшую производительность без меня, ничего не изменив в спецификациях Azure SQL. 06 мар. 172017-03-06 19:48:30

  0

Будет ли это отредактировано для последних версий .net и ef? Мне любопытно узнать, может ли новейшая версия ef обрабатывать 1000 строк быстрее. 10 авг. 172017-08-10 20:14:55

  0

. Я думаю, что базовое соединение открывается и закрывается каждый раз, когда вы воссоздаете контекст, не так ли? Не было бы лучше создать соединение вручную и передать его в контекст? Таким образом, соединение не будет открыто и закрыто для каждого экземпляра контекста. 17 фев. 182018-02-17 10:27:06

  0

Кто-нибудь знает, можно ли применить этот подход в ядре Entity Framework? 02 мар. 182018-03-02 20:52:24


66

Для этого необходимо использовать System.Data.SqlClient.SqlBulkCopy. Вот documentation, и, конечно, в Интернете есть много уроков.

Извините, я знаю, что вы искали простой ответ, чтобы заставить EF делать то, что вы хотите, но массовые операции на самом деле не предназначены для ORM.

+1

. Несколько раз я сталкивался с SqlBulkCopy, исследуя это, но, похоже, он больше ориентирован на вставки таблиц в стол , к сожалению, я не ожидал простых решений, но, скорее, рекомендаций по производительности, например, для управления состоянием соединения вручную, потребовал, чтобы EF сделал это для вас 09 май. 112011-05-09 17:27:07

+7

Я использовал SqlBulkCopy для вставки больших объемов данных прямо из приложения. Вам в основном нужно создать DataTable, заполнить его, а затем передать * этот * в BulkCopy. Есть несколько исправлений, когда вы настраиваете свой DataTable (большинство из которых я забыл, к сожалению), но он должен работать нормально 09 май. 112011-05-09 17:36:52

+2

Я сделал доказательство концепции, и, как обещано, он работает очень быстро, но один из причин, почему я использую EF, заключается в том, что вставка реляционных данных проще, например, если я вставляю объект, который уже содержит реляционные данные, он также будет вставлять его, вы когда-нибудь попадали в этот сценарий? Благодаря! 09 май. 112011-05-09 23:47:31

+2

К сожалению, вставка веб-объектов в СУБД не является чем-то, что сделает BulkCopy. Это преимущество ORM, как EF, а стоимость заключается в том, что он не будет масштабироваться, чтобы эффективно выполнять сотни подобных графиков объектов. 10 май. 112011-05-10 01:02:44

+2

SqlBulkCopy - это определенно способ пойти, если вам нужна необработанная скорость или если вы снова запустите эту вставку. Я вставил с ним несколько миллионов записей, и это очень быстро. Тем не менее, если вам не понадобится повторно запустить эту вставку, может быть проще просто использовать EF. 26 апр. 122012-04-26 16:09:32


8

Попробуйте использовать Сохраненная процедура, которая получит XML-данные, которые вы хотите вставить.

+8

Передача данных в формате XML не требуется, если вы не хотите хранить их как XML. В SQL 2008 вы можете использовать параметр table value. 09 май. 112011-05-09 21:36:32

  0

Я не уточнил это, но мне нужно также поддерживать SQL 2005 10 май. 112011-05-10 00:16:10


2

Как мне известно, no BulkInsert в EntityFramework для увеличения производительности огромных вставок.

В этом случае вы можете пойти с SqlBulkCopy в ADO.net, чтобы решить вашу проблему

  0

Я рассматривал этот класс, но, похоже, он больше ориентирован на вставки таблиц в таблицу, не так ли? 09 май. 112011-05-09 17:23:26

  0

Не уверен, что вы имеете в виду, он имеет перегруженный 'WriteToServer', который принимает' DataTable'. 09 май. 112011-05-09 17:28:37

  0

no вы можете вставить из .Net объектов в SQL также. Что вы ищете? 09 май. 112011-05-09 17:28:51

  0

Способ вставить потенциально тысячи записей в базу данных в блоке TransactionScope 09 май. 112011-05-09 17:33:13

  0

вы можете использовать .Net TransactionScope http://technet.microsoft.com/en-us/library/bb896149.aspx 09 май. 112011-05-09 17:58:31


1

Секрет заключается в том, чтобы вставить в идентичном пустую промежуточную таблицу. Вставки светятся быстро. Затем запустите одну вставку из этого в ваш главный большой стол. Затем усечь подготовительную таблицу, готовую к следующей партии.

ie.

insert into some_staging_table using Entity Framework. 

-- Single insert into main table (this could be a tiny stored proc call) 
insert into some_main_already_large_table (columns...) 
    select (columns...) from some_staging_table 
truncate table some_staging_table 
+4

Вы сделали это с помощью Entity Framework ? 09 май. 112011-05-09 17:36:04

  0

Используя EF, добавьте все свои записи в пустой промежуточный стол. Затем используйте SQL для вставки в основную (большую и медленную) таблицу в инструкции * single * SQL. Затем очистите свой промежуточный стол. Это очень быстрый способ вставки большого количества данных в уже большую таблицу. 18 июл. 122012-07-18 10:41:40

+11

Когда вы говорите, используя EF, добавьте записи в промежуточную таблицу, вы действительно попробовали это с помощью EF? Поскольку EF выдает отдельный вызов в базу данных с каждой вставкой, я подозреваю, что вы увидите тот же самый хит, который OP пытается избежать. Как эта промежуточная таблица избегает этой проблемы? 17 июл. 132013-07-17 14:25:00


154

Эта комбинация увеличивает скорость достаточно хорошо.

context.Configuration.AutoDetectChangesEnabled = false; 
context.Configuration.ValidateOnSaveEnabled = false; 
  0

Для меня это отличалось, по крайней мере, на порядок, при вставке около 40 000 записей в таблицу. 28 ноя. 122012-11-28 15:56:02

  0

Мне пришлось добавить около 150 000 объектов, и это потребовалось навсегда. Я пробовал много подходов, но этот самый простой и лучший! Спасибо. 17 мар. 132013-03-17 21:40:25

  0

Сохранение 23000 записей, и это самый простой способ сделать работу приемлемой. 31 июл. 132013-07-31 10:05:46

+37

Не слепо отключите ValidateOnSaveEnabled, вы можете быть в зависимости от этого поведения, и не осознавать этого, пока не стало слишком поздно. Опять же, вы можете выполнять проверку в другом месте кода и повторить проверку EF еще раз совершенно ненужным. 05 сен. 132013-09-05 16:28:32

+1

В моем тестовом сохранении 20 000 строк сократились с 101 секунд до 88 секунд. Не много и каковы последствия. 24 янв. 142014-01-24 13:22:25

+19

@JeremyCook Я думаю, что вы пытаетесь понять, что этот ответ будет намного лучше, если он объяснит возможные последствия изменения этих свойств из значений по умолчанию (помимо повышения производительности). Согласен. 29 апр. 142014-04-29 04:17:01

+1

Это сработало для меня, хотя если вы обновляете записи в контексте, вам нужно будет вызвать DetectChanges() явно 01 сен. 142014-09-01 13:13:04

+2

Их можно отключить, а затем снова включить с помощью блока try-finally: https://msdn.microsoft. com/en-us/data/jj556205.aspx 11 апр. 152015-04-11 17:42:44

  0

Сохранение около 350 000 записей в новую, пустую базу данных, это сделало нулевую разницу. Итак, вам просто нужно проверить свой сценарий. 28 апр. 152015-04-28 13:03:49


44

Я согласен с Адамом Ракисом. SqlBulkCopy - это самый быстрый способ передачи массовых записей из одного источника данных в другой. Я использовал это для копирования 20 тыс. Записей, и потребовалось менее 3 секунд. Взгляните на приведенный ниже пример.

public static void InsertIntoMembers(DataTable dataTable) 
{   
    using (var connection = new SqlConnection(@"data source=;persist security info=True;user id=;password=;initial catalog=;MultipleActiveResultSets=True;App=EntityFramework")) 
    { 
     SqlTransaction transaction = null; 
     connection.Open(); 
     try 
     { 
      transaction = connection.BeginTransaction(); 
      using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction)) 
      { 
       sqlBulkCopy.DestinationTableName = "Members"; 
       sqlBulkCopy.ColumnMappings.Add("Firstname", "Firstname"); 
       sqlBulkCopy.ColumnMappings.Add("Lastname", "Lastname"); 
       sqlBulkCopy.ColumnMappings.Add("DOB", "DOB"); 
       sqlBulkCopy.ColumnMappings.Add("Gender", "Gender"); 
       sqlBulkCopy.ColumnMappings.Add("Email", "Email"); 

       sqlBulkCopy.ColumnMappings.Add("Address1", "Address1"); 
       sqlBulkCopy.ColumnMappings.Add("Address2", "Address2"); 
       sqlBulkCopy.ColumnMappings.Add("Address3", "Address3"); 
       sqlBulkCopy.ColumnMappings.Add("Address4", "Address4"); 
       sqlBulkCopy.ColumnMappings.Add("Postcode", "Postcode"); 

       sqlBulkCopy.ColumnMappings.Add("MobileNumber", "MobileNumber"); 
       sqlBulkCopy.ColumnMappings.Add("TelephoneNumber", "TelephoneNumber"); 

       sqlBulkCopy.ColumnMappings.Add("Deleted", "Deleted"); 

       sqlBulkCopy.WriteToServer(dataTable); 
      } 
      transaction.Commit(); 
     } 
     catch (Exception) 
     { 
      transaction.Rollback(); 
     } 

    } 
} 
+1

Я пробовал многие из решений, представленных в этом посте, и SqlBulkCopy был самым быстрым. Pure EF занял 15мин, но с сочетанием решения и SqlBulkCopy я смог спуститься до 1,5 мин! Это было с 2 миллионами записей! Без оптимизации индекса БД. 02 дек. 152015-12-02 09:07:42

  0

Список проще, чем DataTable. Существует способ расширения 'AsDataReader()' '' '' '' ', объясненный в этом ответе: http://stackoverflow.com/a/36817205/1507899 24 апр. 162016-04-24 01:58:02

  0

Но его единственный для верхнего лица не реляционный один 04 июн. 172017-06-04 05:58:52

  0

@ ZahidMustafa: да. Это делает BulkInsert, а не Bulk-Analysis-And-Relation-Tracing-On-Object-Graphs .. Если вы хотите охватить отношения, вам нужно проанализировать и определить порядок вставки, а затем увеличить количество отдельных уровней и, возможно, обновить некоторые ключи, как необходимо, и вы получите быстрое индивидуальное решение. Или вы можете полагаться на EF, чтобы сделать это, без работы на вашей стороне, но медленнее во время выполнения. 30 июн. 172017-06-30 16:15:45


14

Контекст dispose() создает проблемы, если объекты, которые вы добавляете(), полагаются на другие предварительно загруженные объекты (например,навигационные свойства) в контексте

Я использую подобную концепцию, чтобы сохранить свой контекст мал, чтобы достичь той же производительности

Но вместо Dispose() контекст и заново, я просто отделить объекты, которые уже SaveChanges()

public void AddAndSave<TEntity>(List<TEntity> entities) where TEntity : class { 

const int CommitCount = 1000; //set your own best performance number here 
int currentCount = 0; 

while (currentCount < entities.Count()) 
{ 
    //make sure it don't commit more than the entities you have 
    int commitCount = CommitCount; 
    if ((entities.Count - currentCount) < commitCount) 
     commitCount = entities.Count - currentCount; 

    //e.g. Add entities [ i = 0 to 999, 1000 to 1999, ... , n to n+999... ] to conext 
    for (int i = currentCount; i < (currentCount + commitCount); i++)   
     _context.Entry(entities[i]).State = System.Data.EntityState.Added; 
     //same as calling _context.Set<TEntity>().Add(entities[i]);  

    //commit entities[n to n+999] to database 
    _context.SaveChanges(); 

    //detach all entities in the context that committed to database 
    //so it won't overload the context 
    for (int i = currentCount; i < (currentCount + commitCount); i++) 
     _context.Entry(entities[i]).State = System.Data.EntityState.Detached; 

    currentCount += commitCount; 
} } 

обернуть его попытку поймать и TrasactionScope(), если вам нужно, не показывать их здесь держать код чистого

+1

Это замедлило вставку (AddRange) с помощью Entity Framework 6.0. Вставка 20 000 строк увеличилась с 101 секунд до 118 секунд. 24 янв. 142014-01-24 13:12:33

+1

@ Stephen Ho: Я также стараюсь избегать использования моего контекста. Я понимаю, что это медленнее, чем воссоздание контекста, но я хочу знать, нашел ли вы это быстрее, чем не воссоздавать контекст, а с помощью набора commitCount. 09 июл. 142014-07-09 09:02:07

  0

@Learner: Я думаю, что это было быстрее, чем воссоздать контекст. Но я действительно не помню, потому что я перешел на использование SqlBulkCopy. 16 июл. 142014-07-16 09:41:19

  0

Мне пришлось использовать эту технику, потому что, по какой-то странной причине, на втором проходе через цикл while наблюдалось некоторое слежение за ходом, хотя у меня было все, завернутое в оператор using, и даже называемое Dispose() на DbContext. Когда я добавлю к контексту (на 2-м проходе), подсчет набора контекстов переместится на 6 вместо одного. Другие элементы, которые были добавлены произвольно, уже были вставлены в первый проход через цикл while, поэтому вызов SaveChanges завершился неудачно на втором проходе (по понятным причинам). 24 авг. 142014-08-24 13:16:42


18

Я исследовал ответ Slauma (который является удивительным, спасибо F или идея человека), и я уменьшил размер партии, пока не достиг максимальной скорости. Глядя на результатах Slauma в:

  • COMMITCOUNT = 1, recreateContext = TRUE: более 10 минут
  • COMMITCOUNT = 10, recreateContext = истина: 241 сек
  • COMMITCOUNT = 100, recreateContext = истина: 164 сек
  • COMMITCOUNT = 1000, recreateContext = верно: 191 сек

видно, что есть увеличение скорости при переходе от 1 до 10, и от 10 до 100, а от 100 до 1000 скорость вставки падает делать wn снова.

Итак, я сосредоточился на том, что происходит, когда вы уменьшаете размер партии до значения где-то между 10 и 100, и вот мои результаты (я использую другое содержимое строки, поэтому мои времена имеют разное значение):

Quantity | Batch size | Interval 
1000 1 3 
10000 1 34 
100000 1 368 

1000 5 1 
10000 5 12 
100000 5 133 

1000 10 1 
10000 10 11 
100000 10 101 

1000 20 1 
10000 20 9 
100000 20 92 

1000 27 0 
10000 27 9 
100000 27 92 

1000 30 0 
10000 30 9 
100000 30 92 

1000 35 1 
10000 35 9 
100000 35 94 

1000 50 1 
10000 50 10 
100000 50 106 

1000 100 1 
10000 100 14 
100000 100 141 

Основываясь на моих результатах, фактический оптимальный размер составляет около 30 для размера партии. Это меньше, чем 10 и 100. Проблема в том, что я понятия не имею, почему 30 оптимальны, и я не мог найти логического объяснения.

+1

Я нашел то же самое с Postrges и чистым SQL (это зависит от SQL не от EF), что 30 является оптимальным. 13 янв. 162016-01-13 09:52:26


1

Вы когда-нибудь пытались вставить через фона рабочего или задачу?

В моем случае im вставляет 7760 регистров, распределенных в 182 разных таблицах с отношениями внешних ключей (by NavigationProperties). Без проблем, потребовалось 2 минуты с половиной. В задаче (Task.Factory.StartNew(...)) потребовалось 15 секунд.

Im только делает SaveChanges() после добавления всех объектов в контекст. (Для обеспечения целостности данных)

+2

Я уверен, что контекст не является потокобезопасным. У вас есть тесты для обеспечения сохранения всех объектов? 29 июн. 132013-06-29 09:58:03

  0

Я знаю, что вся структура сущности не является потокобезопасной вообще, но я просто добавляю объекты в контекст и сохраняю в конце ... Его работа отлично здесь. 22 июл. 132013-07-22 12:53:23

  0

Итак, вы вызываете DbContext.SaveChanges() в основном потоке, но добавление сущностей в контекст выполняется в фоновом потоке, правильно? 31 мар. 142014-03-31 13:18:48

+1

Да, добавьте данные в потоки; ждать, пока все закончится; и Сохранить изменения в основной теме 01 апр. 142014-04-01 17:31:20

  0

Хотя я думаю, что этот путь опасен и подвержен ошибкам, мне это очень интересно. 09 июл. 142014-07-09 10:03:20


2

Вот сравнение производительности между использованием Entity Framework и используя класс SqlBulkCopy на реалистичный пример: How to Bulk Insert Complex Objects into SQL Server Database

Как и другие уже подчеркивалось, ORMs не предназначены для использования в массовых операций. Они предлагают гибкость, разделение проблем и других преимуществ, но массовые операции (кроме массового чтения) не являются одним из них.


-3

Во-первых - он работает гораздо быстрее (примерно в 10 раз), когда проект компилировать в Release не в Debug

Во-вторых - если есть серьезная проблема производительности - изолировать это место в коде и переписать его с помощью ADO стол- Нормированные-параметры. Он будет работать намного быстрее.

+1

Я пробовал как Release, так и Debug, и время оставалось неизменным. 23 сен. 152015-09-23 07:57:47


100

Самый быстрый способ будет использовать bulk insert extension, который я разработал.

Он использует SqlBulkCopy и пользовательский datareader для достижения максимальной производительности. В результате он более 20 раз быстрее, чем с помощью регулярной вставки или AddRange EntityFramework.BulkInsert vs EF AddRange

использования чрезвычайно прост

context.BulkInsert(hugeAmountOfEntities); 
  0

Мой контекст не имеет .BulkInsert, почему бы это быть/Использование EF 6.1 22 июл. 142014-07-22 11:10:32

+9

Это круто. но таблица отношений не вставлена. в EF 6.1 31 авг. 142014-08-31 06:29:48

+1

Прохладная обертка/инструмент, но постоянный недостаток BulkCopy заключается в том, что он не поддерживает upserts напрямую - и я действительно не хочу добавлять промежуточную таблицу в мои промежуточные таблицы. 05 фев. 152015-02-05 21:07:11

+8

Быстро, но только верхний слой иерархии. 29 апр. 152015-04-29 04:02:21

+1

Я попытался использовать это решение, но порядок столбцов, отображаемых при массовом обновлении, отличается от фактического, поэтому, если у меня есть столбцы в db, так как ABCD и соответствующий объект имеют данные для объектов ABCD, значения ошибочно отображаются на что-то вроде BCAD. Знаете ли вы, что это может быть? 04 июн. 152015-06-04 16:11:21

+6

@Muds Я также нашел пару ошибок с этой библиотекой и библиотекой MappingAPI, которую он использует. У меня был лучший успех с библиотекой EFUtilities из [этого другого ответа] (http://stackoverflow.com/a/27564352/1730559). Он также имеет другие утилиты, в том числе для пакетных обновлений. 09 ноя. 152015-11-09 07:15:04

  0

Удивительно, я пошел от нескольких минут до нескольких секунд! Я использовал AddRange() в EF 6, даже если отключено обнаружение изменений, было еще минут, чтобы сделать всего 1200 записей с 110 столбцами. Отличная работа, спас меня, написав мою собственную. 06 май. 162016-05-06 18:00:48

  0

Нужно ли вызывать SaveChanges после запуска BulkInsert, или BulkInsert работает непосредственно в базе данных? Например, я добавляю запись «Сообщение» и вызываю SaveChanges, потому что идентификатор сообщения будет использоваться в качестве внешнего ключа, когда BulkInsert вызывается для добавления записей в ящик пользователя, которые ссылаются на сообщение. Поэтому я делаю: добавьте сообщение, вызовите SaveChanges для фиксации нового сообщения, вызовите BulkInsert для добавления входящих почтовых ящиков. И мне интересно, нужен ли мне еще один вызов SaveChanges после BulkInsert или если BulkInsert напрямую связывается с базой данных, так что SaveChanges не нуждается в вызове впоследствии. 12 июл. 162016-07-12 17:56:53

+1

@ Трийенко. Не нужно вызывать SaveChanges. Инструкции по использованию находятся здесь https://efbulkinsert.codeplex.com/ 18 июл. 162016-07-18 20:39:49

  0

Я использовал это, но он поднял ошибку «данный ключ не присутствовал в словаре» мой случай - список сущностей с отношением, но я пытаюсь вставить без объект отношения 15 авг. 162016-08-15 14:19:26

  0

@muds возникает проблема с массовым обновлением. Порядок свойств в классе сущности должен быть таким же, как порядок столбцов в вашей таблице базы данных. 09 сен. 162016-09-09 18:36:24

  0

Ни в коем случае, это улучшило мои коды для вставки 400k + строк (с 5 столбцами и без ограничений по индексу) с 60 + мин (используя технику из принятого ответа) до 10.2 секунд. Вы только что спасли мою вторую половинку! 14 окт. 162016-10-14 13:47:32

+12

Это не бесплатно. 04 июн. 172017-06-04 14:41:24

+18

Объявления становятся умнее ... это платное произведение и очень дорого для внештатного. Имейте в виду! 13 июн. 172017-06-13 17:30:38

+3

К сожалению, это уже не FOSS, а в какой-то момент владелец переместил его в другой домен и начал взимать плату за объемную версию вставки. Это уже не подходящий вариант :-( 27 июн. 172017-06-27 01:12:30

+5

USD600 за 1 год поддержки и обновлений? Вы сошли с ума? 18 сен. 172017-09-18 10:37:16

+3

im не является владельцем продукта 25 сен. 172017-09-25 12:57:29

  0

Это выглядит классно, но у меня нет таких денег :) 10 янв. 182018-01-10 07:10:39

  0

Есть несколько вилок BulkInsert, которые можно найти на GitHub, прежде чем источник кода был куплен ZZZ Projects. [Этот] (https://github.com/ghost1face/EntityFramework.BulkInsert) можно найти на NuGet. 08 фев. 182018-02-08 15:35:37


16

Я рекомендовал бы эту статью о том, как сделать объемные вставки с помощью EF.

Entity Framework and slow bulk INSERTs

Он исследует эту область и сравнивает сисем:

  1. По умолчанию EF (57 минут для завершения добавления 30000 записей)
  2. Заменив код ADO.NET (25 секунд для тех, то же 30 000)
  3. Context Bloat-Keep active Context Graph small, используя новый контекст для каждого модуля работы (то же самое, 30 000 вставок занимают 33 секунды)
  4. Больших списков - Выключайте AutoDetectChangesEnabled (приносит время примерно до 20 секунд)
  5. БСА (до 16 секунд)
  6. DbTable.AddRange() - (производительность в 12-диапазоне)

13

Как говорили другие люди, SqlBulkCopy - это способ сделать это, если вы хотите действительно хорошую производительность вставки.

Это немного громоздко реализовать, но есть библиотеки, которые могут вам помочь. Есть несколько там, но я shamelesslyplug моя собственная библиотека на этот раз: https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

Единственный код, который нужно будет это:

using (var db = new YourDbContext()) 
{ 
    EFBatchOperation.For(db, db.BlogPosts).InsertAll(list); 
} 

Так насколько быстрее это? Очень сложно сказать, потому что это зависит от множества факторов, производительности компьютера, сети, размера объекта и т. Д. Проведенные тесты производительности предполагают, что объекты размером 25 тыс. Могут быть вставлены примерно в 10 секунд стандартным способом на localhost ЕСЛИ вы оптимизируете свой EF как упомянуто в других ответах. С EFUtilities, который занимает около 300 мс. Еще более интересным является то, что я сохранил около 3 миллионов объектов менее чем за 15 секунд, используя этот метод, составляющий в среднем около 200 тыс. Сущ. В секунду.

Одна проблема, конечно, если вам нужно вставить выпущенные данные. Это можно сделать эффективно в sql-сервере, используя вышеописанный метод, но для этого требуется, чтобы у вас была стратегия генерации идентификаторов, позволяющая генерировать идентификатор в app-коде для родителя, чтобы вы могли установить внешние ключи. Это можно сделать с помощью GUID или что-то вроде генерации идентификатора HiLo.

  0

Хорошо работает. Синтаксис немного подробный. Подумайте, было бы лучше, если бы у 'EFBatchOperation' был конструктор, который вы передаете в' DbContext', вместо того, чтобы переходить к каждому статическому методу. Также будут полезны общие версии 'InsertAll' и' UpdateAll', которые автоматически находят коллекцию, подобную 'DbContext.Set <T>'. 10 ноя. 152015-11-10 00:44:43

  0

Просто быстрый комментарий, чтобы сказать спасибо! Этот код позволил мне сэкономить 170 тыс. Записей за 1,5 секунды! Полностью ударяет любой другой метод, который я пробовал из воды. 05 дек. 162016-12-05 15:39:45

  0

@Mikael Одна проблема касается полей идентичности. У вас есть способ включить идентификационную вставку? 17 апр. 172017-04-17 21:57:01


1

Все решения, написанные здесь, не помогают, потому что когда вы делаете SaveChanges(), вставляемые операторы отправляются в базу данных по одному, вот как работает Entity.

И если ваша поездка в базу данных и обратно составляет 50 мс, то время, необходимое для вставки, - это количество записей x 50 мс.

Вы должны использовать BulkInsert, вот ссылка: https://efbulkinsert.codeplex.com/

Я получил вставки время сократилось с 5-6 минут до 10-12 секунд, используя его.


2

Я сделал общее расширение примера @Slauma выше;

public static class DataExtensions 
{ 
    public static DbContext AddToContext<T>(this DbContext context, object entity, int count, int commitCount, bool recreateContext, Func<DbContext> contextCreator) 
    { 
     context.Set(typeof(T)).Add((T)entity); 

     if (count % commitCount == 0) 
     { 
      context.SaveChanges(); 
      if (recreateContext) 
      { 
       context.Dispose(); 
       context = contextCreator.Invoke(); 
       context.Configuration.AutoDetectChangesEnabled = false; 
      } 
     } 
     return context; 
    } 
} 

Использование:

public void AddEntities(List<YourEntity> entities) 
{ 
    using (var transactionScope = new TransactionScope()) 
    { 
     DbContext context = new YourContext(); 
     int count = 0; 
     foreach (var entity in entities) 
     { 
      ++count; 
      context = context.AddToContext<TenancyNote>(entity, count, 100, true, 
       () => new YourContext()); 
     } 
     context.SaveChanges(); 
     transactionScope.Complete(); 
    } 
} 

3

Другой вариант заключается в использовании SqlBulkTools доступных из NuGet. Он очень прост в использовании и обладает некоторыми мощными функциями.

Пример:

var bulk = new BulkOperations(); 
var books = GetBooks(); 

using (TransactionScope trans = new TransactionScope()) 
{ 
    using (SqlConnection conn = new SqlConnection(ConfigurationManager 
    .ConnectionStrings["SqlBulkToolsTest"].ConnectionString)) 
    { 
     bulk.Setup<Book>() 
      .ForCollection(books) 
      .WithTable("Books") 
      .AddAllColumns() 
      .BulkInsert() 
      .Commit(conn); 
    } 

    trans.Complete(); 
} 

См the documentation для большего количества примеров и расширенного использования. Отказ от ответственности: я являюсь автором этой библиотеки, и любые мнения касаются моего собственного мнения.

+1

Этот проект был удален из NuGet и GitHub. 04 июл. 172017-07-04 06:28:17


3

Я ищу самый быстрый способ вставки в Entity Framework

Существует некоторая библиотека третья сторона поддерживает Bulk Insert в наличии:

  • Z.EntityFramework.Extensions (Рекомендовано)
  • EFUtilities
  • EntityFramework.BulkInsert

См: Entity Framework Bulk Insert library

Будьте осторожны при выборе объемной библиотеки вставки. Только расширения Entity Framework Extensions поддерживают все виды ассоциаций и наследования, и это единственное, что поддерживается.


Отказ от ответственности: Я владелец Entity Framework Extensions

Эта библиотека позволяет выполнять все массовые операции, необходимые для сценариев:

  • Сыпучие SaveChanges
  • Сыпучие Вставка
  • Bulk Удалить
  • Массовое обновление
  • Bulk Слияние

Пример

// Easy to use 
context.BulkSaveChanges(); 

// Easy to customize 
context.BulkSaveChanges(bulk => bulk.BatchSize = 100); 

// Perform Bulk Operations 
context.BulkDelete(customers); 
context.BulkInsert(customers); 
context.BulkUpdate(customers); 

// Customize Primary Key 
context.BulkMerge(customers, operation => { 
    operation.ColumnPrimaryKeyExpression = 
     customer => customer.Code; 
}); 
+8

это отличное расширение, но ** не бесплатно **. 01 апр. 172017-04-01 08:54:27

+2

Этот ответ довольно хорош и [EntityFramework.BulkInsert] (https://www.nuget.org/packages/EntityFramework.BulkInsert-ef6-ext) выполняет массовую вставку из 15 тыс. Строк за 1,5 секунды, работает довольно хорошо для внутреннего как служба Windows. 27 июн. 172017-06-27 23:57:39


1

Вы можете использовать Bulk package библиотеку. Версия Bulk Insert 1.0.0 используется в проектах с инфраструктурой Entity> = 6.0.0.

Более описание можно найти здесь- Bulkoperation source code


2

Использование SqlBulkCopy:

void BulkInsert(GpsReceiverTrack[] gpsReceiverTracks) 
{ 
    if (gpsReceiverTracks == null) 
    { 
     throw new ArgumentNullException(nameof(gpsReceiverTracks)); 
    } 

    DataTable dataTable = new DataTable("GpsReceiverTracks"); 
    dataTable.Columns.Add("ID", typeof(int)); 
    dataTable.Columns.Add("DownloadedTrackID", typeof(int)); 
    dataTable.Columns.Add("Time", typeof(TimeSpan)); 
    dataTable.Columns.Add("Latitude", typeof(double)); 
    dataTable.Columns.Add("Longitude", typeof(double)); 
    dataTable.Columns.Add("Altitude", typeof(double)); 

    for (int i = 0; i < gpsReceiverTracks.Length; i++) 
    { 
     dataTable.Rows.Add 
     (
      new object[] 
      { 
        gpsReceiverTracks[i].ID, 
        gpsReceiverTracks[i].DownloadedTrackID, 
        gpsReceiverTracks[i].Time, 
        gpsReceiverTracks[i].Latitude, 
        gpsReceiverTracks[i].Longitude, 
        gpsReceiverTracks[i].Altitude 
      } 
     ); 
    } 

    string connectionString = (new TeamTrackerEntities()).Database.Connection.ConnectionString; 
    using (var connection = new SqlConnection(connectionString)) 
    { 
     connection.Open(); 
     using (var transaction = connection.BeginTransaction()) 
     { 
      using (var sqlBulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.TableLock, transaction)) 
      { 
       sqlBulkCopy.DestinationTableName = dataTable.TableName; 
       foreach (DataColumn column in dataTable.Columns) 
       { 
        sqlBulkCopy.ColumnMappings.Add(column.ColumnName, column.ColumnName); 
       } 

       sqlBulkCopy.WriteToServer(dataTable); 
      } 
      transaction.Commit(); 
     } 
    } 

    return; 
} 

1

[НОВОЕ РЕШЕНИЕ ДЛЯ PostGreSQL] Эй, я знаю, что это довольно старый пост, но я недавно столкнулся аналогичная проблема, но мы использовали Postgresql. Я хотел использовать эффективный bulkinsert, что оказалось довольно сложным. Я не нашел в этой БД надлежащей бесплатной библиотеки. Я нашел только этого помощника: https://bytefish.de/blog/postgresql_bulk_insert/ который также находится на Nuget. Я написал небольшой картографа, который автоматически отображенный Пропертис путь Entity Framework:

public static PostgreSQLCopyHelper<T> CreateHelper<T>(string schemaName, string tableName) 
     { 
      var helper = new PostgreSQLCopyHelper<T>("dbo", "\"" + tableName + "\""); 
      var properties = typeof(T).GetProperties(); 
      foreach(var prop in properties) 
      { 
       var type = prop.PropertyType; 
       if (Attribute.IsDefined(prop, typeof(KeyAttribute)) || Attribute.IsDefined(prop, typeof(ForeignKeyAttribute))) 
        continue; 
       switch (type) 
       { 
        case Type intType when intType == typeof(int) || intType == typeof(int?): 
         { 
          helper = helper.MapInteger("\"" + prop.Name + "\"", x => (int?)typeof(T).GetProperty(prop.Name).GetValue(x, null)); 
          break; 
         } 
        case Type stringType when stringType == typeof(string): 
         { 
          helper = helper.MapText("\"" + prop.Name + "\"", x => (string)typeof(T).GetProperty(prop.Name).GetValue(x, null)); 
          break; 
         } 
        case Type dateType when dateType == typeof(DateTime) || dateType == typeof(DateTime?): 
         { 
          helper = helper.MapTimeStamp("\"" + prop.Name + "\"", x => (DateTime?)typeof(T).GetProperty(prop.Name).GetValue(x, null)); 
          break; 
         } 
        case Type decimalType when decimalType == typeof(decimal) || decimalType == typeof(decimal?): 
         { 
          helper = helper.MapMoney("\"" + prop.Name + "\"", x => (decimal?)typeof(T).GetProperty(prop.Name).GetValue(x, null)); 
          break; 
         } 
        case Type doubleType when doubleType == typeof(double) || doubleType == typeof(double?): 
         { 
          helper = helper.MapDouble("\"" + prop.Name + "\"", x => (double?)typeof(T).GetProperty(prop.Name).GetValue(x, null)); 
          break; 
         } 
        case Type floatType when floatType == typeof(float) || floatType == typeof(float?): 
         { 
          helper = helper.MapReal("\"" + prop.Name + "\"", x => (float?)typeof(T).GetProperty(prop.Name).GetValue(x, null)); 
          break; 
         } 
        case Type guidType when guidType == typeof(Guid): 
         { 
          helper = helper.MapUUID("\"" + prop.Name + "\"", x => (Guid)typeof(T).GetProperty(prop.Name).GetValue(x, null)); 
          break; 
         } 
       } 
      } 
      return helper; 
     } 

Я использую его следующим образом (я объект с именем Undertaking):

var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking)); 
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd)); 

я показал пример с сделки , но это также можно сделать с обычным подключением, полученным из контекста. ПредпринимателиToAdd перечислены в нормальных записей сущностей, которые я хочу загрузить в базу данных.

Это решение, которое я получил после нескольких часов исследований и попыток, так же, как вы могли ожидать, намного быстрее и, наконец, легко использовать и бесплатно! Я действительно советую вам использовать это решение не только по причинам, упомянутым выше, но и потому, что это единственный, с которым у меня не было проблем с самим Postgresql, многие другие решения работают безупречно, например, с SqlServer.


0

Но для более чем (+4000) вставок я рекомендую использовать хранимую процедуру. приложенное время прошло. я вставил 11.788 строк в 20" enter image description here

Thats это код

public void InsertDataBase(MyEntity entity) 
    { 
     repository.Database.ExecuteSqlCommand("sp_mystored " + 
       "@param1, @param2" 
       new SqlParameter("@param1", entity.property1), 
       new SqlParameter("@param2", entity.property2)); 
    } 

0

Используйте хранимую процедуру, которая принимает входные данные в виде XML для вставки данных.

С вашей C# код прохода вставки . данные как XML

например, в C#, синтаксис будет выглядеть так:

object id_application = db.ExecuteScalar("procSaveApplication", xml) 

0

Я знаю, что это очень старый вопрос, но один парень сказал, что разработал метод расширения для использования массовой вставки с EF, и когда я проверил, я обнаружил, что библиотека стоит 599 долларов сегодня (для одного разработчика). Может быть, это имеет смысл для всей библиотеки, однако для просто большой вставки это слишком много.

Здесь очень простой метод расширения, который я сделал. Я сначала использую это в паре с базой данных (сначала не проверяюсь с кодом, но я думаю, что работает одинаково). Изменение YourEntities с именем вашего контекста:

public partial class YourEntities : DbContext 
{ 
    public async Task BulkInsertAllAsync<T>(IEnumerable<T> entities) 
    { 
     using (var conn = new SqlConnection(Database.Connection.ConnectionString)) 
     { 
      conn.Open(); 

      Type t = typeof(T); 

      var bulkCopy = new SqlBulkCopy(conn) 
      { 
       DestinationTableName = GetTableName(t) 
      }; 

      var table = new DataTable(); 

      var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string)); 

      foreach (var property in properties) 
      { 
       Type propertyType = property.PropertyType; 
       if (propertyType.IsGenericType && 
        propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 
       { 
        propertyType = Nullable.GetUnderlyingType(propertyType); 
       } 

       table.Columns.Add(new DataColumn(property.Name, propertyType)); 
      } 

      foreach (var entity in entities) 
      { 
       table.Rows.Add(
        properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray()); 
      } 

      bulkCopy.BulkCopyTimeout = 0; 
      await bulkCopy.WriteToServerAsync(table); 
     } 
    } 

    public void BulkInsertAll<T>(IEnumerable<T> entities) 
    { 
     using (var conn = new SqlConnection(Database.Connection.ConnectionString)) 
     { 
      conn.Open(); 

      Type t = typeof(T); 

      var bulkCopy = new SqlBulkCopy(conn) 
      { 
       DestinationTableName = GetTableName(t) 
      }; 

      var table = new DataTable(); 

      var properties = t.GetProperties().Where(p => p.PropertyType.IsValueType || p.PropertyType == typeof(string)); 

      foreach (var property in properties) 
      { 
       Type propertyType = property.PropertyType; 
       if (propertyType.IsGenericType && 
        propertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) 
       { 
        propertyType = Nullable.GetUnderlyingType(propertyType); 
       } 

       table.Columns.Add(new DataColumn(property.Name, propertyType)); 
      } 

      foreach (var entity in entities) 
      { 
       table.Rows.Add(
        properties.Select(property => property.GetValue(entity, null) ?? DBNull.Value).ToArray()); 
      } 

      bulkCopy.BulkCopyTimeout = 0; 
      bulkCopy.WriteToServer(table); 
     } 
    } 

    public string GetTableName(Type type) 
    { 
     var metadata = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace; 
     var objectItemCollection = ((ObjectItemCollection)metadata.GetItemCollection(DataSpace.OSpace)); 

     var entityType = metadata 
       .GetItems<EntityType>(DataSpace.OSpace) 
       .Single(e => objectItemCollection.GetClrType(e) == type); 

     var entitySet = metadata 
      .GetItems<EntityContainer>(DataSpace.CSpace) 
      .Single() 
      .EntitySets 
      .Single(s => s.ElementType.Name == entityType.Name); 

     var mapping = metadata.GetItems<EntityContainerMapping>(DataSpace.CSSpace) 
       .Single() 
       .EntitySetMappings 
       .Single(s => s.EntitySet == entitySet); 

     var table = mapping 
      .EntityTypeMappings.Single() 
      .Fragments.Single() 
      .StoreEntitySet; 

     return (string)table.MetadataProperties["Table"].Value ?? table.Name; 
    } 
} 

Вы можете использовать это против любой коллекции, которые наследуют от IEnumerable, как это:

await context.BulkInsertAllAsync(items);