c# - entity framework 예제




Entity Framework에서 가장 빠른 삽입 방법 (17)

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

대량 공급이 가능한 타사 라이브러리가 있습니다.

  • Z.EntityFramework.Extensions ( 권장 )
  • EFUtilities
  • EntityFramework.BulkInsert

참조 : Entity Framework 대량 삽입 라이브러리

대량 삽입 라이브러리를 선택할 때는주의하십시오. Entity Framework Extensions만이 모든 종류의 연결 및 상속을 지원하며 여전히 지원되는 유일한 기능입니다.

면책 조항 : 저는 Entity Framework Extensions 의 소유자입니다.

이 라이브러리를 사용하면 시나리오에 필요한 모든 대량 작업을 수행 할 수 있습니다.

  • 일괄 저장
  • 대량 삽입
  • 대량 삭제
  • 대량 업데이트
  • 대량 병합

// Easy to use
context.BulkSaveChanges();

// Easy to customize
context.BulkSaveChanges(bulk => bulk.BatchSize = 100);

// Perform Bulk Operations
context.BulkDelete(customers);
context.BulkInsert(customers);
context.BulkUpdate(customers);

// Customize Primary Key
context.BulkMerge(customers, operation => {
   operation.ColumnPrimaryKeyExpression = 
        customer => customer.Code;
});

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

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


EF를 사용하여 일괄 삽입하는 방법에 대한이 기사를 추천합니다.

Entity Framework 및 느린 대량 INSERT

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

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

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

"... 각 레코드에 대한 SavingChanges ..."

그것은 당신이 할 수있는 최악의 일입니다! 각 레코드에 대해 SaveChanges() 를 호출하면 대량 삽입이 매우 느려집니다. 성능을 향상시킬 수있는 몇 가지 간단한 테스트를 수행합니다.

  • 모든 레코드 뒤에 SaveChanges() 한 번 호출하십시오.
  • 예를 들어 100 개의 레코드 뒤에 SaveChanges() 호출하십시오.
  • 예를 들어 100 개의 레코드 뒤에 SaveChanges() 호출하고 컨텍스트를 삭제하고 새 컨텍스트를 만듭니다.
  • 변경 감지 비활성화

대량 삽입물의 경우 다음과 같은 패턴으로 작업하고 실험하고 있습니다.

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 분 안에 작동합니다.

성능을 위해 "많은"레코드 ( "100"또는 "1000"주위의 "많은" SaveChanges() 다음에 SaveChanges() 를 호출하는 것이 중요합니다. 또한 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 개의 엔티티에서 멈췄습니다.)이 비선형 행동은 다른 모든 테스트에서 그리 중요하지 않습니다.


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

    }
}

내 지식에 따라 EntityFramework 에는 거대한 인서트의 성능을 높이기위한 no BulkInsertno BulkInsert .

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


다른 사람들은 SqlBulkCopy가 당신이 정말로 좋은 삽입 성능을 원한다면 그것을 할 수 있다고 말합니다.

구현하는 데 다소 번거롭지만 라이브러리를 통해 도움을받을 수 있습니다. 거기에 몇 가지가 있지만 나는이 시간에 내 자신의 라이브러리를 뻔뻔스럽게 연결합니다 : https://github.com/MikaelEliasson/EntityFramework.Utilities#batch-insert-entities

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

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

그럼 얼마나 빠릅니까? 너무 많은 요인, 컴퓨터 성능, 네트워크, 객체 크기 등에 따라 달라지기 때문에 말하기가 매우 어렵습니다. 내가 만든 성능 테스트는 25k 엔티티가 로컬 호스트에 표준 방식 으로 약 10 초에 삽입 될 수 있다고 제안합니다. 다른 답변에서 언급했다. EFUtilities에는 약 300ms가 걸립니다. 더욱 흥미로운 점은이 방법을 사용하여 15 초 이내에 약 3 백만 개의 엔티티를 저장하고 초당 약 200,000 개의 엔티티를 평균화 한 것입니다.

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


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

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


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

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

비밀은 동일한 빈 스테이징 테이블에 삽입하는 것입니다. 삽입물이 빠르게 번지고 있습니다. 그런 다음 기본 큰 테이블에 단일 삽입을 실행하십시오. 그런 다음 스테이징 테이블을 절단하여 다음 배치를 준비하십시오.

즉.

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

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


이 조합은 속도를 충분히 높입니다.

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

이를 위해 System.Data.SqlClient.SqlBulkCopy 를 사용해야합니다. 여기에 설명서가 있습니다. 물론 많은 온라인 자습서가 있습니다.

죄송합니다. 원하는 답변을 EF에 제공하는 간단한 답을 찾고 계시 겠지만 대량 작업은 ORM의 의미가 아닙니다.


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() 싸서 필요하면 여기에 표시하지 않고 코드를 깨끗하게 유지하십시오.


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

데이터를 삽입하기 위해 xml 형식의 입력 데이터를 사용하는 저장 프로 시저를 사용합니다.

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

예를 들어 C #에서는 구문이 다음과 같이됩니다.

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

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

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


하지만, (+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));
    }




entity-framework