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. 의 일반 버전 InsertAll과 UpdateAll자동으로 유사 컬렉션을 찾을 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 BulkInsert에EntityFramework 거대한 삽입의 성능을 향상 할 수 있습니다.

이 시나리오에서는 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.