【问题标题】:Issue with LINQ 'OR' condition when adding .ToList (or ToArray) in the end最后添加 .ToList(或 ToArray)时出现 LINQ 'OR' 条件问题
【发布时间】:2014-12-16 21:40:52
【问题描述】:

通过 DataContext 使用 DB。

有这样的操作:

var flat = StavRealtyDb.TargetFlat
    .Where(x => true || _some condition_).ToList();

那么,如果条件的第一部分是true,那么编译器应该跳过第二部分,对吧? 但它不会发生。如果有 .ToArray(),也会发生同样的问题。

但是,如果它最终不是 .ToList 或 .ToArray 之类的东西 - 编译器将跳过条件的第二部分。

有什么问题?正常吗? :)

更新

好的,谢谢您的回答!我明白这很正常。但问题是条件的第一部分包含: someobject == null;第二部分包含:someobject.Contains()。

.Where(x => someobject == null || someobject.Field.Contains(x.somefield))

所以当 someobject == null 时我有 ArgumentNullException(我希望第一部分将返回 true,而第二部分不会执行)。你能告诉我,我该如何解决这个问题?

P.S:实际上,.Where() 表达式更复杂:

.Where(x=> (part1 || part 2) && (part1 || part2) && ......)

【问题讨论】:

  • 有什么问题?你能展示按照你期望的方式运行的代码吗?
  • 旁注:我不相信 compiler 会跳过正确的条件,即使 OR 的左侧部分对于 lambda/表达式为真(与 if(true || _some condition_) case 不同)。
  • 什么没有发生?无论如何,SQL 的行为都会有所不同。
  • @AlexeiLevenkov:编译器会优化它,但不会针对表达式树。
  • @AlexeiLevenkov:是的。 tryroslyn.azurewebsites.net/…

标签: c# sql-server linq asp-classic lambda


【解决方案1】:

你在 IQueryable 上做 Where 并且参数是 Expression 而不是 Func

http://msdn.microsoft.com/en-us/library/vstudio/bb535040%28v=vs.100%29.aspx

Expression 只是传递给底层的Where 实现。

我发现这个主题很有趣,所以我创建了一个示例。假设我们有以下代码:

using System;
using System.Linq.Expressions;

namespace TestApplication
{
    class CompilerTest
    {
        public void Test()
        {
            Func<bool> func = () => true || Foo();
            Expression<Func<bool>> expr = () => true || Foo();
        }

        public static bool Foo()
        {
            return new Random().Next() % 2 == 0;
        }
    }
}

我们构建它(Release build),反编译后的代码如下:

using System;
using System.Linq.Expressions;
using System.Runtime.CompilerServices;

namespace TestApplication
{
  internal class CompilerTest
  {
    [CompilerGenerated]
    private static Func<bool> CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate1;

    public CompilerTest()
    {
      base.\u002Ector();
    }

    public void Test()
    {
      if (CompilerTest.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate1 == null)
      {
        // ISSUE: method pointer
        CompilerTest.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate1 = new Func<bool>((object) null, __methodptr(\u003CTest\u003Eb__0));
      }
      Func<bool> func = CompilerTest.CS\u0024\u003C\u003E9__CachedAnonymousMethodDelegate1;
      (Expression<Func<bool>>) (() => true || CompilerTest.Foo());
    }

    public static bool Foo()
    {
      return new Random().Next() % 2 == 0;
    }

    [CompilerGenerated]
    private static bool \u003CTest\u003Eb__0()
    {
      return true;
    }
  }
}

如果我们隐藏编译器生成的代码(dotPeek 选项),它看起来像这样:

using System;
using System.Linq.Expressions;
namespace TestApplication
{
  internal class CompilerTest
  {
    public void Test()
    {
      Func<bool> func = (Func<bool>) (() => true);
      (Expression<Func<bool>>) (() => true || CompilerTest.Foo());
    }

    public static bool Foo()
    {
      return new Random().Next() % 2 == 0;
    }
  }
}

正如我们所见,在 lambda 中指定的 Func 代码 优化(如果编译器对其进行优化,那才是真正的问题)。表达式中指定的代码显然没有优化(因为它是一个表达式,所以从编译器的角度来看,此时没有什么可以优化的)。

【讨论】:

  • 感谢您的回答!现在我明白了。但问题是我得到了 ArgumentNullException。我已经更新了我的问题并进一步解释了我的问题。
【解决方案2】:

到目前为止,答案涉及一些发挥作用但并未涵盖全部情况的问题。此外,第二个条件是Contains 语句是必不可少的信息。所以假设核心语句是

StavRealtyDb.TargetFlat.Where(x => true || someList.Contains(x.Id))

如前所述,整个 LINQ 语句是 Expression,因此编译器不会应用任何优化。所以如果一切顺利的话-

  1. .Net 运行时成功创建表达式,
  2. 将其传递给 EF 查询提供程序,
  3. 查询提供程序将其转换为 SQL,
  4. ADO.Net 运行 SQL,
  5. 数据库执行它,并将结果集返回给 .Net 运行时,
  6. .Net 运行时从结果集中创建对象并将它们返回到var flat

在这个事件过程中,第二个谓词总是由 .Net 运行时读取,无论它前面有多少个谓词,但如果第二个谓词评估查询优化器找到这样做的理由。

现在出了点问题:someListnull。现在整个过程在第 3 步停止。查询提供程序尝试读取someList(以便将其转换为IN 子句)。但是,当然,这会因引用为空而失败。

所以你必须确保someList 永远不会为空(它可能是一个空列表),或者你可以组成Where 条件:只有当someList 不为空时,你才包括Contains谓词。

【讨论】:

  • 谢谢!简单快速的解决方案(将 List 清空并检查 .Any())。
【解决方案3】:

我认为这对于 Linq 提供商来说是正常的和设计的。在这种情况下,Linq Provider 旨在获取 Where 表达式并将其转换为 Sql 并在 Sql Server 上执行。

【讨论】:

  • 感谢您的回答!现在我明白了。但问题是我得到了 ArgumentNullException。我已经更新了我的问题并进一步解释了我的问题。
【解决方案4】:

Where() 将返回一个IQueryable&lt;&gt;,您可以将其视为准备执行的查询。但是,它仅在Where()构建,并且在需要实际数据时会执行 - 在这种情况下,当调用ToList() 时。该查询将保存 OR,因此它没有短路是完全正常的行为,因为它根本没有在 Where() 内执行。

如果您想更好地理解它,请尝试将其拆分并在 Where() 处设置断点。

var query = StavRealtyDb.TargetFlat.Where(x => true || _some condition_);
var actualData = query.ToList();

在调试时将鼠标悬停在query 上时,您会注意到它是一个实际的查询,例如

SELECT `Extent1`....

并且在调用ToList()之后,你会注意到数据是从数据库中检索出来的,并放入了List

【讨论】:

  • 感谢您的回答!现在我明白了。但问题是我得到了 ArgumentNullException。我已经更新了我的问题并进一步解释了我的问题。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-02-19
  • 2014-10-25
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多