[C#] Entity Framework에 삽입하는 가장 빠른 방법

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

나는 당신이 활성화 된 TransactionScope를 가지고 있고 삽입이 거대한 시나리오 (4000+) 때문에 이것을 묻습니다. 잠재적으로 10 분 이상 지속될 수 있으며 (기본 트랜잭션 시간 초과) 트랜잭션이 불완전하게됩니다.



답변

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

“… 저장 변경 사항 ( 각 레코드마다 ) …”

그것은 당신이 할 수있는 최악의 일입니다! 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. 삽입 단계를 느리게하는 것은 맥락에서 첨부 된 엔티티의 크기가 커지고 있습니다. 따라서 얼마 후에 정리하면 도움이됩니다.

내 560000 엔터티에 대한 몇 가지 측정 값은 다음과 같습니다.

  • commitCount = 1, recreateContext = false : 여러 시간 (현재 절차 임)
  • commitCount = 100, recreateContext = false : 20 분 이상
  • commitCount = 1000, recreateContext = false : 242 초
  • commitCount = 10000, recreateContext = false : 202 초
  • commitCount = 100000, recreateContext = false : 199 초
  • commitCount = 1000000, recreateContext = false : 메모리 부족 예외
  • commitCount = 1, recreateContext = true : 10 분 이상
  • commitCount = 10, recreateContext = true : 241 초
  • commitCount = 100, recreateContext = true : 164 초
  • commitCount = 1000, recreateContext = true : 191 초

위의 첫 번째 테스트의 동작은 성능이 매우 비선형이며 시간이 지남에 따라 크게 감소한다는 것입니다. ( “많은 시간”은 추정입니다.이 테스트를 완료 한 적이 없으며 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를 사용하여 대량 삽입을 수행하는 방법에 대해이 기사를 추천합니다.

엔터티 프레임 워크 및 느린 대량 삽입

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

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

답변

여기에 언급되지 않았으므로 EFCore를 추천하고 싶습니다 .BulkExtensions here

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