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


682

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

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


1
현재 어떻게하고 있습니까?
Dustin Laine

TransactionScope 만들기, DBContext 인스턴스화, 연결 열기 및 삽입 및 SavingChanges (각 레코드마다)를 수행하는 for-each 문에서 참고 : TransactionScope 및 DBContext는 명령문을 사용하고 있으며 마지막으로 연결을 닫습니다. 블록
봉고 샤프

참조를위한 또 다른 답변 : stackoverflow.com/questions/5798646/…
Ladislav Mrnka

2
SQL 데이터베이스 에 삽입하는 가장 빠른 방법 은 EF와 관련이 없습니다. AFAIK BCP 다음 TVP + 병합 / 삽입.
StingyJack

1
의견을 읽는 사람들을 위해 : 가장 적절하고 현대적인 답변 이 여기 있습니다.
Tanveer Badar

답변:


986

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

"... 저장 변경 사항 ( 각 레코드마다 ) ..."

그것은 당신이 할 수있는 최악의 일입니다! 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 개의 엔티티에서 중지되었습니다.)이 비선형 동작은 다른 모든 테스트에서 그다지 중요하지 않습니다.


89
@Bongo Sharp : DbContext에서 설정 AutoDetectChangesEnabled = false;하는 것을 잊지 마십시오 . 또한 성능이 크게 향상되었습니다. stackoverflow.com/questions/5943394/…
Slauma

6
예, 문제는 Entity Framework 4를 사용하고 있으며 AutoDetectChangesEnabled는 4.1의 일부이지만 그럼에도 불구하고 성능 테스트를 수행하고 놀라운 결과를 얻었습니다. 00:12:00에서 00:00:22 SavinChanges 각 엔티티에서 olverload를 수행했습니다 ... 당신의 answare에 너무 감사합니다! 이것은 내가 찾던 것
Bongo Sharp

10
context.Configuration.AutoDetectChangesEnabled = false에 감사합니다. 팁, 그것은 차이를 만듭니다 .
douglaz

1
@ dahacker89 : 당신은 올바른 버전의 EF> = 4.1 사용하고 DbContext, NOT을 ObjectContext?
Slauma

3
@ dahacker89 : 아마도 더 자세한 내용으로 문제에 대한 별도의 질문을 만드는 것이 좋습니다. 나는 여기서 무엇이 잘못되었는지 알아낼 수 없다.
Slauma

176

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

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

46
맹목적으로 ValidateOnSaveEnabled를 비활성화하지 마십시오. 해당 동작에 따라 달라질 수 있으며 너무 늦을 때까지 인식하지 마십시오. 그런 다음 코드의 다른 곳에서 유효성 검사를 수행하고 EF 유효성 검사를 다시 수행하는 것은 완전히 필요하지 않습니다.
Jeremy Cook

1
내 테스트에서 20.000 행을 절약하면 101 초에서 88 초로 줄어 들었습니다. 많지 않으며 그 의미는 무엇입니까?
.

27
@ JeeremyCook 나는 당신이 얻고 자하는 것이이 속성이 기본값에서 (성능 향상을 제외하고) 기본값에서 변경하는 의미를 설명한다면이 대답이 훨씬 더 좋을 것이라고 생각합니다. 동의한다.
pseudocoder

1
맥락에서 당신이있는 거 업데이트 기록을 경우 명시 적) DetectChanges를 (호출 할 필요가 있지만, 이것은 나를 위해 일한
hillstuk

2
이들은 try-finally 블록으로 비활성화 한 다음 다시 활성화 할 수 있습니다. msdn.microsoft.com/en-us/data/jj556205.aspx
yellavon

98

가장 빠른 방법은 벌크 인서트 확장 을 사용하는 것입니다.

참고 :이 제품은 무료이며 상용 제품입니다

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

사용법은 매우 간단합니다

context.BulkInsert(hugeAmountOfEntities);

10
빠르지 만 계층의 최상위 계층 만 수행합니다.
CAD bloke

66
무료가 아닙니다.
Amir Saniyan

72
광고가 더 똑똑해지고 있습니다. 이것은 유료 제품이며 프리랜서에게는 매우 비쌉니다. 경고 받다!
JulioQc 2012 년

35
1 년 지원 및 업그레이드시 600 달러? 당신 미쳤어?
Camilo Terevinto

7
더 이상 제품 소유자가 아닙니다.
maxlego

83

System.Data.SqlClient.SqlBulkCopy이것을 사용하는 것을보아야 합니다. 여기에 설명서 가 있으며 물론 온라인으로 많은 자습서가 있습니다.

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


1
나는 이것을 조사하는 동안 SqlBulkCopy에 몇 번 뛰어 들었지만 슬프게도 쉬운 솔루션을 기대하지는 않지만 예를 들어 상태를 관리하는 것과 같은 성능 팁을 기대했습니다. EF가 당신을 위해 해줄 수 있도록하는 수동 연결
Bongo Sharp

7
SqlBulkCopy를 사용하여 응용 프로그램에서 대량의 데이터를 바로 삽입했습니다. 기본적으로 DataTable을 만들어 채우고 BulkCopy 로 전달 해야 합니다. DataTable을 설정할 때 몇 가지 문제가 있습니다 (대부분 내가 잊어 버린 슬프게도). 잘 작동합니다.
Adam Rackis

2
나는 개념 증명을했고, 약속 한 것처럼 정말 빨리 작동하지만, EF를 사용하는 이유 중 하나는 관계형 데이터 삽입이 더 쉽다는 것입니다. 이 시나리오에 참여한 적이 있습니까? 감사!
Bongo Sharp

2
불행히도 개체 웹을 DBMS에 삽입하는 것은 실제로 BulkCopy가하는 일이 아닙니다. 이것이 EF와 같은 ORM의 이점입니다. 비용은 수백 개의 유사한 객체 그래프를 효율적으로 확장 할 수 없다는 것입니다.
Adam Rackis

2
SqlBulkCopy는 가공 속도가 필요하거나이 인서트를 다시 실행하려는 경우에 반드시 필요한 방법입니다. 전에 수백만 건의 레코드를 삽입했으며 매우 빠릅니다. 즉,이 인서트를 다시 실행하지 않으면 EF를 사용하는 것이 더 쉬울 수 있습니다.
Neil

49

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

    }
}

1
이 게시물에 제공된 많은 솔루션을 시도했지만 SqlBulkCopy가 훨씬 빠릅니다. 퓨어 EF는 15 분이 걸렸지 만 솔루션과 SqlBulkCopy가 혼합되어 1.5 분으로 단축되었습니다! 이건 2 백만 건의 기록이었습니다! DB 인덱스 최적화가 없습니다.
jonas

DataTable보다 목록이 더 쉽습니다. 있다 AsDataReader()확장 방법은,이 답변에 설명 : stackoverflow.com/a/36817205/1507899
상기 R1b

그러나 관계가 아닌 상위 엔티티에 대해서만
Zahid Mustafa

1
@ZahidMustafa : 예. Bulk-Analysis-and-Relation-Tracing-On-Object-Graphs가 아닌 BulkInsert를하고 있습니다. 관계를 다루려면 삽입 순서를 분석하고 결정한 다음 개별 레벨을 대량 삽입하고 일부 키를 다음과 같이 업데이트해야합니다 필요한 맞춤형 맞춤형 솔루션을 신속하게 얻을 수 있습니다. 또는 EF를 사용하여 수행 할 수는 있지만 런타임에는 느려질 수 있습니다.
quetzalcoatl 2016 년

23

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 범위에 있음)

21

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

1
나는이 제안을 두 번째로한다. 많은 홈 브루 잉 솔루션을 사용해 본 결과 내 삽입물이 50 초 이상에서 1 초로 줄었습니다. 그리고 MIT 라이센스로 통합하기가 쉽습니다.
SouthShoreAK

이것은 ef 6.x에서 제공되는 것입니다
Alok

엔티티가 10 개가 넘는 경우 AddRange를 사용하는 것보다 성능이 뛰어납니다.
Jackal

5
10,000 개의 삽입물이 9 분에서 12 초로 늘어났습니다. 더주의를 기울여야합니다!
callisto

2
수락 된 답변을 변경할 수있는 방법이 있다면 지금은 현대적인 답변이어야합니다. 그리고 EF 팀이 이것을 즉시 제공하기를 바랍니다.
Tanveer Badar

18

나는 Slauma의 답변 (생각해 주셔서 감사합니다)을 조사했으며 최적의 속도에 도달 할 때까지 배치 크기를 줄였습니다. Slauma의 결과를 보면 :

  • commitCount = 1, recreateContext = true : 10 분 이상
  • commitCount = 10, recreateContext = true : 241 초
  • commitCount = 100, recreateContext = true : 164 초
  • commitCount = 1000, recreateContext = true : 191 초

1에서 10으로, 10에서 100으로 이동할 때 속도가 증가하지만 100에서 1000으로의 삽입 속도가 다시 떨어지고 있음을 알 수 있습니다.

그래서 배치 크기를 10에서 100 사이의 값으로 줄일 때 일어나는 일에 중점을 두 었으며 결과는 다음과 같습니다 (다른 행 내용을 사용하고 있으므로 시간이 다른 값입니다).

Quantity    | Batch size    | Interval
1000    1   3
10000   1   34
100000  1   368

1000    5   1
10000   5   12
100000  5   133

1000    10  1
10000   10  11
100000  10  101

1000    20  1
10000   20  9
100000  20  92

1000    27  0
10000   27  9
100000  27  92

1000    30  0
10000   30  9
100000  30  92

1000    35  1
10000   35  9
100000  35  94

1000    50  1
10000   50  10
100000  50  106

1000    100 1
10000   100 14
100000  100 141

내 결과에 따르면 배치 크기의 실제 최적 값은 약 30입니다. 10과 100보다 작습니다. 문제는 왜 30이 최적인지 전혀 알 수 없으며 논리적 설명을 찾을 수 없었습니다.


2
Postrges와 pure SQL (EF가 아닌 SQL에 의존 함)에서 30이 최적이라는 것을 알았습니다.
Kamil Gareev

내 경험은 연결 속도와 행 크기에 따라 최적이 다르다는 것입니다. 빠른 연결과 작은 행의 경우 최적의 행은> 200 행일 수 있습니다.
jing

18

다른 사람들이 말했듯이 SqlBulkCopy는 정말로 우수한 인서트 성능을 원할 때 수행하는 방법입니다.

구현하기가 약간 번거롭지 만 도움이되는 라이브러리가 있습니다. 몇 가지가 있지만 이번에는 내 라이브러리를 뻔뻔스럽게 연결합니다 : https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

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

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

그래서 얼마나 빠릅니까? 너무 많은 요소, 컴퓨터 성능, 네트워크, 객체 크기 등에 의존하기 때문에 말하기가 어렵습니다. 내가 만든 성능 테스트는 25k 엔터티가 로컬 호스트 에서 표준 방식 으로 약 10 초에 삽입 될 수 있음을 제안 합니다. 다른 답변에서 언급했습니다. 약 300ms가 소요되는 EFUtilities 더 흥미로운 점은이 방법을 사용하여 15 초 미만으로 약 300 만 엔터티를 절약하고 초당 평균 약 2 만 엔터티를 절약했다는 것입니다.

관련 데이터를 삽입해야 할 경우 문제는 물론입니다. 위의 방법을 사용하여 SQL 서버에서 효율적으로 수행 할 수 있지만 외래 키를 설정할 수 있도록 부모의 앱 코드에서 ID를 생성 할 수있는 ID 생성 전략이 필요합니다. 이것은 GUID 또는 HiLo id 생성과 같은 것을 사용하여 수행 할 수 있습니다.


잘 작동합니다. 문법은 조금 장황하다. 모든 정적 메소드에 전달하는 대신 to에 EFBatchOperation전달하는 생성자가 있으면 더 좋을 것이라고 생각하십시오 DbContext. 의 일반 버전 InsertAllUpdateAll자동으로 유사 컬렉션을 찾을 DbContext.Set<T>너무 좋은 것입니다.
kjbartel

감사의 말만하세요! 이 코드를 사용하면 1.5 초만에 170k 레코드를 저장할 수 있습니다! 내가 물에서 시도한 다른 방법을 완전히 불어냅니다.
Tom Glenn

@Mikael 하나의 이슈는 아이디 필드를 다루고 있습니다. 신원 삽입을 아직 활성화 할 방법이 있습니까?
Joe Phillips

1
EntityFramework.BulkInsert와 달리이 라이브러리는 여전히 무료입니다. +1
Rudey

14

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

시도 캐치로 감싸고 TrasactionScope()필요한 경우 코드를 깨끗하게 유지하기 위해 여기에 표시하지 마십시오.


1
Entity Framework 6.0을 사용하여 삽입 (AddRange) 속도가 느려졌습니다. 20.000 개의 행 삽입이 약 101 초에서 118 초로 증가했습니다.
.

1
@ Stephen Ho : 나는 또한 내 컨텍스트를 폐기하지 않으려 고 노력하고 있습니다. 컨텍스트를 다시 작성하는 것보다 느리다는 것을 이해할 수 있지만 컨텍스트를 다시 작성하지 않고 commitCount가 설정된 것보다 더 빨리 발견했는지 알고 싶습니다.
학습자

@Learner : 컨텍스트를 다시 만드는 것보다 빠르다고 생각합니다. 그러나 마침내 SqlBulkCopy를 마침내 사용하도록 전환 한 cos를 기억하지 못합니다.
Stephen Ho

나는이 기술을 사용해야했다. 왜냐하면 이상한 이유로 인해 while 루프를 통해 두 번째 패스에서 발생하는 추적이 남아 있기 때문이다. . 컨텍스트에 추가하면 (두 번째 패스에서) 컨텍스트 세트 수가 하나가 아니라 6으로 이동합니다. 임의로 추가 된 다른 항목은 while 루프를 통한 첫 번째 패스에 이미 삽입되었으므로 두 번째 패스에서 SaveChanges 호출이 실패합니다 (명백한 이유로).
Hallmanac

9

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

예제 코드를 작성하십시오. bulkCopy
Seabizkit

1
그것은 이미 여기있다 :await bulkCopy.WriteToServerAsync(table);
Guilherme

어쩌면 나는 명확하지 않았으며, 글을 쓸 때 확장을 제안하십시오 ... 실제로 두 방법 모두 SqlBulkCopy lib를 사용할 때 제 3 부분 lib가 필요하지 않다는 것을 의미했습니다. 이것은 전적으로 확장 라이브러리를 작성한 확장 라이브러리 인 bulkCopy의 출처를 묻는 이유 인 SqlBulkCopy에 전적으로 의존합니다. SqlBulkCopy lib를 어떻게 사용했는지 여기에서 말하는 것이 더 합리적입니다.
Seabizkit

비동기 버전에서 conn.OpenAsync를 사용해야 함
Robert

6

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


9
데이터를 XML로 저장하지 않으려면 데이터를 XML로 전달하지 않아도됩니다. SQL 2008에서는 테이블 값 매개 변수를 사용할 수 있습니다.
Ladislav Mrnka

나는 이것을 명확히하지 않았지만 SQL 2005를 지원해야합니다.
Bongo Sharp

4

위의 @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();
    }
}

4

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

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

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

19
이것은 훌륭한 확장이지만 무료는 아닙니다 .
Okan Kocyigit

2
이 답변은 꽤 좋으며 EntityFramework.BulkInsert 는 1.5 초 만에 15K 행을 대량으로 삽입하고 Windows 서비스와 같은 내부 프로세스에 매우 적합합니다.
목사 코르테스

4
예, 대량 인서트는 600 $입니다. 그만한 가치가 있습니다.
eocron

1
@eocron Yeat 상업적으로 사용한다면 가치가 있습니다. 나는 600 달러 이상의 비용이들 것이므로 직접 빌드하는 데 몇 시간을 소비하지 않아도되는 600 달러의 문제는 보이지 않습니다. 그렇습니다. 돈이 들지만 시간당 요금을 보면 돈을 잘 쓸 수 있습니다!
Jordy van Eijk

3

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

3

목록을 저장하는 가장 빠른 방법 중 하나는 다음 코드를 적용해야합니다.

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

대량 업데이트 용 샘플 코드를 사용할 수 있습니까?
AminGolmahalle

4
Z 라이브러리는 무료로하지 않습니다
SHADOW.NET

3

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

3

[2019 업데이트] EF Core 3.1

위에서 말한대로 EF Core에서 AutoDetectChangesEnabled를 비활성화하면 완벽하게 작동했습니다. 삽입 시간을 100으로 나눕니다 (수 분에서 몇 초까지, 크로스 테이블 관계가있는 10k 레코드)

업데이트 된 코드는 다음과 같습니다.

  context.ChangeTracker.AutoDetectChangesEnabled = false;
            foreach (IRecord record in records) {
               //Add records to your database        
            }
            context.ChangeTracker.DetectChanges();
            context.SaveChanges();
            context.ChangeTracker.AutoDetectChangesEnabled = true; //do not forget to re-enable


2

또 다른 옵션은 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();
}

더 많은 예제와 고급 사용법 은 설명서 를 참조하십시오 . 면책 조항 : 나는이 도서관의 저자이며 모든 견해는 본인의 의견입니다.


2
이 프로젝트는 NuGet 및 GitHub에서 삭제되었습니다.
0xced

1

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

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


나는 그 클래스를 살펴 보았지만 테이블에서 테이블로 삽입하는 데 더 집중되어있는 것 같지 않습니까?
봉고 샤프

무슨 뜻인지 확실하지 않으면 과부하 WriteToServer가 걸리고 있습니다 DataTable.
실명

아니요 .Net 객체에서 SQL로 삽입 할 수도 있습니다.
anishMarokey

TransactionScope 블록 내에서 데이터베이스에 잠재적으로 수천 개의 레코드를 삽입하는 방법
Bongo Sharp

당신이 사용할 수있는 닷넷 TransactionScope에 technet.microsoft.com/en-us/library/bb896149.aspx
anishMarokey

1

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

내 경우에는 내비게이션 키 관계가있는 182 개의 서로 다른 테이블에 배포 된 7760 레지스터를 삽입합니다 (NavigationProperties에 의해).

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

SaveChanges()모든 엔터티를 컨텍스트에 추가 한 후에 만 수행합니다 . (데이터 무결성을 보장하기 위해)


2
컨텍스트가 스레드 안전하지 않다고 확신합니다. 모든 엔티티가 저장되었는지 확인하기위한 테스트가 있습니까?
Danny Varod 2016 년

나는 전체 엔터티 프레임 워크가 스레드로부터 안전하지 않다는 것을 알고 있지만 컨텍스트에 객체를 추가하고 결국 저장하는 중입니다 ... 완벽하게 작동합니다.
Rafael AMS

따라서 메인 스레드에서 DbContext.SaveChanges ()를 호출하고 있지만 컨텍스트에 엔티티를 추가하는 것은 백그라운드 스레드에서 수행됩니다.
Prokurors

1
예, 스레드 내부에 데이터를 추가하십시오. 모두가 끝날 때까지 기다리십시오. 메인 스레드에서 변경 사항 저장
Rafael AMS

비록이 방법이 위험하고 실수를하기 쉽다고 생각하지만, 그것은 매우 흥미 롭습니다.
학습자

1

SaveChanges ()를 수행 할 때 insert 문이 하나씩 데이터베이스에 전송되므로 엔티티가 작동하는 방식이므로 여기에 작성된 모든 솔루션이 도움이되지 않습니다.

그리고 예를 들어 데이터베이스와 백으로의 여행이 50ms 인 경우 삽입에 필요한 시간은 레코드 수 x 50ms입니다.

https://efbulkinsert.codeplex.com/ 링크는 다음과 같습니다. BulkInsert를 사용해야합니다.

삽입 시간을 5-6 분에서 10-12 초로 줄였습니다.



1

[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에서 완벽하게 작동합니다.


0

비밀은 동일한 빈 준비 테이블에 삽입하는 것입니다. 인서트가 빠르게 밝아집니다. 그런 다음 단일 삽입을 기본 대형 테이블에 실행하십시오. 그런 다음 준비 테이블을 다음 배치에 맞게 잘라냅니다.

즉.

insert into some_staging_table using Entity Framework.

-- Single insert into main table (this could be a tiny stored proc call)
insert into some_main_already_large_table (columns...)
   select (columns...) from some_staging_table
truncate table some_staging_table

EF를 사용하여 모든 레코드를 빈 준비 테이블에 추가하십시오. 그런 다음 SQL을 사용하여 단일 SQL 명령어 로 기본 (대형 및 저속) 테이블에 삽입하십시오 . 그런 다음 준비 테이블을 비 웁니다. 이미 큰 테이블에 많은 데이터를 삽입하는 매우 빠른 방법입니다.
Simon Hughes

13
EF를 사용한다고 말하면 준비 테이블에 레코드를 추가하십시오. 실제로 EF로 시도 했습니까? EF는 각 삽입마다 데이터베이스에 대해 별도의 호출을 발행하기 때문에 OP가 피하려고하는 것과 동일한 성능 저하를 볼 것으로 생각됩니다. 준비 테이블은이 문제를 어떻게 방지합니까?
Jim Wooley

-1

그러나 (+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));
    }

-1

xml 형식의 입력 데이터를 사용하는 데이터를 삽입하는 저장 프로 시저를 사용하십시오.

C # 코드에서 삽입 데이터를 xml로 전달하십시오.

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

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

-7

이 기술을 사용하여 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 = "";
    }
}

이것은 효율적이지만 엔티티 프레임 워크를 사용하지 않는 것과 같습니다. 영업 질문은 엔티티 프레임 워크의 맥락에서 효율성을 극대화하는 방법이었다
kall2sollies
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.