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

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

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

25 ответов


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

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


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

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

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

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

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

context.BulkInsert(hugeAmountOfEntities);

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

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


Я согласен с Адамом Rackis. 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();
        }

    }
}

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

  • commitCount = 1, recreateContext = true: более 10 минут
  • commitCount = 10, recreateContext = true: 241 сек
  • commitCount = 100, recreateContext = true: 164 sec
  • 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 оптимален, и не мог найти логического объяснения этому.


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

Entity Framework и медленные массовые вставки

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

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

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

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

единственный код, который вам потребуется-это:

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

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

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


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

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


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


Я сделал общее расширение примера @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();
    }
}

Я знаю, что это очень старый вопрос, но один парень здесь сказал, что разработал метод расширения для использования bulk insert с 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);

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

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

  • З. И EntityFramework.Расширения (рекомендовано)
  • EFUtilities
  • EntityFramework.BulkInsert

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

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


отказ от ответственности: я владелец Расширения Entity Framework

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

  • Массовая SaveChanges
  • Массовая Вставка
  • Удалить
  • Навальный Update
  • Массового Слияния

пример

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

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

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


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

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


другой вариант-использовать 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();
}

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


использовать 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;
}

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

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

AutoDetectChangesEnabled = false

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

ValidateOnSaveEnabled = false;

не обнаруживает изменение tracker

вы должны добавить nuget

Install-Package Z.EntityFramework.Extensions

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

var context = new MyContext();

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

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

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

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

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

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

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

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


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

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

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

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


вы можете использовать Навальный пакет библиотека. Массовая вставка 1.0.0 версия используется в проектах, имеющих Entity framework >=6.0.0 .

больше описания можно найти здесь- Bulkoperation исходный код


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

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

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

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

Я показал пример с транзакцией, но это также можно сделать с обычным соединением, полученным из контекста. undertakingsToAdd перечисляет обычные записи сущностей, которые я хочу bulkInsert в DB.

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


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

вот этот код

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

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

из кода c# передайте данные вставки как xml.

e.G в c# синтаксис будет следующим:

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