LINQ IQueryable查询中的Where子句无效

5
使用Linq to Entities时,当我在使用已编写的LINQ语句创建的Linq对象上执行Where lamba表达式时,where子句不起作用。这种方法行不通,查询会被执行,但结果将不会进行过滤。
var myQuery = (from l in db.MyTable
                     select l);
myQuery.Where(r => availableStatusList.Contains(r.Status));
var myObj = myQuery.ToList();  

这是有效的。查询被执行,并且结果已经正确地过滤返回。
var myQuery = (from l in db.MyTable
                     select l).Where(r => availableStatusList.Contains(r.Status));
var myObj = myQuery.ToList();

据我所知,这两个查询应该返回相同的结果。第一个查询为什么不遵循Where子句,原因是什么?

3
你没有将它重新指定回myQuery。Where子句创建一个新对象,而不是改变现有对象。 - Ian Mercer
尝试将“where”放在相同的原始格式中(不要将标准格式与内联格式混合):from l in db.MyTable where availableStatusList.Contains(l.Status) select l;。我很好奇看看这是否会改变它。 - BlackjacketMack
3个回答

15
Where子句在调用时不会创建新对象;它通过将集合包装在最终将运行的过滤器中应用筛选器来对现有集合进行过滤。然而,由于它是一个纯函数,该过滤器的返回值需要返回到原始引用myQuery,这就是为什么第二个示例有效...您已经通过Where()子句将结果链接回myQuery。通过使用ToList()材料化延迟查询,您将过滤后的集合返回。

在第一个示例中,您假定筛选器直接应用于集合,但这不是LINQ的工作方式,因为当调用函数时,筛选器不会立即修改集合。相反,它只应用将被提供的谓词,前提是它“附加”到原始的Queryable集合并且您使用ToList()解析查询。

有时候把延迟执行看作是一系列承诺可能更容易理解。

如果我给你一美元,你就有一美元。如果我扣掉 25 美分作为税收,我已经立即解决了我们的交易(查询)。

但是,如果我承诺在星期二给你一美元,我就返回了一个Queryable<T>承诺

然而,从现在到星期二之间可能发生多种事件。我可以链式筛选器以进行税收(25 美分),口香糖(25 美分)或任何其他筛选器。

但是,我们的记账系统有一个注意事项。我们不能只对承诺的金额调用.Taxes()(我们的Where子句)并期望它更新。我们必须通过将过滤器返回给原始变量来记录承诺金额的交易,并使用已针对我们的承诺金额进行的交易进行更新。

myQuery = myQuery.Where(condition);

在周二,当你通过调用一系列链式 promises/filters 的 ToList() 方法来收取你的付款时,我会将已发生的事件从承诺的内容中扣除,最终支付50美分。这就是延迟执行的工作方式。


1
我认为“Where”子句确实返回一个新的IEnumerable,经过筛选等操作。 var list = new List<string>{"hi","bye"}; Assert.AreNotSame(list,list.Where(w=>w == "hi")); - BlackjacketMack
1
@BlackjacketMack IEnumerable不是IQuerable。为了强调,更新答案。此外,您的比较无效。您正在将列表类型与IEnumerable类型进行比较,这是无效的。尽管底层集合没有更改,但它们不是相同的类型。如果我们从引用的角度来看“对象”,则会创建一个新的引用。如果我们从基础集合的角度来看“对象”,则在查询被实现之前不会创建或返回新的集合。 - David L
我明白了...所以IQueryable会不断被修改但保留相同的引用?那么如果我添加其他子句,Assert.AreSame()就会通过?我原本认为,像IEnumerables一样,每个附加的linq子句都会返回一个新的和不同的IQueryable,有效地将前面的语句包装成装饰器。 - BlackjacketMack
@BlackjacketMack 对于IQueryable或IEnumerable,你的印象是正确的,它返回一个新包装的语句系列,当解决时将执行。但我的观点是底层集合仍然相同,只是一个语义问题。你的理解是正确的。我的抱怨在于术语。它仍然是原始集合(对象)。然而,如果你看一系列链接的where子句,你会发现对象只被包装一次,这是很有趣的。 - David L
好的,有趣。谢谢你的详细说明。 - BlackjacketMack

9

简短回答:

与字符串变异函数类似,您需要将其重新分配给变量:

string sample = "text";

// this doesn't change sample
sample.ToUpper();   

// this one does
sample = sample.ToUpper();

所需的是什么呢:

首先,您需要:

myQuery = myQuery.Where(...)

1
在你的第一个例子中。
1.    var myQuery = (from l in db.MyTable select l);
2.    myQuery.Where(r => availableStatusList.Contains(r.Status));
3.    var myObj = myQuery.ToList();

第一行返回myQuery。

第二行将Where(..)子句的结果丢弃。

第三行从myQuery开始...

在示例1中,您可能想要编写的内容是:

1.    var myQuery = (from l in db.MyTable select l);
2.    var q2 = myQuery.Where(r => availableStatusList.Contains(r.Status));
3.    var myObj = q2.ToList();

当Where子句的结果保存在q2中时,现在我的理解是q2.ToList()等同于你第二个示例中的myQuery.ToList()


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接