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)?
通过计时代码,我可以看到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();
后的上下文,然后创建一个新的上下文,具体取决于实体列表的大小,清除上下文可能会产生进一步的改进。
但这一切都取决于我们谈论的实体数量,可能只有在成百上千的创纪录场景中才会引人注目。