簡體   English   中英

如何修復緩慢的 EF Core 7 性能

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM