[英]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.