简体   繁体   English

如何修复缓慢的 EF Core 7 性能

[英]How to fix slow EF Core 7 performance

I have code more or less like this:我或多或少有这样的代码:

// 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();
    }
}

So, I'm adding 100,000 new entities to my database.因此,我向数据库中添加了 100,000 个新实体。 I'm committing the changes every 500 iterations.我每 500 次迭代提交更改。

I've noticed that the performance of my loop degrades significantly as it proceeds.我注意到我的循环的性能随着它的进行而显着下降。 I'm looking for suggestions for how to improve the performance of this code.我正在寻找有关如何提高此代码性能的建议。

EDIT:编辑:

I had assumed the performance degrades because EF is tracking more and more objects in the newItems list.我曾假设性能下降是因为 EF 正在跟踪newItems列表中越来越多的对象。 I tried adding _db.ChangeTracker.Clear() after both of the _db.SaveChangesAsync() calls, but that had no obvious effect on the poor performance.我尝试在两个_db.SaveChangesAsync()调用之后添加_db.ChangeTracker.Clear() ,但这对性能不佳没有明显影响。

I think it's a good practice not to use database calls in the loop if you can avoid it.我认为如果可以避免的话,最好不要在循环中使用数据库调用。

You can use AddRange but you'll have to write custom code for batch-wise processing.您可以使用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();
}

Or Microsoft.EntityFrameworkCore.BulkExtensions .Microsoft.EntityFrameworkCore.BulkExtensions In BulkExtensions you can perform the batch-wise insertion.BulkExtensions ,您可以执行批量插入。 No need to write custom code.无需编写自定义代码。

context.BulkInsert(newItems, options =>
{
        options.BatchSize = 5000;
        // disable tracking
        options.TrackingBehavior = TrackingBehavior.NoTracking;
});

BulkInsert has a default value of 30-sec timeout. BulkInsert的默认值为 30 秒超时。 you can increase this in options.Timeout你可以在options.Timeout中增加这个

I had similar issue in past and what I did was on every few iteration I was creating new instance for db context.我过去有过类似的问题,我所做的是每隔几次迭代就为数据库上下文创建新实例。 I utilized using () {} scope so that it will automatically dispose old db context instances.我使用using () {} scope 以便它会自动处理旧的数据库上下文实例。

I assume that you are using dependency injection to instantiate db context & your db context class is DatabaseContext .我假设您正在使用依赖注入来实例化数据库上下文,并且您的数据库上下文 class 是DatabaseContext You need to get IServiceScopeFactory instance from from constructor.您需要从构造函数中获取IServiceScopeFactory实例。 And use it to instantiate db context.并用它来实例化数据库上下文。 Explanation is given in comment.注释中给出了解释。 You can update your code like below.您可以像下面这样更新您的代码。

Note: If you are not using dependency injection to retrieve db context then you can simply use using (var _db = new DatabaseContext()) { } and remove IServiceScopeFactory & respective using blocks from below code.注意:如果您没有使用依赖注入来检索数据库上下文,那么您可以简单地使用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