Entity Framework에 삽입하는 가장 빠른 방법을 찾고 있습니다.
나는 당신이 활성화 된 TransactionScope를 가지고 있고 삽입이 거대한 시나리오 (4000+) 때문에 이것을 묻습니다. 잠재적으로 10 분 이상 지속될 수 있으며 (기본 트랜잭션 시간 초과) 트랜잭션이 불완전하게됩니다.
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 엔터티에 대한 몇 가지 측정 값은 다음과 같습니다.
위의 첫 번째 테스트의 동작은 성능이 매우 비선형이며 시간이 지남에 따라 크게 감소한다는 것입니다. ( "많은 시간"은 추정입니다.이 테스트를 완료 한 적이 없으며 20 분 후에 50.000 개의 엔티티에서 중지되었습니다.)이 비선형 동작은 다른 모든 테스트에서 그다지 중요하지 않습니다.
AutoDetectChangesEnabled = false;
하는 것을 잊지 마십시오 . 또한 성능이 크게 향상되었습니다. stackoverflow.com/questions/5943394/…
DbContext
, NOT을 ObjectContext
?
이 조합은 속도를 충분히 향상시킵니다.
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
가장 빠른 방법은 벌크 인서트 확장 을 사용하는 것입니다.
참고 :이 제품은 무료이며 상용 제품입니다
SqlBulkCopy 및 사용자 정의 데이터 리더를 사용하여 최대 성능을 얻습니다. 결과적으로 일반 인서트 또는 AddRange를 사용하는 것보다 20 배 이상 빠릅니다.
사용법은 매우 간단합니다
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();
}
}
}
AsDataReader()
확장 방법은,이 답변에 설명 : stackoverflow.com/a/36817205/1507899
EF를 사용하여 대량 삽입을 수행하는 방법에 대해이 기사를 추천합니다.
그는 이러한 영역을 탐구하고 성능을 비교합니다.
여기에 언급되지 않았으므로 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);
나는 Slauma의 답변 (생각해 주셔서 감사합니다)을 조사했으며 최적의 속도에 도달 할 때까지 배치 크기를 줄였습니다. Slauma의 결과를 보면 :
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이 최적인지 전혀 알 수 없으며 논리적 설명을 찾을 수 없었습니다.
다른 사람들이 말했듯이 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 생성과 같은 것을 사용하여 수행 할 수 있습니다.
EFBatchOperation
전달하는 생성자가 있으면 더 좋을 것이라고 생각하십시오 DbContext
. 의 일반 버전 InsertAll
과 UpdateAll
자동으로 유사 컬렉션을 찾을 DbContext.Set<T>
너무 좋은 것입니다.
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()
필요한 경우 코드를 깨끗하게 유지하기 위해 여기에 표시하지 마십시오.
나는 이것이 매우 오래된 질문이라는 것을 알고 있지만 여기서 한 사람은 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);
await bulkCopy.WriteToServerAsync(table);
삽입하려는 데이터의 XML을 가져 오는 저장 프로 시저 를 사용해보십시오 .
위의 @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();
}
}
Entity Framework에 삽입하는 가장 빠른 방법을 찾고 있습니다.
대량 삽입을 지원하는 타사 라이브러리가 있습니다.
참조 : 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;
});
사용 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;
}
목록을 저장하는 가장 빠른 방법 중 하나는 다음 코드를 적용해야합니다.
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();
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;
}
}
}
[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
실제 예제에서 Entity Framework 사용과 SqlBulkCopy 클래스 사용 간의 성능 비교는 다음과 같습니다. 복잡한 개체를 SQL Server 데이터베이스에 대량 삽입하는 방법
다른 사람들이 이미 강조했듯이 ORM은 대량 작업에 사용되지 않습니다. 유연성, 우려 사항 분리 및 기타 이점을 제공하지만 대량 작업 (대량 판독 제외)은 그 중 하나가 아닙니다.
또 다른 옵션은 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();
}
더 많은 예제와 고급 사용법 은 설명서 를 참조하십시오 . 면책 조항 : 나는이 도서관의 저자이며 모든 견해는 본인의 의견입니다.
내 지식에 따라 거기 no BulkInsert
에EntityFramework
거대한 삽입의 성능을 향상 할 수 있습니다.
이 시나리오에서는 SqlBulkCopy 를 사용 ADO.net
하여 문제를 해결할 수 있습니다.
WriteToServer
가 걸리고 있습니다 DataTable
.
백그라운드 작업자 또는 작업을 통해 삽입하려고 시도한 적이 있습니까?
내 경우에는 내비게이션 키 관계가있는 182 개의 서로 다른 테이블에 배포 된 7760 레지스터를 삽입합니다 (NavigationProperties에 의해).
작업이 없으면 2 분 반이 걸렸습니다. 작업 ( Task.Factory.StartNew(...)
) 내에서 15 초가 걸렸습니다.
SaveChanges()
모든 엔터티를 컨텍스트에 추가 한 후에 만 수행합니다 . (데이터 무결성을 보장하기 위해)
SaveChanges ()를 수행 할 때 insert 문이 하나씩 데이터베이스에 전송되므로 엔티티가 작동하는 방식이므로 여기에 작성된 모든 솔루션이 도움이되지 않습니다.
그리고 예를 들어 데이터베이스와 백으로의 여행이 50ms 인 경우 삽입에 필요한 시간은 레코드 수 x 50ms입니다.
https://efbulkinsert.codeplex.com/ 링크는 다음과 같습니다. BulkInsert를 사용해야합니다.
삽입 시간을 5-6 분에서 10-12 초로 줄였습니다.
벌크 패키지를 사용할 수 있습니다 라이브러리를 . 대량 삽입 1.0.0 버전은 엔티티 프레임 워크> = 6.0.0 인 프로젝트에서 사용됩니다.
자세한 설명은 여기를 참조하십시오 .-대량 작업 소스 코드
[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에서 완벽하게 작동합니다.
비밀은 동일한 빈 준비 테이블에 삽입하는 것입니다. 인서트가 빠르게 밝아집니다. 그런 다음 단일 삽입을 기본 대형 테이블에 실행하십시오. 그런 다음 준비 테이블을 다음 배치에 맞게 잘라냅니다.
즉.
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
그러나 (+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에서 레코드를 삽입하는 속도를 높입니다. 여기에서는 간단한 저장 프로 시저를 사용하여 레코드를 삽입합니다. 그리고이 저장 프로 시저 를 실행하려면 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 = "";
}
}