具有OR子句的lambda表达式的LINQ where子句和返回不完整结果的空值
问题简而言之
我们在Where子句中使用了lambda表达式,它没有返回“预期”结果。
快速摘要
在analyzeObjectRepository对象中,某些对象也包含名为Parent的属性中的父关系。 我们正在查询此analyzeObjectRepository以返回一些对象。
详情
下面的代码应该做的是,返回包含ID值的特定对象的根,第一个子节点(直接子节点)和孙子节点。
在下面的代码中,常识说所有使3个单独的OR条件中的任何一个成为真的结果都应该像结果一样返回。
List analysisObjects = analysisObjectRepository .FindAll() .Where(x => x.ID == packageId || x.Parent.ID == packageId || x.Parent.Parent.ID == packageId) .ToList();
但上面的代码只返回子孙,而没有返回根对象(使用null父值)
x.ID == packageId
条件是真的。
只有制作第二个的对象
x.Parent.ID == packageId
第三
x.Parent.Parent.ID == packageId
条款被退回。
如果我们只编写代码以使用下面的代码返回根对象,则返回它,因此我们完全确定analyzeObjectRepository包含所有对象
List analysisObjects = analysisObjectRepository .FindAll() .Where(x => x.ID == packageId ) .ToList();
但是,当我们将其重写为委托时,我们得到预期的结果,返回所有预期的对象。
List analysisObjects = analysisObjectRepository .FindAll() .Where(delegate(AnalysisObject x) { return (x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) || (x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId); }) .ToList();
题
我们是否遗漏了lambda表达式中的某些内容? 这是一个非常简单的3部分OR条件,我们认为应该返回任何使三个条件中的任何一个成为真的对象。 我们怀疑具有null Parent值的根对象可能会导致问题,但无法准确判断出来。
任何帮助都会很棒。
你的第二个代表不是重写匿名委托(而不是lambda)格式的第一个委托。 看看你的情况。
第一:
x.ID == packageId || x.Parent.ID == packageId || x.Parent.Parent.ID == packageId
第二:
(x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) || (x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)
对lambda的调用将为ID不匹配的任何x
抛出exception,并且父级为null或不匹配且祖父级为null。 将空检查复制到lambda中,它应该可以正常工作。
评论到问题后编辑
如果您的原始对象不是List
,那么我们无法知道FindAll()
的返回类型是什么,以及它是否实现了IQueryable
接口。 如果确实如此,则可能解释了这种差异。 因为lambdas可以在编译时转换为Expression
但是匿名委托不能 ,所以在使用lambda版本时可能会使用IQueryable
,而在使用匿名委托版本时则可能使用LINQ-to-Objects。
这也可以解释为什么你的lambda没有导致NullReferenceException
。 如果你要将lambda表达式传递给实现IEnumerable
而不是 IQueryable
,那么lambda的运行时评估(与其他方法没有区别,匿名与否)将在第一次遇到时抛出NullReferenceException
ID
不等于目标且父或祖父母为空的对象。
已添加2011/3/16美国东部时间上午8:29
请考虑以下简单示例:
IQueryable source = ...; // some object that implements IQueryable var anonymousMethod = source.Where(delegate(MyObject o) { return o.Name == "Adam"; }); var expressionLambda = source.Where(o => o.Name == "Adam");
这两种方法产生完全不同的结果。
第一个查询是简单版本。 匿名方法导致委托,然后传递给IEnumerable
扩展方法,其中将针对您的委托检查source
的全部内容(使用普通编译代码手动在内存中)。 换句话说,如果你熟悉C#中的迭代器块,它就像这样:
public IEnumerable MyWhere(IEnumerable dataSource, Func predicate) { foreach(MyObject item in dataSource) { if(predicate(item)) yield return item; } }
这里的重点是,您实际上是在客户端的内存中执行过滤。 例如,如果您的源是某个SQL ORM,则查询中不会有WHERE
子句; 整个结果集将被带回客户端并在那里进行过滤。
使用lambda表达式的第二个查询将转换为Expression
并使用IQueryable
扩展方法。 这会导致一个对象也被键入为IQueryable
。 所有这些都适用于将表达式传递给基础提供程序。 这就是为什么你没有得到NullReferenceException
。 这完全取决于查询提供程序如何将表达式(它不是一个它可以调用的实际编译函数,是使用对象的表达式逻辑的表示)转换成它可以使用的东西。
查看区别(或者至少存在区别)的一种简单方法是在调用lambda版本中的Where
之前调用AsEnumerable()
。 这将强制您的代码使用LINQ-to-Objects(意味着它在IEnumerable
上运行,就像匿名委托版本一样,而不是像lambda版本当前那样运行的IQueryable
),并且您将获得预期的exception。
TL; DR版本
它的长短是你的lambda表达式被转换成针对你的数据源的某种查询,而匿名方法版本正在评估内存中的整个数据源。 无论做什么,将lambda转换为查询都不能代表您期望的逻辑,这就是为什么它不会产生您期望的结果。
尝试使用与委托相同的条件写入lambda。 像这样:
List analysisObjects = analysisObjectRepository.FindAll().Where( (x => (x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) || (x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId) ).ToList();
您正在委托中检查Parent
属性是否为null。 同样也适用于lambda表达式。
List analysisObjects = analysisObjectRepository .FindAll() .Where(x => (x.ID == packageId) || (x.Parent != null && (x.Parent.ID == packageId || (x.Parent.Parent != null && x.Parent.Parent.ID == packageId))) .ToList();