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

发布于 2024-10-21 20:10:03 字数 1905 浏览 2 评论 0 原文

问题简而言之

我们在Where子句中使用了一个lambda表达式,它没有返回“预期”结果。

快速摘要

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

detail

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

在下面的代码中,常识表明,使 3 个单独的 OR 条件中的任何一个为 true 的所有结果都应该像结果中一样返回。

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID               == packageId ||
                    x.Parent.ID        == packageId || 
                    x.Parent.Parent.ID == packageId)
        .ToList();

但上面的代码只返回子对象和孙子对象,而不返回使条件成立的根对象(具有 null Parent 值)

x.ID == packageId

构成第二个

x.Parent.ID == packageId

和第三个

x.Parent.Parent.ID == packageId

仅返回

子句的对象。如果我们只用下面的代码编写返回根对象的代码,那么它就会被返回,所以我们完全确定analysisObjectRepository包含所有对象

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID == packageId )
        .ToList();

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

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();

问题

我们是否在 lambda 表达式中遗漏了某些内容?这是一个非常简单的三部分 OR 条件,我们认为任何使三个条件中的任何一个为 true 的对象都应该被返回。我们怀疑具有 null Parent 值的根对象可能会导致问题,但无法准确地弄清楚。

任何帮助都会很棒。

the problem in short

we have a lambda expression used in the Where clause, which is not returning the "expected" result.

quick summary

in the analysisObjectRepository object, there are certain objects which also contain the parent relationship in a property named Parent. we are querying this analysisObjectRepository to return some objects.

detail

what the code below supposed to do is, returning the root, the first children (immediate children) and grandchildren of a specific object containing the ID value.

in the code below, common sense says that all the results which makes any of the 3 seperate OR conditions true should be returned as in the results.

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID               == packageId ||
                    x.Parent.ID        == packageId || 
                    x.Parent.Parent.ID == packageId)
        .ToList();

but the above code only returns the children and grandchildren, while not returning the root objects (with a null Parent value) which make the

x.ID == packageId

condition true.

only objects which make the second

x.Parent.ID == packageId

and third

x.Parent.Parent.ID == packageId

clauses are returned.

If we only write the code to return the root object with the below code, it is returned, so we are totally sure that analysisObjectRepository contains all the objects

List<AnalysisObject> analysisObjects = 
    analysisObjectRepository
        .FindAll()
        .Where(x => x.ID == packageId )
        .ToList();

However, when we rewrite it as a delegate, we get the expected result, returning all the expected objects.

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();

question

Are we missing something in the lambda expression? it is a really simple 3 part OR condition and we think that any object that makes any of the three conditions true should be returned. we suspected that the root object having a null Parent value might cause a problem but couldn't figure it out exactly.

any help would be great.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

单调的奢华 2024-10-28 20:10:03

您的第二个委托不是以匿名委托(而不是 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)

对于 ID 不匹配且父级为 null 或不匹配且祖父级为 null 的任何 x,对 lambda 的调用将引发异常。将 null 检查复制到 lambda 中,它应该可以正常工作。

在问题评论后编辑

如果您的原始对象不是 List,那么我们无法知道 FindAll() 的返回类型是什么,以及是否或者没有实现 IQueryable 接口。如果确实如此,那么这可能可以解释这种差异。由于 lambda 可以在编译时转换为 Expression> 但匿名委托不能,因此您可能正在使用 IQueryable< 的实现/code> 使用 lambda 版本时,但使用匿名委托版本时使用 LINQ-to-Objects。

这也可以解释为什么您的 lambda 不会导致 NullReferenceException。如果您要将 lambda 表达式传递给实现 IEnumerable IQueryable 的对象,则 lambda 的运行时评估 (这与其他方法(无论是否匿名)没有什么不同)在第一次遇到 ID 不等于目标和父级或祖级的对象时会抛出 NullReferenceException为空。

添加于 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");

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

第一个查询是简单版本。匿名方法生成一个委托,然后将其传递给 IEnumerable.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;
    }
}

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

第二个查询使用 lambda 表达式,转换为 Expression> 并使用 IQueryable.Where() 扩展方法。这会产生一个类型也为 IQueryable 的对象。所有这些都是通过将表达式传递给底层提供者来实现的。 这就是您没有收到 NullReferenceException 的原因。这完全取决于查询提供者如何将表达式(它不是一个可以调用的实际编译函数,而是使用对象的表达式的逻辑表示)转换为它想要的东西。可以使用。

查看区别(或者至少是存在区别)的一个简单方法是在调用 AsEnumerable() 之前调用 AsEnumerable() >Where 在 lambda 版本中。这将强制您的代码使用 LINQ-to-Objects(这意味着它像匿名委托版本一样在 IEnumerable 上运行,而不是像 lambda 版本那样在 IQueryable 上运行目前是这样),您将得到预期的异常。

TL;DR 版本

总而言之,您的 lambda 表达式正在被转换为针对数据源的某种查询,而匿名方法版本正在评估内存中的整个数据源。无论将 lambda 转换为查询,所做的任何操作都不代表您期望的逻辑,这就是为什么它不会产生您期望的结果。

Your second delegate is not a rewrite of the first in anonymous delegate (rather than lambda) format. Look at your conditions.

First:

x.ID == packageId || x.Parent.ID == packageId || x.Parent.Parent.ID == packageId

Second:

(x.ID == packageId) || (x.Parent != null && x.Parent.ID == packageId) || 
(x.Parent != null && x.Parent.Parent != null && x.Parent.Parent.ID == packageId)

The call to the lambda would throw an exception for any x where the ID doesn't match and either the parent is null or doesn't match and the grandparent is null. Copy the null checks into the lambda and it should work correctly.

Edit after Comment to Question

If your original object is not a List<T>, then we have no way of knowing what the return type of FindAll() is, and whether or not this implements the IQueryable interface. If it does, then that likely explains the discrepancy. Because lambdas can be converted at compile time into an Expression<Func<T>> but anonymous delegates cannot, then you may be using the implementation of IQueryable when using the lambda version but LINQ-to-Objects when using the anonymous delegate version.

This would also explain why your lambda is not causing a NullReferenceException. If you were to pass that lambda expression to something that implements IEnumerable<T> but not IQueryable<T>, runtime evaluation of the lambda (which is no different from other methods, anonymous or not) would throw a NullReferenceException the first time it encountered an object where ID was not equal to the target and the parent or grandparent was null.

Added 3/16/2011 8:29AM EDT

Consider the following simple example:

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");

These two methods produce entirely different results.

The first query is the simple version. The anonymous method results in a delegate that's then passed to the IEnumerable<MyObject>.Where extension method, where the entire contents of source will be checked (manually in memory using ordinary compiled code) against your delegate. In other words, if you're familiar with iterator blocks in C#, it's something like doing this:

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

The salient point here is that you're actually performing your filtering in memory on the client side. For example, if your source were some SQL ORM, there would be no WHERE clause in the query; the entire result set would be brought back to the client and filtered there.

The second query, which uses a lambda expression, is converted to an Expression<Func<MyObject, bool>> and uses the IQueryable<MyObject>.Where() extension method. This results in an object that is also typed as IQueryable<MyObject>. All of this works by then passing the expression to the underlying provider. This is why you aren't getting a NullReferenceException. It's entirely up to the query provider how to translate the expression (which, rather than being an actual compiled function that it can just call, is a representation of the logic of the expression using objects) into something it can use.

An easy way to see the distinction (or, at least, that there is) a distinction, would be to put a call to AsEnumerable() before your call to Where in the lambda version. This will force your code to use LINQ-to-Objects (meaning it operates on IEnumerable<T> like the anonymous delegate version, not IQueryable<T> like the lambda version currently does), and you'll get the exceptions as expected.

TL;DR Version

The long and the short of it is that your lambda expression is being translated into some kind of query against your data source, whereas the anonymous method version is evaluating the entire data source in memory. Whatever is doing the translating of your lambda into a query is not representing the logic that you're expecting, which is why it isn't producing the results you're expecting.

静若繁花 2024-10-28 20:10:03

尝试使用与委托相同的条件编写 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();

Try writting the lambda with the same conditions as the delegate. like this:

  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();
海的爱人是光 2024-10-28 20:10:03

您正在检查委托中的 Parent 属性是否为 null。同样的情况也适用于 lambda 表达式。

List<AnalysisObject> 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();

You are checking Parent properties for null in your delegate. The same should work with lambda expressions too.

List<AnalysisObject> 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();
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文