具有OR子句的lambda表达式的LINQ where子句和返回不完整结果的空值

Cih*_*urt 13 c# lambda delegates expression

问题简而言之

我们有一个在Where子句中使用的lambda表达式,它不会返回"预期"结果.

快速摘要

在analyzeObjectRepository对象中,某些对象也包含名为Parent的属性中的父关系.我们正在查询此analyzeObjectRepository以返回一些对象.

详情

下面的代码应该做的是,返回包含ID值的特定对象的根,第一个子节点(直接子节点)和孙子节点.

在下面的代码中,常识表示所有使3个单独的OR条件中的任何一个成为真的结果应该在结果中返回.

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID               == packageId ||
                    x.Parent.ID        == packageId || 
                    x.Parent.Parent.ID == packageId)
        .ToList();
Run Code Online (Sandbox Code Playgroud)

但上面的代码只返回子孙,而不返回根对象(使用null父值)

x.ID == packageId
Run Code Online (Sandbox Code Playgroud)

条件是真的.

只有制作第二个的对象

x.Parent.ID == packageId
Run Code Online (Sandbox Code Playgroud)

第三

x.Parent.Parent.ID == packageId
Run Code Online (Sandbox Code Playgroud)

条款被退回.

如果我们只编写代码以使用下面的代码返回根对象,则返回它,因此我们完全确定analyzeObjectRepository包含所有对象

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID == packageId )
        .ToList();
Run Code Online (Sandbox Code Playgroud)

但是,当我们将其重写为委托时,我们得到预期的结果,返回所有预期的对象.

List<AnalysisObject> 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();
Run Code Online (Sandbox Code Playgroud)

我们在lambda表达式中遗漏了什么吗?它是一个非常简单的3部分OR条件,我们认为应该返回任何使三个条件中的任何一个成为真的对象.我们怀疑具有null Parent值的根对象可能会导致问题,但无法准确找出问题.

任何帮助都会很棒.

Ada*_*son 13

你的第二个代表不是重写匿名委托(而不是lambda)格式的第一个委托.看看你的情况.

第一:

x.ID == packageId || x.Parent.ID == packageId || x.Parent.Parent.ID == packageId
Run Code Online (Sandbox Code Playgroud)

第二:

(x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) || 
(x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)
Run Code Online (Sandbox Code Playgroud)

对lambda的调用将为xID不匹配的任何内容抛出异常,并且父级为null或不匹配且祖父级为null.将空检查复制到lambda中,它应该可以正常工作.

评论到问题后编辑

如果你的原始对象不是a List<T>,那么我们无法知道返回类型FindAll()是什么,以及它是否实现了IQueryable接口.如果确实如此,则可能解释了这种差异.因为lambdas可以在编译时转换为Expression<Func<T>> 但匿名委托不能,所以你可能在使用IQueryablelambda版本时使用实现,但在使用匿名委托版本时使用LINQ-to-Objects.

这也可以解释为什么你的lambda没有导致NullReferenceException.如果你要的是lambda表达式传递的东西实现IEnumerable<T>,但不是 IQueryable<T>,拉姆达的运行评估(这是没有其它方法不同,匿名或不)会抛出一个NullReferenceException在第一次遇到一个对象,其中ID不等于目标而父母或祖父母是空的.

2011年3月16日上午8:29 EDT

考虑以下简单示例:

IQueryable<MyObject> source = ...; // some object that implements IQueryable<MyObject>

var anonymousMethod =  source.Where(delegate(MyObject o) { return o.Name == "Adam"; });    
var expressionLambda = source.Where(o => o.Name == "Adam");
Run Code Online (Sandbox Code Playgroud)

这两种方法产生完全不同的结果.

第一个查询是简单版本.匿名方法导致委托然后传递给IEnumerable<MyObject>.Where扩展方法,其中source将针对您的委托检查整个内容(使用普通编译代码手动在内存中).换句话说,如果你熟悉C#中的迭代器块,它就像这样:

public IEnumerable<MyObject> MyWhere(IEnumerable<MyObject> dataSource, Func<MyObject, bool> predicate)
{
    foreach(MyObject item in dataSource)
    {
        if(predicate(item)) yield return item;
    }
}
Run Code Online (Sandbox Code Playgroud)

这里的重点是你实际上是在客户端进行内存过滤.例如,如果您的源是某个SQL ORM,则WHERE查询中不会有任何子句; 整个结果集将被带回客户端并在那里进行过滤.

使用lambda表达式的第二个查询将转换为a Expression<Func<MyObject, bool>>并使用IQueryable<MyObject>.Where()扩展方法.这会导致一个对象也被输入为IQueryable<MyObject>.所有这些都适用于将表达式传递给基础提供程序.这就是为什么你没有得到一个NullReferenceException.这完全取决于查询提供程序如何将表达式(它可以只是调用的实际编译函数,它是使用对象的表达式逻辑的表示)转换为它可以使用的东西.

一种看待区别(或者至少存在区别)的简单方法AsEnumerable()在调用Wherelambda版本之前调用.这将强制您的代码使用LINQ-to-Objects(意味着它IEnumerable<T>像匿名委托版本一样运行,而不是IQueryable<T>像lambda版本当前那样),并且您将获得预期的异常.

TL; DR版本

它的长短是你的lambda表达式被转换成针对你的数据源的某种查询,而匿名方法版本正在评估内存中的整个数据源.无论做什么,将lambda转换为查询都不能代表您期望的逻辑,这就是为什么它不会产生您期望的结果.


Jea*_*aka 6

尝试使用与委托相同的条件写入lambda.像这样:

  List<AnalysisObject> 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();
Run Code Online (Sandbox Code Playgroud)