问题与LINQ' OR'在最后添加.ToList(或ToArray)时的条件

时间:2014-12-16 21:40:52

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

通过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) && ......)

4 个答案:

答案 0 :(得分:2)

您在[{1}}上Where进行了IQueryable而论证是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;
        }
    }
}

我们构建它(发布版本),反编译代码如下所示:

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代码是优化的(如果编译器对其进行优化,这是真正的问题)。表达式中指定的代码显然没有被优化(因为它是一个表达式,因此从编译器的角度来看,此时没有任何优化)。

答案 1 :(得分:1)

到目前为止,答案触及了一些可以发挥作用的问题,但并未涵盖整体情况。此外,第二个条件是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
  7. 在这个事件过程中,第二个谓词总是由.Net运行时,而不管它前面有多少谓词,但如果数据库评估查询优化器找到理由这样做。

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

    因此,您必须确保someList永远不会为空(可能是空列表),或者您可以撰写Where条件:仅当someList不是{0}时null,您包含Contains谓词。

答案 2 :(得分:0)

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

答案 3 :(得分:0)

Where()将返回IQueryable<>,您可以将其视为准备执行的查询。但是,Where()中只有内置,只要需要实际数据,它就会执行 - 在这种情况下,调用{{1}时}。查询将保持OR,因此它是完全正常的行为,它不会被短路,因为它根本不在ToList()内执行。

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

Where()

在调试时将鼠标悬停在var query = StavRealtyDb.TargetFlat.Where(x => true || _some condition_); var actualData = query.ToList(); 上时,您会发现它是一个像

这样的实际查询
query

调用SELECT `Extent1`.... 后,您会注意到数据已从数据库中检索出来并放入ToList()