繁体   English   中英

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

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

我们正在研究一个性能问题,其中EF 6.1.3速度很慢,我们无法弄清楚可能导致它的原因。

数据库上下文初始化为:

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

我们已将性能问题与以下方法隔离开来:

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;
}

changedProperties列表中有两个名称,EF正确生成了一个更新语句,只更新这两个属性。

重复调用此方法(处理数据项的集合),大约需要15-20秒才能完成。

如果我们用以下方法替换上面的方法,执行时间将减少到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;
}

在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);
    }
}

EF做什么完全杀死了性能? 我们可以做些什么来解决它(没有使用另一个ORM)?

通过对代码进行计时,我能够看到EF花费的额外时间是在将对象附加到上下文的调用中,而不是在实际查询中更新数据库。

通过消除所有对象引用(在附加对象之前将它们设置为null并在更新完成后恢复它们),EF代码以“可比较的时间”(5秒,但有大量的日志代码)运行到手写解决方案。

因此看起来EF有一个“错误”(有些人可能称之为功能),导致它以递归方式检查附加对象,即使已禁用更改跟踪和验证。

更新:EF 7似乎通过允许您在调用Attach时传入GraphBehavior枚举来解决此问题。

Entity框架的问题在于,当您调用SaveChanges()时,insert语句将逐个发送到数据库,这就是Entity的工作方式。

实际上每个插入有2个db命中,第一个db命中是一个记录的insert语句,第二个是select语句来获取插入记录的id。

所以你有一个数据库旅行的numOfRecords * 2数据库旅行*时间。

在代码context.Database.Log = message => Debug.WriteLine(message);写下context.Database.Log = message => Debug.WriteLine(message); 将生成的sql记录到控制台,你会看到我在说什么。

您可以使用BulkInsert,这里是链接: https ://efbulkinsert.codeplex.com/

看起来好像你已经尝试过设置:

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

并且您没有使用有序列表,我认为您将不得不重构代码并进行一些基准测试。

我认为瓶颈来自于foreach因为上下文必须处理潜在的大量批量数据(不确定在您的情况下有多少)。

在调用SaveChanges();之前,尝试将数组中包含的项目缩小为较小的批次SaveChanges(); SaveChangesAsync(); 方法,并注意性能偏差,因为它们使上下文变得过大。

此外,如果您仍然没有看到进一步的收益,请尝试处理SaveChanges(); 然后根据实体列表的大小创建一个新的,刷新上下文可能会产生进一步的改进。

但这一切都取决于我们谈论的实体数量,并且可能只在数百和数千个记录场景中引人注意。

暂无
暂无

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

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