Entity Frameworkに挿入する最速の方法を探しています。
アクティブなTransactionScopeがあり、挿入が巨大(4000以上)であるというシナリオのために、私はこれを求めています。それは潜在的に10分(トランザクションのデフォルトのタイムアウト)より長く続く可能性があり、これは不完全なトランザクションにつながります。
Entity Frameworkに挿入する最速の方法を探しています。
アクティブなTransactionScopeがあり、挿入が巨大(4000以上)であるというシナリオのために、私はこれを求めています。それは潜在的に10分(トランザクションのデフォルトのタイムアウト)より長く続く可能性があり、これは不完全なトランザクションにつながります。
回答:
あなたの質問へのコメントであなたの発言に:
"... SavingChanges(レコードごと)..."
それはあなたができる最悪のことです!SaveChanges()
各レコードを呼び出すと、一括挿入が非常に遅くなります。パフォーマンスを向上させる可能性が高いいくつかの簡単なテストを行います。
SaveChanges()
すべてのレコードの後で1回呼び出します。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
あるデータソースから別のデータソースに一括レコードを転送する最も速い方法です。これを使用して2万件のレコードをコピーしました。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をここでコメントします
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から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);
}
それで、それはどれくらい速いですか?非常に多くの要因、コンピューターのパフォーマンス、ネットワーク、オブジェクトサイズなどに依存するため、非常に難しいです。私が行ったパフォーマンステストでは、25,000のエンティティをlocalhost の標準的な方法でおよそ10秒で挿入できることを示唆しています。他の回答で述べた。EFUtilitiesでは約300msかかります。さらに興味深いのは、この方法を使用して15秒未満で約300万のエンティティを保存し、1秒あたり約20万のエンティティを平均化したことです。
関連するデータを挿入する必要がある場合、1つの問題は当然です。これは、上記の方法を使用してSQLサーバーで効率的に実行できますが、外部キーを設定できるように、親のアプリコードでIDを生成できるID生成戦略が必要です。これは、GUIDまたはHiLo id生成などを使用して実行できます。
EFBatchOperation
渡すのではDbContext
なく、toに渡すコンストラクターがあるとよいと思います。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;
} }
try catchでラップし、TrasactionScope()
必要に応じて、コードをクリーンに保つためにここに表示しない
これは非常に古い質問であることはわかっていますが、EFで一括挿入を使用する拡張メソッドを開発したとある人が言ったところ、チェックしたところ、今日のライブラリの価格は599ドル(開発者1人)であることがわかりました。多分それはライブラリ全体にとって理にかなっていますが、一括挿入だけではこれは多すぎます。
これは私が作った非常に単純な拡張メソッドです。私は最初にデータベースとペアで使用します(最初にコードでテストしないでくださいが、同じように機能すると思います)。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 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;
}
リストを保存する最も速い方法の1つは、次のコードを適用する必要があります
context.Configuration.AutoDetectChangesEnabled = false;
context.Configuration.ValidateOnSaveEnabled = false;
AutoDetectChangesEnabled = false
Add、AddRange&SaveChanges:変更を検出しません。
ValidateOnSaveEnabled = false;
変更トラッカーを検出しません
nugetを追加する必要があります
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クラスを使用した場合のパフォーマンスの比較を次に示します。SQLServer データベースに複雑なオブジェクトを一括挿入する方法
他の人がすでに強調したように、ORMは一括操作で使用することを意図していません。柔軟性、懸念事項の分離、その他の利点がありますが、一括操作(一括読み取りを除く)はその1つではありません。
別のオプションは、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
。
バックグラウンドワーカーまたはタスクを介して挿入しようとしたことがありますか?
私の場合、(NavigationPropertiesによって)外部キー関係を持つ182の異なるテーブルに分散された7760レジスタを挿入しています。
タスクがなければ、2分半かかりました。タスク内(Task.Factory.StartNew(...)
) 15秒かかりました。
SaveChanges()
すべてのエンティティをコンテキストに追加した後にのみ実行します。(データの整合性を確保するため)
一括パッケージライブラリを使用できます。一括挿入1.0.0バージョンは、エンティティフレームワーク> = 6.0.0のプロジェクトで使用されます。
詳細については、こちらをご覧ください - 一括操作のソースコード
[POSTGRESQLの新しい解決策]かなり古い投稿であることはわかっていますが、最近同様の問題が発生しましたが、Postgresqlを使用していました。効果的な一括挿入を使用したかったのですが、かなり難しいことがわかりました。この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;
}
私はそれを次のように使用しています(Undertakeという名前のエンティティがありました)。
var undertakingHelper = BulkMapper.CreateHelper<Model.Undertaking>("dbo", nameof(Model.Undertaking));
undertakingHelper.SaveAll(transaction.UnderlyingTransaction.Connection as Npgsql.NpgsqlConnection, undertakingsToAdd));
トランザクションの例を示しましたが、コンテキストから取得した通常の接続でも実行できます。undertakeingsToAddは、通常のエンティティレコードを列挙できます。これを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回のループごとに1回です。
このために、このプロシージャを実行する文字列クエリを作成し、レコードのすべてのセットに追加し続けます。
次に、ループが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 = "";
}
}