简体   繁体   English

EntityFramework在执行更新查询时非常缓慢

[英]EntityFramework is painfully slow at executing an update query

We're investigating a performance issue where EF 6.1.3 is being painfully slow, and we cannot figure out what might be causing it. 我们正在研究一个性能问题,其中EF 6.1.3速度很慢,我们无法弄清楚可能导致它的原因。

The database context is initialized with: 数据库上下文初始化为:

Configuration.ProxyCreationEnabled = false;
Configuration.AutoDetectChangesEnabled = false;
Configuration.ValidateOnSaveEnabled = false;

We have isolated the performance issue to the following method: 我们已将性能问题与以下方法隔离开来:

protected virtual async Task<long> UpdateEntityInStoreAsync(T entity,
                                                            string[] changedProperties)
{
    using (var session = sessionFactory.CreateReadWriteSession(false, false))
    {
        var writer = session.Writer<T>();
        writer.Attach(entity);
        await writer.UpdatePropertyAsync(entity, changedProperties.ToArray()).ConfigureAwait(false);
    }
    return entity.Id;
}

There are two names in the changedProperties list, and EF correctly generated an update statement that updates just these two properties. changedProperties列表中有两个名称,EF正确生成了一个更新语句,只更新这两个属性。

This method is called repeatedly (to process a collection of data items) and takes about 15-20 seconds to complete. 重复调用此方法(处理数据项的集合),大约需要15-20秒才能完成。

If we replace the method above with the following, execution time drops to 3-4 seconds: 如果我们用以下方法替换上面的方法,执行时间将减少到3-4秒:

protected virtual async Task<long> UpdateEntityInStoreAsync(T entity,
                                                            string[] changedProperties)
{
    var sql = $"update {entity.TypeName()}s set";
    var separator = false;
    foreach (var property in changedProperties)
    {
         sql += (separator ? ", " : " ") + property + " = @" + property;
         separator = true;
    }
    sql += " where id = @Id";
    var parameters = (from parameter in changedProperties.Concat(new[] { "Id" })
                      let property = entity.GetProperty(parameter)
                      select ContextManager.CreateSqlParameter(parameter, property.GetValue(entity))).ToArray();
    using (var session = sessionFactory.CreateReadWriteSession(false, false))
    {
        await session.UnderlyingDatabase.ExecuteSqlCommandAsync(sql, parameters).ConfigureAwait(false);
    }
    return entity.Id;
}

The UpdatePropertiesAsync method called on the writer (a repository implementation) looks like this: 在writer(存储库实现)上调用的UpdatePropertiesAsync方法如下所示:

public virtual async Task UpdatePropertyAsync(T entity, string[] changedPropertyNames, bool save = true)
{
    if (changedPropertyNames == null || changedPropertyNames.Length == 0)
    {
        return;
    }

    Array.ForEach(changedPropertyNames, name => context.Entry(entity).Property(name).IsModified = true);
    if (save)
        await context.SaveChangesAsync().ConfigureAwait(false);
    }
}

What is EF doing that completely kills performance? EF做什么完全杀死了性能? And is there anything we can do to work around it (short of using another ORM)? 我们可以做些什么来解决它(没有使用另一个ORM)?

By timing the code I was able to see that the additional time spent by EF was in the call to Attach the object to the context, and not in the actual query to update the database. 通过对代码进行计时,我能够看到EF花费的额外时间是在将对象附加到上下文的调用中,而不是在实际查询中更新数据库。

By eliminating all object references (setting them to null before attaching the object and restoring them after the update is complete) the EF code runs in "comparable times" (5 seconds, but with lots of logging code) to the hand-written solution. 通过消除所有对象引用(在附加对象之前将它们设置为null并在更新完成后恢复它们),EF代码以“可比较的时间”(5秒,但有大量的日志代码)运行到手写解决方案。

So it looks like EF has a "bug" (some might call it a feature) causing it to inspect the attached object recursively even though change tracking and validation have been disabled. 因此看起来EF有一个“错误”(有些人可能称之为功能),导致它以递归方式检查附加对象,即使已禁用更改跟踪和验证。

Update: EF 7 appears to have addressed this issue by allowing you to pass in a GraphBehavior enum when calling Attach. 更新:EF 7似乎通过允许您在调用Attach时传入GraphBehavior枚举来解决此问题。

The problem with Entity framework is that when you call SaveChanges(), insert statements are sent to database one by one, that's how Entity works. Entity框架的问题在于,当您调用SaveChanges()时,insert语句将逐个发送到数据库,这就是Entity的工作方式。

And actually there are 2 db hits per insert, first db hit is insert statement for a record, and the second one is select statement to get the id of inserted record. 实际上每个插入有2个db命中,第一个db命中是一个记录的insert语句,第二个是select语句来获取插入记录的id。

So you have numOfRecords * 2 database trips * time for one database trip. 所以你有一个数据库旅行的numOfRecords * 2数据库旅行*时间。

Write this in your code context.Database.Log = message => Debug.WriteLine(message); 在代码context.Database.Log = message => Debug.WriteLine(message);写下context.Database.Log = message => Debug.WriteLine(message); to log generated sql to console, and you will see what am I talking about. 将生成的sql记录到控制台,你会看到我在说什么。

You can use BulkInsert, here is the link: https://efbulkinsert.codeplex.com/ 您可以使用BulkInsert,这里是链接: https ://efbulkinsert.codeplex.com/

Seeing as though you already have tried setting: 看起来好像你已经尝试过设置:

Configuration.AutoDetectChangesEnabled = false;
Configuration.ValidateOnSaveEnabled = false;

And you are not using an ordered lists, I think you are going to have to refactor your code and do some benchmarking. 并且您没有使用有序列表,我认为您将不得不重构代码并进行一些基准测试。

I believe the bottleneck is coming from the foreach as the context is having to deal with a potentially large amounts of bulk data (not sure how many this is in your case). 我认为瓶颈来自于foreach因为上下文必须处理潜在的大量批量数据(不确定在您的情况下有多少)。

Try and cut the items contained in your array down into smaller batches before calling the SaveChanges(); 在调用SaveChanges();之前,尝试将数组中包含的项目缩小为较小的批次SaveChanges(); or SaveChangesAsync(); SaveChangesAsync(); methods, and note the performance deviations as apposed to letting the context grow too large. 方法,并注意性能偏差,因为它们使上下文变得过大。

Also, if you are still not seeing further gains, try disposing of the context post SaveChanges(); 此外,如果您仍然没有看到进一步的收益,请尝试处理SaveChanges(); and then creating a new one, depending on the size of your entities list, flushing out the context may yield even further improvements. 然后根据实体列表的大小创建一个新的,刷新上下文可能会产生进一步的改进。

But this all depends on how many entities we are talking about and may only be noticeable in the hundreds and thousands of record scenarios. 但这一切都取决于我们谈论的实体数量,并且可能只在数百和数千个记录场景中引人注意。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM