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

Mor*_*ner 5 c# entity-framework

我们正在调查 EF 6.1.3 运行缓慢的性能问题,我们无法弄清楚可能是什么原因造成的。

数据库上下文初始化为:

Configuration.ProxyCreationEnabled = false;
Configuration.AutoDetectChangesEnabled = false;
Configuration.ValidateOnSaveEnabled = false;
Run Code Online (Sandbox Code Playgroud)

我们已将性能问题隔离为以下方法:

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;
}
Run Code Online (Sandbox Code Playgroud)

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

This method is called repeatedly (to process a collection of data items) and takes about 15-20 seconds to complete.

If we replace the method above with the following, execution time drops to 3-4 seconds:

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;
}
Run Code Online (Sandbox Code Playgroud)

The UpdatePropertiesAsync method called on the writer (a repository implementation) looks like this:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

What is EF doing that completely kills performance? And is there anything we can do to work around it (short of using another ORM)?

Mor*_*ner 5

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

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

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

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