我有一个问题,建立一个相当大的
linq查询.基本上我有一种情况,我需要在循环中执行一个子查询来过滤掉从数据库返回的匹配数.示例代码在下面的循环中:
foreach (Guid parent in parentAttributes) { var subQuery = from sc in db.tSearchIndexes join a in db.tAttributes on sc.AttributeGUID equals a.GUID join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID where a.RelatedGUID == parent && userId == pc.CPSGUID select sc.CPSGUID; query = query.Where(x => subQuery.Contains(x.Id)); }
当我随后在查询变量上调用ToList()时,似乎只执行了一个子查询,而且我留下了一大堆数据.但是这种方法有效:
IList<Guid> temp = query.Select(x => x.Id).ToList(); foreach (Guid parent in parentAttributes) { var subQuery = from sc in db.tSearchIndexes join a in db.tAttributes on sc.AttributeGUID equals a.GUID join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID where a.RelatedGUID == parent && userId == pc.CPSGUID select sc.CPSGUID; temp = temp.Intersect(subQuery).ToList(); } query = query.Where(x => temp.Contains(x.Id));
不幸的是,这种方法是令人讨厌的,因为它会导致对远程数据库的多个查询,因此如果我能够使其工作,最初的方法只会导致一次命中.有任何想法吗?
解决方法
我想你是在特定情况下捕获用于过滤的lambda表达式中的循环变量.也称为修改关闭错误的访问.
尝试这个:
foreach (Guid parentLoop in parentAttributes) { var parent = parentLoop; var subQuery = from sc in db.tSearchIndexes join a in db.tAttributes on sc.AttributeGUID equals a.GUID join pc in db.tPeopleIndexes on a.GUID equals pc.AttributeGUID where a.RelatedGUID == parent && userId == pc.CPSGUID select sc.CPSGUID; query = query.Where(x => subQuery.Contains(x.Id)); }
问题是捕获闭包中的父变量(LINQ语法被转换为),这导致所有的子查询使用相同的父ID进行运行.
会发生什么是编译器生成一个类来保存委托和委托访问的局部变量.编译器为每个循环重新使用该类的相同实例;因此,一旦查询执行,所有Wheres都将执行与同一个父Guid,即最后执行.
在循环范围内声明父进程使编译器基本上使得具有正确值的变量的副本被捕获.
起初有点难以理解,所以如果这是第一次打你,我会推荐这两篇文章的背景和一个彻底的解释:
> Eric Lippert:Closing over the loop variable considered harmful和part two.
> Jon Skeet:Closures