带有 lambda 表达式的 LINQ where 子句具有 OR 子句和返回不完整结果的空值
Posted
技术标签:
【中文标题】带有 lambda 表达式的 LINQ where 子句具有 OR 子句和返回不完整结果的空值【英文标题】:LINQ where clause with lambda expression having OR clauses and null values returning incomplete results 【发布时间】:2011-07-16 00:05:02 【问题描述】:简而言之问题
我们在 Where 子句中使用了一个 lambda 表达式,它没有返回“预期”的结果。
快速总结
在 analysisObjectRepository 对象中,有一些对象在名为 Parent 的属性中也包含父关系。我们正在查询这个 analysisObjectRepository 以返回一些对象。
详情
下面的代码应该做的是,返回包含 ID 值的特定对象的根、第一个子项(直接子项)和孙子项。
在下面的代码中,常识表明,使 3 个单独的 OR 条件中的任何一个为真的所有结果都应与结果一样返回。
List<AnalysisObject> analysisObjects =
analysisObjectRepository
.FindAll()
.Where(x => x.ID == packageId ||
x.Parent.ID == packageId ||
x.Parent.Parent.ID == packageId)
.ToList();
但上面的代码只返回子孙,而不返回根对象(父值为空),这使得
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 表达式中遗漏了什么?这是一个非常简单的 3 部分 OR 条件,我们认为应该返回使三个条件中的任何一个为真的任何对象。我们怀疑具有 null Parent 值的根对象可能会导致问题,但无法准确解决。
任何帮助都会很棒。
【问题讨论】:
analysisObjectRepository
是 List<AnalysisObject>
吗?如果是这样,则无需调用FindAll
。
这是什么?无论是使用 LINQ-to-Objects 还是 IQueryable
的自定义实现,都可以使 all 有所不同。
你能把它减少到一个显示错误行为的对象吗?出现这种情况时x.ID的值和packageId的值是多少?
【参考方案1】:
您的第二个委托不是以匿名委托(而不是 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)
对任何x
的 lambda 调用都会引发异常,其中 ID 不匹配且父级为 null 或不匹配且祖父级为 null。将空检查复制到 lambda 中,它应该可以正常工作。
对问题发表评论后编辑
如果你的原始对象不是List<T>
,那么我们无法知道FindAll()
的返回类型是什么,以及它是否实现了IQueryable
接口。如果是这样,那么这很可能解释了这种差异。因为 lambda 可以在编译时转换为 Expression<Func<T>>
但匿名委托不能,所以您可能在使用 lambda 版本时使用 IQueryable
的实现,但在使用匿名委托版本。
这也可以解释为什么您的 lambda 不会导致 NullReferenceException
。如果您将该 lambda 表达式传递给实现 IEnumerable<T>
但不 IQueryable<T>
的东西,则 lambda 的运行时评估(这与其他方法没有什么不同,无论是否匿名)将抛出一个 @ 987654332@ 第一次遇到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");
这两种方法产生完全不同的结果。
第一个查询是简单版本。匿名方法产生一个委托,然后传递给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;
这里的重点是您实际上是在客户端执行过滤内存。例如,如果您的源是某个 SQL ORM,则查询中将没有 WHERE
子句;整个结果集将被带回客户端并在那里过滤。
使用 lambda 表达式的第二个查询被转换为 Expression<Func<MyObject, bool>>
并使用 IQueryable<MyObject>.Where()
扩展方法。这会产生一个也键入为IQueryable<MyObject>
的对象。所有这些都通过将 表达式 传递给底层提供者来实现。 这就是为什么您没有收到NullReferenceException
。完全取决于查询提供者如何将表达式(而不是它可以调用的实际编译函数,是使用对象的表达式的 逻辑 的表示)转换成它的东西可以用。
查看区别(或至少存在)区别的一种简单方法是在 lambda 中调用 AsEnumerable()
之前调用 AsEnumerable()
版本。这将强制您的代码使用 LINQ-to-Objects(这意味着它像匿名委托版本一样在 IEnumerable<T>
上运行,而不是像当前的 lambda 版本那样在 IQueryable<T>
上运行),并且您将按预期得到异常。
TL;DR 版本
总而言之,您的 lambda 表达式正在被转换为针对您的数据源的某种查询,而匿名方法版本正在评估内存中的整个数据源。将 lambda 转换为查询的任何操作都不能代表您所期望的逻辑,这就是为什么它没有产生您所期望的结果。
【讨论】:
lambda 表达式执行完美,但只返回不完整的结果。存在具有不匹配 ID 值和 null 父值的对象。 Adam 您的评论阐明了这个问题,因为您已经猜到它实现了 IQueryable 接口。我们已经尝试了在 lambda 表达式中工作的委托的完全匹配,但它仍然没有以“预期”的方式工作,并且它没有抛出 NullReferenceException。我们不想在这个问题上浪费时间,我们将在这部分使用代表,但它为什么不能以“预期”的方式工作仍然是一个谜。 @Cihan:它不会抛出异常,因为 lambda(在 this 情况下)和匿名方法的工作方式完全不同。查看我的编辑。 很明显,它们以完全不同的方式工作。我们必须深入挖掘提供程序类结构,以找到导致这种看似“奇怪”行为的实现细微差别。感谢您的支持。【参考方案2】:尝试使用与委托相同的条件编写 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();
【讨论】:
我们已经尝试过了,但它没有以“预期”的方式工作。【参考方案3】:您正在检查代理中的 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();
【讨论】:
我们已经尝试过了,但没有成功。 lambda 表达式不应该处理这个问题吗? @yaqari:为什么 lambda 表达式会处理空值检查? 我想说 lambda 表达式没有抛出异常,并且第一个 OR 条件为真,因此 lambda 表达式应该“处理”并返回正确的结果。 如果没有空检查,当x.ID != packegeId && x.Parent == null
时它将失败。以上是关于带有 lambda 表达式的 LINQ where 子句具有 OR 子句和返回不完整结果的空值的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 linq lambda 扩展方法执行带有 where 子句的左外连接
如何为CriteriaOperator过滤对象转换为lambda表达式,即:linq to xpo的动态where语句
错误:对于带有 from 的 Linq,“表达式树 lambda 可能不包含空传播运算符”