我有父子表关系。在存储库中,我这样做:
return (from p in _ctx.Parents
.Include( "Children" )
select p).AsQueryable<Parent>();
然后在过滤器中,我想通过子ID列表过滤父项:
IQueryable<Parent> qry; // from above
List<int> ids; // huge list (8500)
var filtered =
from p in qry.Where( p => p.Children.Any(c => ids.Contains(c.ChildId)) ) select s;
我的ID列表非常庞大。这会生成一个简单的SQL语句,它确实有一个很大的id列表“in(1,2,3 ...)”,但它本身不需要花费很多时间。然而,EF只需要整整一分钟来生成语句。我通过设置断点并调用:
来证明这一点((ObjectQuery<Parent>)filtered).ToTraceString();
这需要所有时间。这是我上一次linq声明中的问题吗?我不知道在(ids)中执行相当于Child.ChildId的任何其他方法。即使我的linq陈述不好,这个世界应该花多长时间?
答案 0 :(得分:4)
不幸的是,在Linq to Entities中构建查询是一个非常重要的问题,但我发现它通常可以节省时间,因为能够在实际访问数据库之前从其组件构建查询。
他们实现Contains方法的方式可能是使用一种算法,该算法假定Contains通常用于相对较小的数据集。根据我的测试,列表中每个ID所花费的时间开始在8000左右飙升。
因此,将查询分解为多个部分可能会有所帮助。将它们分组为1000或更少的组,并连接一堆Where
表达式。
var idGroups = ids.GroupBy(i => i / 1000);
var q = Parents.Include("Children").AsQueryable();
var newQ = idGroups.Aggregate(q,
(s, g) => s.Concat(
q.Where(w => w.Children.Any(wi => g.Contains(wi.ChildId)))));
这会显着加快速度,但对于您的目的而言可能不够明显,在这种情况下,您将不得不求助于存储过程。不幸的是,这个特定的用例并不适合预期的实体框架行为的“框”。如果您的ID列表可以作为来自同一实体上下文的查询开始,则实体框架可以正常工作。
答案 1 :(得分:2)
使用Lambda语法重写您的查询,它会将时间缩短3秒(或至少为我的EF项目做的时间)。
return _ctx.Parents.Include( "Children" ).AsQueryable<Parent>();
和
IQueryable<Parent> qry; // from above
List<int> ids; // huge list (8500)
var filtered = qry.Where( p => p.Children.Any(c => ids.Contains(c.ChildId)) );