为什么 包含缓慢?通过主键获取多个实体的最有效方法?
使用Contains
实体框架其实是很慢的。的确,它可以转换为IN
sql中的子句,并且SQL查询本身可以快速执行。但是问题和性能瓶颈在于从LINQ查询到sql的转换。将要创建的表达式树被扩展为一长串OR
串联,因为没有原生表达式表示IN
。创建sql时,OR
可以识别出许多s的表达式并将其折叠回sqlIN
子句中。
这并不意味着使用Contains
比在ids
集合中的每个元素上发出一个查询(您的第一个选择)更糟糕。它可能仍然更好- 至少对于不是太大的收藏。但是对于大型收藏来说,这确实很糟糕。我记得我曾经测试过一个Contains
包含大约12.000个元素的查询,该查询可以工作,但是花费了大约一分钟,即使sql中的查询在不到一秒钟的时间内执行了。
可能需要测试多次往返数据库的组合的性能,并且Contains
每次往返表达式中的元素数量较少。
Contains
此处显示并说明了这种方法以及与Entity Framework一起使用的局限性:
为什么Contains()运算符会如此大幅度降低Entity Framework的性能?
在这种情况下,原始sql命令可能会发挥最佳性能,这意味着您将调用@Rune答案中显示的sqldbContext.Database.sqlQuery<Image>(sqlString)
或dbContext.Images.sqlQuery(sqlString)
在哪里sqlString
。
以下是一些测量:
我在具有550000条记录和11列的表上完成了此操作(ID从1开始没有间隙),并随机选择了20000个ID:
using (var context = new MyDbContext())
{
Random rand = new Random();
var ids = new List<int>();
for (int i = 0; i < 20000; i++)
ids.Add(rand.Next(550000));
Stopwatch watch = new Stopwatch();
watch.Start();
// here are the code snippets from below
watch.Stop();
var msec = watch.ElapsedMilliseconds;
}
var result = context.Set<MyEntity>()
.Where(e => ids.Contains(e.ID))
.ToList();
结果->
var result = context.Set<MyEntity>().AsNoTracking()
.Where(e => ids.Contains(e.ID))
.ToList();
结果->
的这种微小影响AsNoTracking
非常不寻常。它表明瓶颈不是对象实现(而不是如下所示的sql)。
对于这两个测试,可以在sql Profiler中看到SQL查询很晚才到达数据库。(我没有进行精确测量,但是它晚了70秒。)显然,将LINQ查询转换为sql的代价非常高。
var values = new StringBuilder();
values.AppendFormat("{0}", ids[0]);
for (int i = 1; i < ids.Count; i++)
values.AppendFormat(", {0}", ids[i]);
var sql = string.Format(
"SELECT * FROM [MyDb].[dbo].[MyEntities] WHERE [ID] IN ({0})",
values);
var result = context.Set<MyEntity>().sqlQuery(sql).ToList();
结果->
// same as Test 3 but this time including AsNoTracking
var result = context.Set<MyEntity>().sqlQuery(sql).AsNoTracking().ToList();
结果->
这次,禁用跟踪的效果更加明显。
// same as Test 3 but this time using Database.sqlQuery
var result = context.Database.sqlQuery<MyEntity>(sql).ToList();
结果->
我的理解是context.Database.sqlQuery<MyEntity>(sql)
与相同context.Set<MyEntity>().sqlQuery(sql).AsNoTracking()
,因此在测试4和测试5之间没有预期的区别。
(由于随机id选择后可能重复,结果集的长度并不总是相同的,但始终在19600和19640个元素之间。)
即使是数据库的20000次往返也比使用Contains
以下方法更快:
var result = new List<MyEntity>();
foreach (var id in ids)
result.Add(context.Set<MyEntity>().SingleOrDefault(e => e.ID == id));
结果->
请注意,我使用SingleOrDefault
代替Find
。使用相同的代码Find
非常慢(几分钟后我取消了测试),因为内部Find
调用了DetectChanges
。禁用自动更改检测(context.Configuration.AutoDetectChangesEnabled = false
)可获得与大致相同的性能SingleOrDefault
。使用AsNoTracking
可以将时间减少一到两秒。
测试是在同一台计算机上使用数据库客户端(控制台应用程序)和数据库服务器完成的。由于有许多往返,使用“远程”数据库的最后结果可能会变得更糟。
解决方法通过主键选择多个实体的最有效方法是什么?
public IEnumerable<Models.Image> GetImagesById(IEnumerable<int> ids)
{
//return ids.Select(id => Images.Find(id)); //is this cool?
return Images.Where( im => ids.Contains(im.Id)); //is this better,worse or the same?
//is there a (better) third way?
}
我意识到我可以进行一些性能测试以进行比较,但是我想知道实际上是否有比这两种方法更好的方法,并且我想对这两个查询(如果有)之间的区别(如果有)有所启发“翻译”。
总结以上是真正的电脑专家为你收集整理的为什么。包含缓慢?通过主键获取多个实体的最有效方法?的全部内容,希望文章能够帮你解决所遇到的问题。
如果觉得真正的电脑专家网站内容还不错,欢迎将真正的电脑专家推荐给好友。