Найшвидший спосіб включення в Entity Framework


682

Я шукаю найшвидший спосіб вставити в Entity Framework.

Я запитую це через сценарій, коли у вас є активний TransactionScope, а вставка величезна (4000+). Це потенційно може тривати більше 10 хвилин (час очікування транзакцій за замовчуванням), і це призведе до неповної транзакції.


1
Як ти зараз це робиш?
Дастін Лайн

Створення TransactionScope, інстанціювання DBContext, відкриття з'єднання та для кожного оператора, що виконує вставки та SavingChanges (для кожного запису), ПРИМІТКА. TransactionScope та DBContext використовують оператори, і я закриваю з'єднання нарешті блок
Bongo Sharp

Ще одна відповідь для довідки: stackoverflow.com/questions/5798646/…
Ладіслав Мрнка

2
Найшвидший спосіб вставлення в базу даних SQL не передбачає використання EF. AFAIK Його BCP потім TVP + Об'єднання / вставка.
StingyJack

1
Для тих, хто прочитає коментарі: Найбільш застосовна, сучасна відповідь - тут.
Танвер Бадар

Відповіді:


986

На ваше зауваження в коментарях до вашого питання:

"... Збереження змін ( для кожного запису ) ..."

Це найгірше, що ти можеш зробити! Виклик 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 . Саме повільний розмір доданих об'єктів у контексті сповільнює вставку поетапно. Отже, корисно це очистити через деякий час.

Ось кілька вимірювань для моїх 560000 осіб:

  • commitCount = 1, recreateContext = false: багато годин (Це ваша поточна процедура)
  • commitCount = 100, recreateContext = false: більше 20 хвилин
  • commitCount = 1000, recreateContext = false: 242 сек
  • commitCount = 10000, recreateContext = false: 202 сек
  • commitCount = 100000, recreateContext = false: 199 сек
  • commitCount = 1000000, recreateContext = false: виняток із пам'яті
  • commitCount = 1, recreateContext = true: більше 10 хвилин
  • commitCount = 10, recreateContext = true: 241 сек
  • commitCount = 100, recreateContext = true: 164 сек
  • commitCount = 1000, recreateContext = true: 191 сек

Поведінка в першому тесті, наведеному вище, полягає в тому, що продуктивність дуже нелінійна і з часом сильно знижується. ("Багато годин" - це оцінка. Я ніколи не закінчував цей тест, я зупинився на 50 000 осіб через 20 хвилин.) Ця нелінійна поведінка не настільки значна у всіх інших тестах.


89
@Bongo Sharp: Не забудьте встановити AutoDetectChangesEnabled = false;DbContext. Він також має великий додатковий ефект продуктивності: stackoverflow.com/questions/5943394 / ...
Slauma

6
Так, проблема полягає в тому, що я використовую Entity Framework 4, і AutoDetectChangesEnabled є частиною 4.1, проте, я зробив тест на працездатність, і у мене були вражаючі результати, він пройшов з 00:12:00 до 00:00:22 SavinChanges на кожну організацію робили olverload ... ДЯКУЄТЕ так багато за ваше програмне забезпечення! це те, що я шукав
Bongo Sharp

10
Дякую за контекст.Configuration.AutoDetectChangesEnabled = false; підказка, це робить величезну різницю.
дуглаз

1
@ dahacker89: Ви використовуєте правильну версію EF> = 4.1 та DbContext, НЕ ObjectContext?
Слаума

3
@ dahacker89: Я пропоную вам створити окреме питання для вашої проблеми, можливо, більше деталей. Я не в змозі зрозуміти, що тут не так.
Слаума

176

Ця комбінація досить швидко збільшує швидкість.

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

46
Не сліпо вимикайте ValidateOnSaveEnabled, можливо, ви залежите від такої поведінки, і не усвідомлюйте це, поки не пізно. Тоді знову ви можете проводити перевірку в іншому місці коду, а повторне підтвердження EF ще зовсім не потрібне.
Джеремі Кук

1
У моєму тесті економія 20 000 рядків знизилася з 101 секунди до 88 секунд. Не багато і які наслідки.
АГ.

27
@JeremyCook Я думаю, що те, що ви намагаєтеся отримати, - це відповідь було б набагато краще, якби це пояснило можливі наслідки зміни цих властивостей від значень за замовчуванням (окрім підвищення продуктивності). Я згоден.
псевдокодер

1
Це працювало для мене, хоча якщо ви оновлюєте записи в контексті, вам потрібно буде чітко зателефонувати до DetectChanges ()
hillstuk

2
Їх можна відключити, а потім знову ввімкнути за допомогою блоку спробу
yellavon

98

Найшвидшим способом було б використання розширення з масовими вставками , яке я розробив

Примітка: це комерційний продукт, не безкоштовний

Для отримання максимальної продуктивності використовується SqlBulkCopy та користувацький читач даних. В результаті це в 20 разів швидше, ніж використання звичайної вставки або AddRange EntityFramework.BulkInsert проти EF AddRange

використання надзвичайно просте

context.BulkInsert(hugeAmountOfEntities);

10
Швидкий, але тільки верхній шар ієрархії.
CAD заблокували

65
Це не безкоштовно.
Амір Саніян

72
Оголошення стають розумнішими ... це платний продукт і дуже дорогий для фрілансу. Будьте попереджені!
JulioQc

35
600 доларів США за 1-річну підтримку та оновлення? Ви не з розуму?
Каміло Теревінто

7
я більше не є власником товару
maxlego

83

Ви повинні подивитися на використання System.Data.SqlClient.SqlBulkCopyдля цього. Ось документація , і звичайно, навчальних посібників в Інтернеті багато.

Вибачте, я знаю, що ви шукали просту відповідь, щоб змусити EF робити те, що ви хочете, але масові операції - це насправді не те, для чого призначені ORM.


1
Я кілька разів наткнувся на SqlBulkCopy, вивчаючи це, але, схоже, він більше орієнтований на вставки «таблиця до таблиці», на жаль, я не очікував легких рішень, а, скоріше, поради щодо ефективності, як, наприклад, управління державою підключення вручну, що дозволяє дозволити EF зробити це за вас
Bongo Sharp

7
Я використовував SqlBulkCopy, щоб вставити велику кількість даних прямо з моєї програми. Ви в основному повинні створити DataTable, заповнити її, а потім передати , що в BulkCopy. Коли ви налаштовуєте свій DataTable, є декілька проблем, які, на жаль, я забув), але це повинно працювати добре
Адам Раккіс,

2
Я зробив доказ концепції, і, як було обіцяно, вона працює дуже швидко, але одна з причин, чому я використовую EF, полягає в тому, що вставлення реляційних даних простіше, наприклад, якщо я вставлю сутність, яка вже містить реляційні дані , він також вставить його, ви коли-небудь потрапляли в цей сценарій? Дякую!
Bongo Sharp

2
На жаль, вставлення Інтернету об’єктів у СУБД насправді не те, що буде робити BulkCopy. Це перевага ORM, як EF, що полягає в тому, що він не зможе ефективно робити сотні подібних об'єктних графіків.
Адам Ракіс

2
SqlBulkCopy - це безперечно шлях, якщо вам потрібна швидка швидкість або якщо ви будете повторно запускати цю вставку. Раніше я вставив кілька мільйонів записів, і це надзвичайно швидко. Однак, якщо вам не знадобиться повторно запустити цю вставку, просто використовувати EF може бути простіше.
Ніл

49

Я згоден з Адамом Ракісом. SqlBulkCopyце найшвидший спосіб передачі масових записів з одного джерела даних в інший. Я використовував це для копіювання записів 20K, і це зайняло менше 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 мільйонами записів! Без оптимізації індексу БД.
jonas

Список простіше, ніж DataTable. Там є AsDataReader()метод розширення, пояснює в цій відповіді: stackoverflow.com/a/36817205/1507899
RJB

Але це лише для верхньої сутності, а не реляційне
Захід Мустафа

1
@ZahidMustafa: так. Це робить BulkInsert, а не Bulk-Analysis-And-Relation-Tracing-On-Object-Graphs. Якщо ви хочете охопити відносини, вам доведеться проаналізувати та визначити порядок вставки, а потім масово вставити окремі рівні та, можливо, оновити деякі клавіші як потрібно, і ви отримаєте швидке індивідуальне рішення. Або ви можете розраховувати на EF, щоб це зробити, без вашої роботи, але повільніше під час виконання.
кетсалькоатл

23

Я б рекомендував цю статтю про те, як робити об'ємні вставки за допомогою EF.

Entity Framework та повільні масові ВСТАВКИ

Він досліджує ці області та порівнює ефективність:

  1. EF за замовчуванням (57 хвилин, щоб додати 30000 записів)
  2. Заміна кодом ADO.NET (25 секунд для тих же 30 000)
  3. Context Bloat - збережіть активну графіку контексту невеликою, використовуючи новий контекст для кожної одиниці роботи (ті ж 30 000 вставок займають 33 секунди)
  4. Великі списки - вимкніть функцію AutoDetectChangesEnabled (зменшує час приблизно до 20 секунд)
  5. Дозування (до 16 секунд)
  6. DbTable.AddRange () - (продуктивність в діапазоні 12)

21

як ніколи тут не згадувались, я хочу рекомендувати EFCore.BulkExtensions тут

context.BulkInsert(entitiesList);                 context.BulkInsertAsync(entitiesList);
context.BulkUpdate(entitiesList);                 context.BulkUpdateAsync(entitiesList);
context.BulkDelete(entitiesList);                 context.BulkDeleteAsync(entitiesList);
context.BulkInsertOrUpdate(entitiesList);         context.BulkInsertOrUpdateAsync(entitiesList);         // Upsert
context.BulkInsertOrUpdateOrDelete(entitiesList); context.BulkInsertOrUpdateOrDeleteAsync(entitiesList); // Sync
context.BulkRead(entitiesList);                   context.BulkReadAsync(entitiesList);

1
Я друге цю пропозицію. Спробувавши багато рішень для домашнього приготування, це зменшило мою вставку до 1 секунди протягом понад 50 секунд. І ліцензію MIT так легко включити.
SouthShoreAK

це допомога для ef 6.x
Alok

це лише більш ефективно, ніж використання AddRange, якщо це більше 10 об'єктів
Шакал,

5
10 000 вставок пройшли від 9 хвилин до 12 секунд. Це заслуговує на більшу увагу!
callisto

2
Якщо є якийсь спосіб змінити прийняті відповіді, це повинна бути сучасна прийнята відповідь. І я б хотів, щоб команда EF надала це поза межами.
Tanveer Badar

18

Я досліджував відповідь Слауми (що приголомшливо, дякую людині ідеї), і я зменшив розмір партії, поки не досяг оптимальної швидкості. Дивлячись на результати Слауми:

  • commitCount = 1, recreateContext = true: більше 10 хвилин
  • commitCount = 10, recreateContext = true: 241 сек
  • commitCount = 100, recreateContext = true: 164 сек
  • commitCount = 1000, recreateContext = true: 191 сек

Видно, що при русі від 1 до 10 і від 10 до 100 спостерігається збільшення швидкості, але швидкість вставки з 100 до 1000 знову падає.

Тому я зосередився на тому, що відбувається, коли ви зменшуєте розмір партії до значення десь від 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 оптимальних, і я не міг знайти для цього жодного логічного пояснення.


2
Я виявив те саме з Postrges та чистим SQL (це залежить від SQL, а не від EF), що 30 є оптимальним.
Каміль Гареєв

Мій досвід полягає в тому, що оптимум відрізняється для різної швидкості з'єднання та розміру рядка. Для швидкого з'єднання та невеликих рядків оптимальним може бути навіть> 200 рядів.
цзин

18

Як кажуть інші люди, SqlBulkCopy - це спосіб зробити це, якщо ви хочете по-справжньому хороші показники вставки.

Це трохи громіздко реалізувати, але є бібліотеки, які можуть вам у цьому допомогти. Там є кілька, але я цього разу безсоромно підключую власну бібліотеку: 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, використовуючи метод, описаний вище, але це вимагає від вас стратегії генерації Id, яка дозволяє генерувати ідентифікатори в коді програми для батьків, щоб ви могли встановити зовнішні ключі. Це можна зробити за допомогою GUID або щось подібне до генерації HiLo id.


Добре працює. Синтаксис є дещо багатослівним. Подумайте, що було б краще, якби EFBatchOperationбув конструктор, який ви DbContextпередаєте в, а не переходите до кожного статичного методу. Загальні версії InsertAllта, UpdateAllякі автоматично знаходять колекцію, подібні до DbContext.Set<T>, також було б добре.
kjbartel

Просто швидкий коментар, щоб сказати спасибі! Цей код дозволив мені зберегти 170 к записів за 1,5 секунди! Повністю видуває будь-який інший метод, який я випробував з води.
Том Гленн

@Mikael Одне питання стосується полів ідентичності. Чи є у вас ще спосіб включити особу для вставки?
Джо Філіпс

1
На відміну від EntityFramework.BulkInsert, ця бібліотека залишалася безкоштовною. +1
Rudey

14

Dispose()контекст створює проблеми, якщо об'єкти, на яких ви Add()покладаєтесь на інші попередньо завантажені об'єкти (наприклад, властивості навігації) у контексті

Я використовую подібну концепцію, щоб мій контекст був малим, щоб досягти тієї ж продуктивності

Але замість 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 секунд.
АГ.

1
@Stephen Ho: Я також намагаюся уникати розкриття свого контексту. Я можу зрозуміти, що це повільніше, ніж відтворювати контекст, але я хочу знати, чи ви виявили це досить швидко, ніж не відтворити контекст, а встановити набір комісійного комітету.
Учень

@Learner: Я думаю, що це було швидше, ніж відтворити контекст. Але я зараз не пам'ятаю, тому що, нарешті, я перейшов використовувати SqlBulkCopy.
Стівен Хо

Мені в кінцевому підсумку довелося скористатися цією технікою, тому що з якоїсь дивної причини відслідковувалося деяке відстеження, що виникає під час другого проходу через цикл while, хоча у мене все було загорнуте у оператор, що використовує, і навіть називається Dispose () на DbContext . Коли я додав би до контексту (на 2-му проході), кількість наборів контексту підскочить до 6, а не лише до одного. Інші елементи, які були довільно додані, вже були вставлені в перший прохід через цикл while, тому виклик SaveChanges не вдалося би під час другого проходу (з очевидних причин).
Hallmanac

9

Я знаю, що це дуже давнє запитання, але один хлопець тут сказав, що розробив метод розширення, щоб використовувати об'ємну вставку з EF, і коли я перевірив, я виявив, що бібліотека сьогодні коштує $ 599 (для одного розробника). Можливо, це має сенс для всієї бібліотеки, проте для простої масової вставки це занадто багато.

Ось дуже простий метод розширення, який я зробив. Я використовую це в парі з базою даних спочатку (не тестуйте спочатку з кодом, але я думаю, що це працює так само). Змініть YourEntitiesназву вашого контексту:

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

            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);

будь ласка, заповніть свій приклад код. де bulkCopy
Seabizkit

1
Це вже тут:await bulkCopy.WriteToServerAsync(table);
Гільгерме

Можливо, мені було не зрозуміло, у вашому записі ви пропонуєте зробити розширення ..., що я мав на увазі, що жодна вкладка 3-ї частини не потрібна, коли насправді в обох методах використовується lib SqlBulkCopy. Це повністю покладається на SqlBulkCopy, і чому я запитав, звідки береться bulkCopy, її розширення, про яке ви написали вкладку розширення. Щойно було б більше сенсу сказати ось як я використав SqlBulkCopy lib.
Seabizkit

слід використовувати conn.OpenAsync у версії async
Роберт

6

Спробуйте використовувати Збережену процедуру, яка отримає XML даних, які ви хочете вставити.


9
Передача даних як XML не потрібна, якщо ви не хочете зберігати їх як XML. У SQL 2008 можна використовувати параметр, що оцінюється в таблиці.
Ладислав Мрнка

я не уточнив цього, але мені також потрібно підтримати SQL 2005
Bongo Sharp

4

Я зробив загальне розширення прикладу @Slauma s вище;

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();
    }
}

4

Я шукаю найшвидший спосіб вставити в Entity Framework

Є кілька сторонніх бібліотек, які підтримують групову вставку:

  • Z.EntityFramework.Extensions ( рекомендується )
  • EFUtilities
  • EntityFramework.BulkInsert

Див.: Бібліотека масових вставок Framework Entity Framework

Будьте уважні, вибираючи бібліотеку масових вставок. Лише розширення Entity Framework підтримує всілякі асоціації та спадщини, і це єдине, що все ще підтримується.


Відмова : Я є власником розширень Entity Framework

Ця бібліотека дозволяє виконувати всі масові операції, необхідні для ваших сценаріїв:

  • Масові збереження змін
  • Об'ємна вставка
  • Масове видалення
  • Масове оновлення
  • Масове злиття

Приклад

// 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;
});

19
це чудове розширення, але не безкоштовне .
Окан Кочігіт

2
Ця відповідь є досить хорошою, і EntityFramework.BulkInsert виконує об'ємне вставлення 15К рядків за 1,5 секунди, працює дуже добре для внутрішнього процесу, як Windows Service.
Пастор Кортес

4
Так, 600 доларів за об'ємну вставку. Цілком варто того.
eocron

1
@eocron Yeat того варто, якщо використовувати його в комерційних цінах. Я не бачу жодної проблеми з 600 доларів за те, що мені не доведеться витрачати години на його створення, що коштуватиме мені набагато більше, ніж 600 доларів. Так, це коштує грошей, але дивлячись на мою погодинну ставку, це гроші, які добре витрачають!
Джорді ван Ейк

3

Використання 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;
}

3

Одним із найшвидших способів збереження списку потрібно застосувати наступний код

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

AutoDetectChangesEnabled = false

Додати, AddRange & SaveChanges: не виявляє змін.

ValidateOnSaveEnabled = false;

Не виявляє трекер змін

Ви повинні додати nuget

Install-Package Z.EntityFramework.Extensions

Тепер ви можете використовувати наступний код

var context = new MyContext();

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

context.BulkInsert(list);
context.BulkSaveChanges();

чи можу я використовувати Ваш зразок коду для масового оновлення?
AminGolmahalle

4
Z бібліотека не безкоштовна
SHADOW.NET

3

SqlBulkCopy дуже швидкий

Це моя реалізація:

// at some point in my calling code, I will call:
var myDataTable = CreateMyDataTable();
myDataTable.Rows.Add(Guid.NewGuid,tableHeaderId,theName,theValue); // e.g. - need this call for each row to insert

var efConnectionString = ConfigurationManager.ConnectionStrings["MyWebConfigEfConnection"].ConnectionString;
var efConnectionStringBuilder = new EntityConnectionStringBuilder(efConnectionString);
var connectionString = efConnectionStringBuilder.ProviderConnectionString;
BulkInsert(connectionString, myDataTable);

private DataTable CreateMyDataTable()
{
    var myDataTable = new DataTable { TableName = "MyTable"};
// this table has an identity column - don't need to specify that
    myDataTable.Columns.Add("MyTableRecordGuid", typeof(Guid));
    myDataTable.Columns.Add("MyTableHeaderId", typeof(int));
    myDataTable.Columns.Add("ColumnName", typeof(string));
    myDataTable.Columns.Add("ColumnValue", typeof(string));
    return myDataTable;
}

private void BulkInsert(string connectionString, DataTable dataTable)
{
    using (var connection = new SqlConnection(connectionString))
    {
        connection.Open();
        SqlTransaction transaction = null;
        try
        {
            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();
        }
        catch (Exception)
        {
            transaction?.Rollback();
            throw;
        }
    }
}

3

[Оновлення 2019] EF Core 3.1

Після сказаного вище, відключення функції AutoDetectChangesEnabled в EF Core працювало чудово: час вставки ділився на 100 (від багатьох хвилин до декількох секунд, 10 к записів із взаємозв'язками між таблицями)

Оновлений код:

  context.ChangeTracker.AutoDetectChangesEnabled = false;
            foreach (IRecord record in records) {
               //Add records to your database        
            }
            context.ChangeTracker.DetectChanges();
            context.SaveChanges();
            context.ChangeTracker.AutoDetectChangesEnabled = true; //do not forget to re-enable

2

Ось порівняння продуктивності між використанням Entity Framework та використанням класу SqlBulkCopy на реалістичному прикладі: Як масово вставити складні об'єкти в базу даних SQL Server

Як вже наголошували інші, ORM не призначені для використання в масових операціях. Вони пропонують гнучкість, розділення проблем та інші переваги, але масові операції (крім масового читання) - не одна з них.


2

Інший варіант - використовувати 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();
}

Щоб отримати додаткові приклади та розширене використання, перегляньте документацію . Відмова: Я є автором цієї бібліотеки, і будь-які погляди на мою власну думку.


2
Цей проект видалено з NuGet та GitHub.
0xced

1

За моїми відомостями є no BulkInsertв EntityFrameworkзбільшенні продуктивності величезних вставок.

В цьому випадку ви можете піти з SqlBulkCopy в , ADO.netщоб вирішити вашу проблему


Я дивився на цей клас, але, схоже, він більше орієнтований на вставки таблиці в таблицю, чи не так?
Bongo Sharp

Не впевнений, що ви маєте на увазі, це перевантаження, WriteToServerяка приймає DataTable.
Сліпий

ні ви можете вставити з .Net об'єкти в SQL також. Що ви шукаєте?
anishMarokey

Спосіб вставити в базу даних потенційно тисячі записів у блок TransactionScope
Bongo Sharp

Ви можете використовувати .Net TransactionScope technet.microsoft.com/en-us/library/bb896149.aspx
anishMarokey

1

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

У моєму випадку я вставляю 7760 регістрів, розподілених у 182 різних таблицях із зовнішніми ключовими зв’язками (за NavigationProperties).

Без завдання пройшло 2 хвилини з половиною. Протягом завдання ( Task.Factory.StartNew(...)) минуло 15 секунд.

Я займаюся лише SaveChanges()після додавання всіх сутностей до контексту. (для забезпечення цілісності даних)


2
Я майже впевнений, що контекст не є безпечним для потоків. Чи є у вас тести, щоб забезпечити збереження всіх організацій?
Danny Varod

Я знаю, що вся структура суті не є безпечною ниткою зовсім, але я просто додаю об'єкти до контексту і економлячи в кінці ... Тут ідеально працює.
Рафаель AMS

Отже, ви викликаєте DbContext.SaveChanges () в основному потоці, але додавання сутностей до контексту виконується у фоновому потоці, правда?
Prokurors

1
Так, додайте дані всередину потоків; чекати, поки всі закінчать; і Збережіть зміни в основній темі
Рафаель AMS

Хоча я вважаю, що цей спосіб небезпечний і схильний до помилок, я вважаю це дуже цікавим.
Учень

1

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

І якщо, наприклад, ваша поїздка до бази даних і назад становить 50 мс, то час, необхідний для вставки, - це кількість записів х 50 мс.

Ви повинні використовувати BulkInsert, ось посилання: https://efbulkinsert.codeplex.com/

Час вставки скоротився з 5-6 хвилин до 10-12 секунд, використовуючи його.



1

[НОВЕ РІШЕННЯ для POSTGRESQL] Привіт, я знаю, що це досить стара публікація, але я нещодавно зіткнувся з подібною проблемою, але ми використовували Postgresql. Мені хотілося використати ефективний булкінсер, що виявилося досить складно. Я не знайшов належної безкоштовної бібліотеки для цього в БД. Я знайшов лише цього помічника: 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));

Я показав приклад з транзакцією, але це також можна зробити за допомогою звичайного з'єднання, вилученого з контексту. enterprisessToAdd - це безліч звичайних записів сутностей, які я хочу об'єднати в DB.

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


0

Секрет полягає в тому, щоб вставити в ідентичну порожню таблицю інсценування. Вставки швидко освітлюються. Потім запустіть a одну вставку з цієї у вашу основну велику таблицю. Потім обрізайте стіл для постановки готового до наступної партії.

тобто.

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

Використовуючи EF, додайте всі свої записи до порожньої таблиці інсценізації. Потім використовуйте SQL для вставки в основну (велику і повільну) таблицю в одній інструкції SQL. Потім випорожніть стіл для постановки. Це дуже швидкий спосіб вставити багато даних у вже велику таблицю.
Саймон Хьюз

13
Коли ви скажете, що використовуєте EF, додайте записи в таблицю інсценізації, чи дійсно ви спробували це з EF? Оскільки EF видає окремий дзвінок до бази даних із кожним вкладишем, я підозрюю, що ви будете бачити той самий перф-хіт, якого намагається уникнути ОП. Як постановочна таблиця уникає цього питання?
Джим Вулі

-1

Але для більш ніж (+4000) вставок я рекомендую використовувати збережену процедуру. додається час, що минув. Я вставив це 11,788 рядків у 20 "введіть тут опис зображення

ось це код

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

-1

Використовуйте збережену процедуру, яка приймає вхідні дані у вигляді xml для вставки даних.

З вашого коду c # введіть дані у вигляді XML.

наприклад, у c #, синтаксис виглядає так:

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

-7

Використовуйте цю техніку для збільшення швидкості вставки записів в Entity Framework. Тут я використовую просту процедуру, що зберігається, щоб вставити записи. І щоб виконати цю збережену процедуру, я використовую метод .romerq (() Entity Framework, який виконує Raw SQL.

Збережений код процедури:

CREATE PROCEDURE TestProc
@FirstParam VARCHAR(50),
@SecondParam VARCHAR(50)

AS
  Insert into SomeTable(Name, Address) values(@FirstParam, @SecondParam) 
GO

Далі перегляньте всі ваші 4000 записів та додайте код Entity Framework, який виконує збережені

процедуру нарощуйте через кожну 100-ту петлю.

Для цього я створюю рядок запиту для виконання цієї процедури, продовжую додавати до нього всі набори записів.

Потім перевірте, чи працює цикл у кратних 100 і в цьому випадку виконайте його, використовуючи .FromSql().

Тож для 4000 записів мені потрібно виконати процедуру лише 4000/100 = 40 разів .

Перевірте наведений нижче код:

string execQuery = "";
var context = new MyContext();
for (int i = 0; i < 4000; i++)
{
    execQuery += "EXEC TestProc @FirstParam = 'First'" + i + "'', @SecondParam = 'Second'" + i + "''";

    if (i % 100 == 0)
    {
        context.Student.FromSql(execQuery);
        execQuery = "";
    }
}

Це може бути ефективно, але еквівалентно НЕ, використовуючи структуру сутності. Питання ОП полягало в тому, як досягти максимальної ефективності в контексті Entity Framework
kall2sollies
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.