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

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

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

Ответ 1

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

"... 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 минут.) Это нелинейное поведение не так важно во всех других тестах.

Ответ 2

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

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

Ответ 3

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

примечание: это коммерческий продукт, а не бесплатный

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

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

context.BulkInsert(hugeAmountOfEntities);

Ответ 4

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

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

Ответ 5

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

    }
}

Ответ 6

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

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

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

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

Ответ 7

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

Ответ 8

Как говорили другие люди, 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.

Ответ 9

Контекст 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() если вам нужно, не показывая их здесь для поддержания чистоты кода

Ответ 10

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

Ответ 11

как это никогда не упоминалось здесь, я хочу рекомендовать 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);

Ответ 12

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

Ответ 13

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

Ответ 14

Я ищу самый быстрый способ вставки в 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;
});

Ответ 15

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

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

Ответ 16

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

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

Ответ 17

Используйте 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;
}

Ответ 18

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

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

Ответ 19

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

Ответ 20

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

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

Ответ 21

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

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

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

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

Ответ 22

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

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

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

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

Ответ 23

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

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

Ответ 24

[НОВОЕ РЕШЕНИЕ ДЛЯ 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.

Ответ 25

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

т.

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

Ответ 26

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

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

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

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

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

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

Ответ 27

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

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

Ответ 28

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

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

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

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

Ответ 29

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

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

Ответ 30

Используйте эту технику, чтобы увеличить скорость вставки записей в Entity Framework. Здесь я использую простую хранимую процедуру для вставки записей. И для выполнения этой хранимой процедуры я использую метод .FromSql() 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, и в этом случае выполните его, используя .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 = "";
    }
}