[英]How to fix slow EF Core 7 performance
我或多或少有這樣的代碼:
// The Parent entity
public Class Parent
{
public int Id { get; set; }
public string SomeProperty { get; set; }
public List<Child> Children { get; set; } // Navigation to Children
}
// The Child entity
public Class Parent
{
public int Id { get; set; }
public string SomeProperty { get; set; }
public int ParentId { get; set; } // FK to the Parent
public Parent Parent { get; set; }
}
// The code that does saves data to the database
public Class MyClass
{
public void DoSomething(List<Parent> newItems)
{
var iterationNumber = 0;
// newItems contains 100,000 Parent/Child objects!
// Each newItem consists of a Parent and one Child.
foreach (var newItem in newItems) {
// Commit the changes every 500 iterations
if (iterationNumber++ % 500 == 0) {
await _db.SaveChangesAsync();
}
_db.Set<Parent>().Add(newItem);
existingItems.Add(newItem);
}
await _db.SaveChangesAsync();
}
}
因此,我向數據庫中添加了 100,000 個新實體。 我每 500 次迭代提交更改。
我注意到我的循環的性能隨着它的進行而顯着下降。 我正在尋找有關如何提高此代碼性能的建議。
編輯:
我曾假設性能下降是因為 EF 正在跟蹤newItems
列表中越來越多的對象。 我嘗試在兩個_db.SaveChangesAsync()
調用之后添加_db.ChangeTracker.Clear()
,但這對性能不佳沒有明顯影響。
我認為如果可以避免的話,最好不要在循環中使用數據庫調用。
您可以使用AddRange
,但您必須為批量處理編寫自定義代碼。
context.Parent.AddRange(newItems);
//batch-wise processing
const int batchSize = 5000;
var totalCount = newItems.Count();
var batches = Math.Ceiling(totalCount / (double)batchSize);
//disable tracking
context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
for (var i = 0; i < batches; i++)
{
var batch = newItems.Skip(i * batchSize).Take(batchSize);
context.Parents.AddRange(batch);
context.SaveChanges();
}
或Microsoft.EntityFrameworkCore.BulkExtensions
。 在BulkExtensions
,您可以執行批量插入。 無需編寫自定義代碼。
context.BulkInsert(newItems, options =>
{
options.BatchSize = 5000;
// disable tracking
options.TrackingBehavior = TrackingBehavior.NoTracking;
});
BulkInsert
的默認值為 30 秒超時。 你可以在options.Timeout
中增加這個
我過去有過類似的問題,我所做的是每隔幾次迭代就為數據庫上下文創建新實例。 我使用using () {}
scope 以便它會自動處理舊的數據庫上下文實例。
我假設您正在使用依賴注入來實例化數據庫上下文,並且您的數據庫上下文 class 是DatabaseContext
。 您需要從構造函數中獲取IServiceScopeFactory
實例。 並用它來實例化數據庫上下文。 注釋中給出了解釋。 您可以像下面這樣更新您的代碼。
注意:如果您沒有使用依賴注入來檢索數據庫上下文,那么您可以簡單地使用using (var _db = new DatabaseContext()) { }
並從下面的代碼中刪除IServiceScopeFactory
和相應的using
塊。
public Class MyClass
{
// serviceScopeFactory will be needed to instantiate db context from dependency injection.
private readonly IServiceScopeFactory serviceScopeFactory;
// retrieve serviceScopeFactory from constructor
public CustomerCriteriaRepository(IServiceScopeFactory serviceScopeFactory)
{
this.serviceScopeFactory = serviceScopeFactory;
}
public void DoSomething(List<Parent> newItems)
{
// set batch count to save at single go.
var batchCount = 500;
// loop over list and save. Do not use increment condition (i++) in for.
for (var i = 0; i < newItems.Count;)
{
// create scope which can instantiate db context
using (var scope = this.serviceScopeFactory.CreateScope())
{
// instantiate db context using scope object
using (var _db = scope.ServiceProvider.GetService<DatabaseContext>())
{
// increase i with batchCount value
i += batchCount;
// skip already processed values and take next batch and add them all into _db using AddRange.
_db.Set<Parent>().AddRange(newItems.Skip(i).Take(batchCount));
// save all newly added objects
await _db.SaveChangesAsync();
}
}
}
}
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.