EntityFramework执行更新查询的速度非常慢

本文关键字:速度 非常 查询 执行 更新 EntityFramework | 更新日期: 2023-09-27 18:23:54

我们正在调查一个性能问题,其中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;
}

写入程序(存储库实现)上调用的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)?

EntityFramework执行更新查询的速度非常慢

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

通过消除所有对象引用(在附加对象之前将其设置为null,并在更新完成后将其恢复),EF代码在与手写解决方案"相当的时间"(5秒,但包含大量日志记录代码)内运行。

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

更新:EF 7似乎已经解决了这个问题,允许您在调用Attach时传入GraphBehavior枚举。

Entity框架的问题是,当您调用SaveChanges()时,插入语句会一个接一个地发送到数据库,这就是Entity的工作方式。

实际上,每次插入有2个数据库命中,第一个数据库命中是记录的insert语句,第二个是select语句,用于获取插入记录的id。

因此,一次数据库访问有numOfRecords * 2次数据库访问*时间。

在代码context.Database.Log = message => Debug.WriteLine(message);中写下这一点,将生成的sql记录到控制台,您就会明白我在说什么。

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

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

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

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

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

在调用SaveChanges();SaveChangesAsync();方法之前,请尝试将数组中包含的项切成更小的批,并注意与上下文过大有关的性能偏差。

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

但这一切都取决于我们谈论的实体数量,可能只有在成百上千的创纪录场景中才会引人注目。