Самый быстрый способ вставки в Entity Framework

577

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

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

  • 1
    Как ты сейчас это делаешь?
  • 0
    Создание TransactionScope, создание экземпляра DBContext, открытие соединения и выполнение оператора for-each для вставки и SavingChanges (для каждой записи), NOTE: TransactionScope и DBContext находятся в инструкциях using, и я закрываю соединение в окончании блок
Показать ещё 3 комментария
Теги:
entity-framework

28 ответов

920
Лучший ответ

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

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

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

  • Вызов SaveChanges() один раз после записей ALL.
  • Вызов 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 = 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 минут.) Это нелинейное поведение не так важно во всех других тестах.

  • 0
    Мне не было известно о снижении производительности при вызове SaveChanges после каждой вставки, я попытаюсь использовать предоставленные вами параметры, спасибо за ваш комментарий, я сообщу вам мои результаты.
  • 0
    Я оставил загрузочный тест с этим подходом, утром я сообщу вам результаты этого, но пока я заметил ОГРОМНОЕ улучшение, выполняя SaveChanges каждые 100 записей.
Показать ещё 40 комментариев
175

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

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
  • 0
    Для меня это также изменило, по крайней мере, порядок величины при вставке около 40 000 записей в таблицу.
  • 0
    Мне пришлось добавить около 150 000 объектов, и это заняло вечность. Я перепробовал много подходов, но этот самый простой и лучший! Благодарю.
Показать ещё 7 комментариев
99

Самый быстрый способ - использовать расширение объемной вставки, которое я разработал.

Он использует SqlBulkCopy и пользовательский datareader для достижения максимальной производительности. В результате это более чем в 20 раз быстрее, чем использование обычной вставки или AddRange Изображение 1026

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

context.BulkInsert(hugeAmountOfEntities);
  • 0
    Мой контекст не имеет .BulkInsert, почему это будет / Использование EF 6.1
  • 9
    Это круто. но таблица отношений не вставлена. в EF 6.1
Показать ещё 21 комментарий
71

Для этого вы должны использовать System.Data.SqlClient.SqlBulkCopy. Здесь документация, и, конечно, в Интернете есть много обучающих программ.

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

  • 1
    Я пару раз сталкивался с SqlBulkCopy при исследовании этого, но, похоже, он больше ориентирован на вставки из таблицы в таблицу, к сожалению, я ожидал не простых решений, а скорее советов по производительности, как, например, управление состоянием соединение вручную, позволяя EF сделать это за вас
  • 7
    Я использовал SqlBulkCopy для вставки больших объемов данных прямо из моего приложения. Вы в основном должны создать DataTable, заполнить ее, а затем передать , что BulkCopy. При настройке DataTable есть несколько ошибок (к сожалению, большинство из которых я забыл), но они должны работать очень хорошо
Показать ещё 3 комментария
45

Я согласен с Адамом Ракисом. 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 был самым быстрым. Чистый EF занял 15 минут, но со смесью раствора и SqlBulkCopy мне удалось получить до 1,5 минут! Это было с 2 миллионами записей! Без какой-либо оптимизации индекса БД.
  • 0
    Список проще, чем DataTable. Существует метод расширения AsDataReader() , объясненный в этом ответе: stackoverflow.com/a/36817205/1507899
Показать ещё 2 комментария
18

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

  • 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 является оптимальным.
16

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

Entity Framework и медленные объемные ВСТАВКИ

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

  • По умолчанию EF (57 минут для добавления 30 000 записей)
  • Замена кодом ADO.NET(25 секунд для тех же 30 000)
  • Context Bloat. Сохраняйте активный контекстный график небольшим с помощью нового контекста для каждой единицы работы (то же самое, 30 000 вставок занимают 33 секунды).
  • Большие списки - Отключить AutoDetectChangesEnabled (сокращает время до 20 секунд)
  • Пакетирование (до 16 секунд)
  • DbTable.AddRange() - (производительность находится в диапазоне 12)
14

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

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

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

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

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

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

  • 0
    Работает хорошо. Хотя синтаксис немного многословен. Думаю, было бы лучше, если бы EFBatchOperation имел конструктор, который вы передаете в DbContext а не передаете в каждый статический метод. Универсальные версии InsertAll и UpdateAll которые автоматически находят коллекцию, похожую на DbContext.Set<T> , также были бы хороши.
  • 0
    Просто быстрый комментарий, чтобы сказать спасибо! Этот код позволил мне сохранить 170 тыс. Записей за 1,5 секунды! Полностью уносит любой другой метод, который я пробовал, из воды.
Показать ещё 1 комментарий
13

Контекст Dispose() создает проблемы, если Add() вами объекты 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;
} }

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

  • 1
    Это замедлило вставку (AddRange) с использованием Entity Framework 6.0. Вставка 20 000 строк увеличилась с 101 до 118 секунд.
  • 1
    @ Стефен Хо: Я также пытаюсь избежать избавления от моего контекста. Я могу понять, что это медленнее, чем воссоздание контекста, но я хочу знать, если вы нашли это достаточно быстро, чем не воссоздание контекста, но с установленным значением commitCount.
Показать ещё 2 комментария
6

Я знаю, что это очень старый вопрос, но один парень сказал, что разработал метод расширения для использования массовой вставки с 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);
  • 0
    Пожалуйста, заполните ваш пример кода. где это оптом?
  • 1
    Это уже здесь: await bulkCopy.WriteToServerAsync(table);
Показать ещё 3 комментария
5

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

  • 9
    Передача данных в формате XML не требуется, если вы не хотите хранить их в формате XML. В SQL 2008 вы можете использовать табличный параметр.
  • 0
    я не уточнил это, но мне нужно также поддерживать SQL 2005
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();
    }
}
3

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

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

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

Смотрите: Entity Framework Библиотека массовой вставки

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


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

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

  • Массовое сохранение изменений
  • Массовая вставка
  • Массовое удаление
  • Массовое обновление
  • Массовое слияние

пример

// 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;
});
  • 14
    это отличное расширение, но не бесплатное .
  • 2
    Этот ответ довольно хорош, и EntityFramework.BulkInsert выполняет массовую вставку 15K строк за 1,5 секунды, довольно хорошо работает для внутреннего процесса, такого как служба Windows.
Показать ещё 2 комментария
2

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

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

AutoDetectChangesEnabled = false

Add, AddRange & SaveChanges: не обнаруживает изменений.

ValidateOnSaveEnabled = false;

Не обнаруживает изменения трекер

Вы должны добавить нюгет

Install-Package Z.EntityFramework.Extensions

Теперь вы можете использовать следующий код

var context = new MyContext();

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

context.BulkInsert(list);
context.BulkSaveChanges();
  • 0
    я могу использовать Ваш образец кода для массового обновления?
  • 1
    Да, этот пример кода используется в BulkInsert
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;
}
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.
2

Вот сравнение производительности между использованием Entity Framework и использованием класса SqlBulkCopy на реалистичном примере: Как заполнить сложные объекты в базе данных SQL Server

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

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 перечислимы для обычных записей сущностей, которые я хочу загрузить в DB.

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

1

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

Дополнительное описание можно найти здесь: Исходный код Bulkoperation

1

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

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

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

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

1

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

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

Без задачи потребовалось 2 минуты с половиной. В рамках задачи (Task.Factory.StartNew(...)) потребовалось 15 секунд.

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

  • 2
    Я уверен, что контекст не является потокобезопасным. Есть ли у вас тесты, чтобы убедиться, что все объекты были сохранены?
  • 0
    Я знаю, что вся структура сущностей не является поточно-ориентированной, но я просто добавляю объекты в контекст и сохраняю их в конце ... Это отлично работает здесь.
Показать ещё 3 комментария
1

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

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

  • 0
    Я смотрел на этот класс, но он, кажется, более ориентирован на вставки из таблицы в таблицу, не так ли?
  • 0
    Не уверен, что вы имеете в виду, у него есть перегруженный WriteToServer который принимает DataTable .
Показать ещё 3 комментария
0

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;
        }
    }
}
0

Массовая операция была бы хорошим способом привести к этому.

Есть репозиторий github, который содержит оба полезных метода: BulkInsert и BulkUpdate с использованием MySql и EF6+.

BulkUpdate/BulkInsert в основном считывает все свойства из вашей общей сущности, а затем создает массовый запрос для вас.

Ps: Это было разработано для моих нужд, и проект открыт для тех, кто хочет улучшить его или изменить его на лучшее решение, которое будет полезно сообществу.

PS2: Если это не решает проблему, попробуйте внести изменения в проект, чтобы улучшить и достичь того, чего вы хотите, по крайней мере, это хорошее начало.

Пожалуйста, посмотрите здесь

0

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

Из вашего кода С# вставьте данные в формате XML.

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

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

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

thats it code

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

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

т.

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?
  • 0
    Используя EF, добавьте все свои записи в пустую промежуточную таблицу. Затем используйте SQL для вставки в основную (большую и медленную) таблицу в одной инструкции SQL. Затем очистите свой промежуточный стол. Это очень быстрый способ вставить много данных в уже большую таблицу.
Показать ещё 1 комментарий
-2

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню