programing

Entity Framework에 삽입하는 가장 빠른 방법

nasanasas 2020. 10. 2. 22:33
반응형

Entity Framework에 삽입하는 가장 빠른 방법


Entity Framework에 삽입하는 가장 빠른 방법을 찾고 있습니다.

활성 TransactionScope가 있고 삽입이 거대 (4000+) 인 시나리오 때문에 이것을 묻습니다. 잠재적으로 10 분 이상 (트랜잭션의 기본 제한 시간) 이상 지속될 수 있으며 이로 인해 불완전한 트랜잭션이 발생합니다.


질문에 대한 의견에 대한 의견 :

"... 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 개의 스칼라 속성, 탐색 속성 없음)를 DB에 삽입하는 테스트 프로그램이 있습니다. 이 코드를 사용하면 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 초

위의 첫 번째 테스트의 동작은 성능이 매우 비선형이며 시간이 지남에 따라 극도로 감소한다는 것입니다. ( "수많은 시간"은 추정치입니다. 저는이 테스트를 끝내지 않았고, 20 분 후에 50.000 개의 엔티티에서 멈췄습니다.)이 비선형 동작은 다른 모든 테스트에서 그다지 중요하지 않습니다.


이 조합은 속도를 충분히 향상시킵니다.

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

가장 빠른 방법은 내가 개발 한 대량 삽입 확장 을 사용하는 것입니다.

참고 : 이것은 무료가 아닌 상용 제품입니다.

SqlBulkCopy 및 사용자 지정 데이터 리더를 사용하여 최대 성능을 얻습니다. 결과적으로 일반 삽입 또는 AddRange를 사용하는 것보다 20 배 이상 빠릅니다.EntityFramework.BulkInsert 대 EF AddRange

사용법은 매우 간단합니다

context.BulkInsert(hugeAmountOfEntities);

이를 System.Data.SqlClient.SqlBulkCopy위해를 사용해야 합니다. 여기에 문서 가 있으며 물론 온라인 튜토리얼이 많이 있습니다.

죄송합니다. EF가 원하는 작업을 수행 할 수있는 간단한 답변을 찾고 있었다는 것을 알고 있지만 대량 작업은 실제로 ORM의 의미가 아닙니다.


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

    }
}

EF를 사용하여 대량 삽입을 수행하는 방법에 대한이 기사를 권장합니다.

Entity Framework 및 느린 대량 INSERT

그는 이러한 영역을 탐색하고 성능을 비교합니다.

  1. 기본 EF (레코드 30,000 개 추가 완료까지 57 분 소요)
  2. ADO.NET 코드로 대체 ( 동일한 30,000 개의 경우 25 )
  3. 컨텍스트 Bloat- 각 작업 단위에 대해 새 컨텍스트를 사용하여 활성 컨텍스트 그래프를 작게 유지합니다 (동일한 30,000 개의 삽입에 33 초 소요).
  4. 큰 목록-AutoDetectChangesEnabled 해제 (약 20 초로 시간 단축)
  5. 일괄 처리 (최소 16 초)
  6. DbTable.AddRange ()-(성능이 12 범위에 있음)

나는 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이 최적인지 전혀 모르고 이에 대한 논리적 설명도 찾을 수 없다는 것입니다.


다른 사람들이 말했듯이 SqlBulkCopy가 정말 좋은 삽입 성능을 원한다면 그렇게하는 방법입니다.

구현하는 것은 약간 번거롭지 만이를 지원할 수있는 라이브러리가 있습니다. 몇 가지가 있지만 이번에는 내 라이브러리를 뻔뻔하게 연결하겠습니다. https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

필요한 유일한 코드는 다음과 같습니다.

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

그래서 얼마나 빠를까요? 너무 많은 요인, 컴퓨터 성능, 네트워크, 개체 크기 등에 따라 달라지기 때문에 말하기가 매우 어렵습니다. 제가 수행 한 성능 테스트에서는 다음과 같이 EF 구성을 최적화하는 경우 localhost 에서 표준 방식 으로 약 10 초에 25k 엔터티를 삽입 할 수 있다고 제안 합니다. 다른 답변에서 언급했습니다. 약 300ms가 걸리는 EFUtilities로. 더 흥미로운 점은이 방법을 사용하여 15 초 이내에 약 300 만 개의 항목을 저장하여 초당 평균 약 200,000 개 항목을 절약했다는 것입니다.

한 가지 문제는 관련 데이터를 삽입해야하는 경우입니다. 이는 위의 방법을 사용하여 SQL 서버에서 효율적으로 수행 할 수 있지만 외래 키를 설정할 수 있도록 부모의 앱 코드에 ID를 생성 할 수있는 ID 생성 전략이 필요합니다. 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()필요한 경우 코드를 깨끗하게 유지하기 위해 여기에 표시하지 마십시오.


나는 이것이 매우 오래된 질문이라는 것을 알고 있지만 여기 한 사람이 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);

여기에 언급되지 않았으므로 여기에서 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);

삽입하려는 데이터의 XML을 가져올 저장 프로 시저사용해보십시오 .


위의 @Slauma 예제의 일반적인 확장을 만들었습니다.

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

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

용법:

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

Entity Framework에 삽입하는 가장 빠른 방법을 찾고 있습니다.

사용할 수있는 대량 삽입을 지원하는 타사 라이브러리가 있습니다.

  • Z.EntityFramework.Extensions ( 권장 )
  • EF 유틸리티
  • 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;
});

다음은 실제 예제에서 Entity Framework 사용과 SqlBulkCopy 클래스 사용 간의 성능 비교입니다. SQL Server 데이터베이스에 복잡한 개체를 대량 삽입하는 방법

다른 사람들이 이미 강조했듯이 ORM은 대량 작업에 사용되지 않습니다. 유연성, 우려 사항 분리 및 기타 이점을 제공하지만 대량 작업 (대량 읽기 제외)은 그중 하나가 아닙니다.


또 다른 옵션은 Nuget에서 제공하는 SqlBulkTools를 사용하는 것입니다. 사용하기 매우 쉽고 강력한 기능이 있습니다.

예:

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;

변경 추적기를 감지하지 못함

너겟을 추가해야합니다

Install-Package Z.EntityFramework.Extensions

이제 다음 코드를 사용할 수 있습니다.

var context = new MyContext();

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

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

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

내 지식에 따라 거기 no BulkInsertEntityFramework거대한 삽입의 성능을 향상 할 수 있습니다.

이 시나리오에서는 SqlBulkCopy 를 사용 ADO.net하여 문제를 해결할 수 있습니다.


백그라운드 작업자 또는 작업을 통해 삽입을 시도한 적이 있습니까?

제 경우에는 외래 키 관계가있는 182 개의 다른 테이블에 배포 된 7760 개의 레지스터를 삽입했습니다 (NavigationProperties에 의해).

작업 없이는 2 분 반이 걸렸습니다. 작업 ( Task.Factory.StartNew(...)) 내에서 15 초가 걸렸습니다.

나는 SaveChanges()모든 엔티티를 컨텍스트에 추가 후에 만 수행합니다 . (데이터 무결성 보장)


여기에 작성된 모든 솔루션은 도움이되지 않습니다. 왜냐하면 SaveChanges ()를 수행하면 insert 문이 하나씩 데이터베이스로 전송되므로 Entity가 작동합니다.

예를 들어 데이터베이스로의 여행이 50ms이면 삽입에 필요한 시간은 레코드 수 x 50ms입니다.

BulkInsert를 사용해야합니다. 여기 링크가 있습니다 : https://efbulkinsert.codeplex.com/

그것을 사용함으로써 삽입 시간이 5-6 분에서 10-12 초로 단축되었습니다.


대량 패키지 라이브러리를 사용할 수 있습니다 . 대량 삽입 1.0.0 버전은 Entity framework> = 6.0.0 인 프로젝트에서 사용됩니다.

자세한 설명은 여기 - 찾을 수 있습니다 Bulkoperation 소스 코드를


[POSTGRESQL을위한 새로운 솔루션] 이봐, 꽤 오래된 게시물이라는 것을 알고 있지만 최근에 비슷한 문제가 발생했지만 Postgresql을 사용하고있었습니다. 나는 효과적인 bulkinsert를 사용하고 싶었는데, 꽤 어려웠습니다. 이 DB에서 그렇게 할 적절한 무료 라이브러리를 찾지 못했습니다. 이 도우미 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));

트랜잭션에 대한 예제를 보여 주었지만 컨텍스트에서 검색된 정상적인 연결로도 수행 할 수 있습니다. undertakingsToAdd는 DB에 bulkInsert하려는 일반 엔터티 레코드를 열거 할 수 있습니다.

몇 시간 동안 조사하고 시도한 끝에 얻은이 솔루션은 훨씬 더 빠르고 마침내 사용하기 쉽고 무료입니다! 위에서 언급 한 이유뿐만 아니라 Postgresql 자체에 문제가없는 유일한 솔루션이기 때문에이 솔루션을 사용하는 것이 좋습니다. 예를 들어 SqlServer와 같은 다른 많은 솔루션이 완벽하게 작동합니다.


비밀은 동일한 빈 준비 테이블에 삽입하는 것입니다. 삽입물은 빠르게 밝아집니다. 그런 다음 하나의 삽입을 기본 대형 테이블로 실행 하십시오. 그런 다음 다음 일괄 처리를 위해 준비 테이블을 자릅니다.

즉.

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

그러나 (+4000) 개 이상의 삽입에 대해서는 저장 프로 시저를 사용하는 것이 좋습니다. 경과 시간을 첨부했습니다. 20 인치에 11.788 행을 삽입했습니다.여기에 이미지 설명 입력

그게 코드 야

 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로 삽입합니다.

예를 들어 C #에서 구문은 다음과 같습니다.

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

이 기술을 사용하여 Entity Framework에서 레코드 삽입 속도를 높일 수 있습니다. 여기에서는 간단한 저장 프로 시저를 사용하여 레코드를 삽입합니다. 그리고이 저장 프로 시저를 실행하기 위해 Raw SQL을 실행하는 Entity Framework의 .FromSql () 메서드를 사용 합니다.

저장 프로 시저 코드 :

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

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

다음으로 모든 4000 레코드를 반복 하고 저장된 항목을 실행하는 Entity Framework 코드를 추가합니다.

절차는 100 번째 루프마다 한 번입니다.

이를 위해이 프로 시저를 실행하는 문자열 쿼리를 만들고 모든 레코드 세트에 계속 추가합니다.

그런 다음 루프가 100의 배수로 실행되고 있는지 확인하고이 경우를 사용하여 실행하십시오 .FromSql().

따라서 4000 개 레코드의 경우 4000 / 100 = 40 번만 프로 시저를 실행하면됩니다 .

아래 코드를 확인하십시오.

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

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

참고 URL : https://stackoverflow.com/questions/5940225/fastest-way-of-inserting-in-entity-framework

반응형