具有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.Where扩展方法,其中将针对您的委托检查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.Where()扩展方法。 这会导致一个对象也被键入为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();