c# 예제 - Entity Framework에서 가장 빠른 삽입 방법





12 Answers

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

context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
c# sql entity-framework

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

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




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

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




나는 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입니다. 문제는 30 가지가 왜 최적인지 전혀 알지 못합니다. 또 어떤 논리적 인 설명도 찾을 수 없었습니다.




다른 사람들은 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 생성과 같은 것을 사용하여 수행 할 수 있습니다.




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




저는 이것이 아주 오래된 질문 인 것을 알고 있습니다. 그러나 한 사람은 EF를 사용하여 대량 삽입물을 사용하는 확장 방법을 개발했다고 말했고, 제가 검사했을 때, 라이브러리 비용은 오늘 (한 명의 개발자에게) 599 달러라는 것을 알았습니다. 어쩌면 그것은 전체 라이브러리에 대해 이해할 수 있습니다. 그러나 대량 삽입물의 경우 이것은 너무 많습니다.

여기에 제가 작성한 아주 간단한 확장 방법이 있습니다. 먼저 데이터베이스와 쌍을 이루는 코드를 사용합니다 (코드를 먼저 테스트하지는 마십시오.하지만 동일하다고 생각합니다). 컨텍스트의 이름으로 YourEntities 를 변경하십시오.

public partial class YourEntities : DbContext
{
    public async Task BulkInsertAllAsync<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;
            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);



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

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




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



배경 작업자 또는 작업을 통해 삽입하려고 시도한 적이 있습니까?

필자의 경우, 7760 개의 레지스터를 삽입하고 182 개의 다른 테이블에 외래 키 관계 (NavigationProperties에 의해)로 배포했다.

그 일이 없으면 2 분 30 초가 걸렸습니다. Task ( Task.Factory.StartNew(...)) 내에서 15 초가 걸렸습니다.

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




대량 패키지 라이브러리를 사용할 수 있습니다 . 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));
    }



Related