【问题标题】:Filtering IQueryable<T> returns wrong result过滤 IQueryable<T> 返回错误结果
【发布时间】:2012-01-09 22:43:34
【问题描述】:

我刚刚遇到了一个非常令人惊讶的问题。

情况很简单:返回所有当前处于活动状态的实体,意思是:根据其布尔Active属性过滤GetAll()方法返回的所有结果

public IQueryable<T> GetAllActive()
{
      return implementation.GetAll().Where(a => ((IDeactivable)a).Active);  
}

其中GetAll()方法的实现对象定义为:

public IQueryable<T> GetAll();

问题是,GetAllActive() 返回所有记录,无论其 Active 属性的值如何,就像没有 Where 子句一样。

这可能是什么原因?

注意:代码简化,检查T类型实现IDeactivable接口。在运行时也不会抛出异常。

编辑:实现对象返回的IQueryable来自NHibernate

Edit2:我使用以下代码检查实体的实际值(除了使用 VS 调试器):

foreach (var a in active) {              //active -> filtered IQueryable before return
        _logger.Warn(a.Id);
        _logger.Warn(((IDeactivable)a).Active);
}

结果是:

11/30/2011 18:10:00 WARN xxx.Repository`1.GetAllActive: 70db43fa-2361-4c1f-a8e5-9fab012b5a2b
11/30/2011 18:10:01 WARN xxx.Repository`1.GetAllActive: False
11/30/2011 18:10:02 WARN xxx.Repository`1.GetAllActive: 5493c9bb-ec6e-4690-b5d6-9fab012b5b16
11/30/2011 18:10:02 WARN xxx.Repository`1.GetAllActive: True

【问题讨论】:

  • 要解决它,您必须证明((IDeactivable)someRecord).Active 实际上在应该返回false 时返回。似乎它总是返回true
  • 你真的确定错误不喜欢你删除的一些代码吗?乍一看,它会做你想做的事。我的怀疑是问题出在您在简化中删除的代码中的某个地方,或者您是否证明上述代码确实也失败了?正如其他人可能会说的那样,您能否提供一个小而完整的程序来演示您的问题?
  • Where 谓词中的任何内容都是相关的。不要简化这部分。
  • 也用于调试尝试implementation.GetAll().ToEnumerable().Where(...,它将在.net 中进行过滤而不是翻译它。
  • 我已经尝试了几种写 Where 的方法,尽管它们中的大多数似乎都在做同样的工作,但结果是一样的。但是,我已经尝试了 CodeInChaos 提出的建议并且它确实有效。但它的真正原因是什么,它的作用是什么? QueryProvider 翻译器以某种方式忽略了我的过滤器?优化器?转换为 IEnumerable 并返回的性能开销是多少?

标签: c# .net nhibernate linq-to-nhibernate


【解决方案1】:

当您返回 IQueryable&lt;T&gt; 时,您实际上并没有返回结果集。你返回的是一个可以查询的对象。

.Where() 方法的执行是延迟,直到您(或调用您的方法的人)实际强制执行 Linq 链。这使得下游客户端可以将其额外的 Linq 方法应用于结果,仍然可以对整个 Linq 链进行惰性求值。

因此,当您说IQueryable&lt;T&gt; 正在返回所有记录时,您可能正在查看调试器中的结果,它显示的是未经过滤的原始数据集(因为 .Where() 尚未执行然而)。

转换为IEnumerable 起作用的原因是因为它触发了Linq 命令链的执行, 并且结果是一个真实的列表,而不是一个可以查询的对象。 调用ToList()ToArray() 也会触发执行。

简而言之,您可以确保在测试过程中从 Linq 方法中看到正确结果的唯一方法是强制执行 Linq 链:

foreach(var record in GetAllActive.ToList())
{
    // Display each record
}

如需了解其工作原理,请参阅Working with Deferred Execution。它包含一个示例,说明您实际上如何在从using 块返回IQueryable 时遇到麻烦,因为IQueryable 对象在查询执行之前被释放

【讨论】:

  • 我知道你提到的大部分事实,我能理解它是如何工作的。但是,很明显,CodeInChaos 提出的解决方案在生产环境中是不能被接受的,因为它破坏了延迟执行,在这里真的很感激。我相信每个人都会同意,我的代码的预期结果应该略有不同,因为无论何时指定或使用延迟执行,都不应改变查询的行为。
  • 当您最终使用代码中某处的查询(通过生成一个列表)时,您得到正确的结果。如果您希望在下游代码中使用查询结果之前测试查询结果,则只需执行强制转换或ToList()。我建议您在单元测试中执行ToList();您无需在 GetAllActive() 方法中返回 IEnumerable。事实上,它违背了目的。
  • 顺便说一句。使用foreach(GetAllActive中的var记录)和foreach(GetAllActive.ToList()中的var记录)遍历所有元素有什么区别。无论如何,查询必须在循环执行时执行。当然,在这两种方式中,您都可以从 using 块的外部获得 IQueryable。
  • 我可以说通过单元测试调用GetAllActive().Count() 是使用查询的正确方法吗?无论如何它都不会返回正确的结果。
  • MSDN 说 IQueryable.Count “依赖于源参数类型的实现。预期的行为是它计算源中的项目数。” i> 试试GetAllActive().AsEnumerable.Count()
【解决方案2】:

我尝试了几种不同的方法,最后我发现我的部分代码还没有经过测试。原来 LINQ Queries to NHibernate 在使用 Where 子句时引起了一些我以前没有注意到的问题。

最终,我发现我使用了错误版本的 LINQ to NHibernate QueryProvider(不是 NH 3.0 中包含的那个),这是一个已知问题。现在我已经摆脱了它,一切正常。谢谢你们的帮助,伙计们!你为我指明了正确的方向。

在以下线程中描述了提到的问题: Problem with linq query

【讨论】:

    猜你喜欢
    • 2019-10-07
    • 1970-01-01
    • 2019-10-20
    • 2010-10-17
    • 1970-01-01
    • 1970-01-01
    • 2016-11-30
    相关资源
    最近更新 更多