LINQ:一个 "where" 子句与多个链式 "where clauses"

LINQ: One "where" clause versus multiple chained "where clauses"

我在想接下来的事情:

我可以使用 LINQ To Entities 查询我的数据库,如下所示:

GetAll().Where(
      x => x.SomeProperty == 'Yes'
      && x.SomeOtherProperty == 'No')
.ToList();

虽然我看到我的一些同事更改了这两个 WHERE 子句,如下所示:

GetAll().Where(x => x.SomeProperty == 'Yes')
      .Where(x.SomeOtherProperty == 'No')
.ToList();

两个查询应该产生相同的输出,但我想知道两者之一是否有 advantages/disadvantages。例如,一种方法会产生较慢的数据库查询,还是会产生相同的 sql-query?

我在 LINQPad 中创建了以下测试用例。

void Main()
{
    var listOfObjects = Enumerable.Range(0, 100000).Select(x => new TestClass() { SomeProperty = x.ToString() }).ToArray();
    var iterations = DateTime.UtcNow.Day * 50;
    Console.WriteLine("Doing {0} iterations", iterations);

    var sw = Stopwatch.StartNew();
    for(int i = 0; i < iterations; i++)
    {
        var filteredList = listOfObjects.Where(x => x.SomeProperty.Contains('0') && x.SomeProperty.Contains('1')).ToArray();
    }
    sw.Stop();

    Console.WriteLine(sw.ElapsedMilliseconds);

    sw.Reset();
    sw.Start();
    for(int i = 0; i < iterations; i++)
    {
        var filteredList = listOfObjects.Where(x => x.SomeProperty.Contains('0')).Where(x => x.SomeProperty.Contains('1')).ToArray();
    }
    sw.Stop();
    Console.WriteLine(sw.ElapsedMilliseconds);
}

public class TestClass
{
    public string SomeProperty { get; set; }
    public string SomeOtherProperty { get; set; }
}

迭代 1350 次后得到以下结果。

  • 对于 && 子句,19415 毫秒
  • 对于两个 Where 子句,19596 毫秒

这似乎表明第一个要快一点。这可能只是编译器在幕后做了一些魔术,但是通过计算 TestClass 的列表以及 运行 时间的迭代次数,它不应该做任何事情。

为了确保与实际 SQL 一致,您应该看一下 IL,但根据我的看来,有效代码似乎是相同的。

<Main>b__2:
IL_0000:  ldarg.0     
IL_0001:  callvirt    UserQuery+TestClass.get_SomeProperty
IL_0006:  ldc.i4.s    30 
IL_0008:  call        System.Linq.Enumerable.Contains
IL_000D:  brfalse.s   IL_001E
IL_000F:  ldarg.0     
IL_0010:  callvirt    UserQuery+TestClass.get_SomeProperty
IL_0015:  ldc.i4.s    31 
IL_0017:  call        System.Linq.Enumerable.Contains
IL_001C:  br.s        IL_001F
IL_001E:  ldc.i4.0    
IL_001F:  nop         
IL_0020:  stloc.0     // CS[=11=]00
IL_0021:  br.s        IL_0023
IL_0023:  ldloc.0     // CS[=11=]00
IL_0024:  ret         

<Main>b__3:
IL_0000:  ldarg.0     
IL_0001:  callvirt    UserQuery+TestClass.get_SomeProperty
IL_0006:  ldc.i4.s    30 
IL_0008:  call        System.Linq.Enumerable.Contains
IL_000D:  stloc.0     // CS[=11=]00
IL_000E:  br.s        IL_0010
IL_0010:  ldloc.0     // CS[=11=]00
IL_0011:  ret         

<Main>b__4:
IL_0000:  ldarg.0     
IL_0001:  callvirt    UserQuery+TestClass.get_SomeProperty
IL_0006:  ldc.i4.s    31 
IL_0008:  call        System.Linq.Enumerable.Contains
IL_000D:  stloc.0     // CS[=11=]00
IL_000E:  br.s        IL_0010
IL_0010:  ldloc.0     // CS[=11=]00
IL_0011:  ret         

使用&&Where子句是IL的第一位,显示了在匿名方法中对Contains的两次调用。使用双 Where 子句的测试调用两个不同的匿名方法,但是它们看起来是相同的,除了传递给 Contains 的参数。查看 main 方法,我们可以看到进行的调用,并且开销很小。当使用两个 Where 子句时,它会导致代码稍慢。

我的建议是 运行 针对实际的 SQL 数据库进行测试(就像我在上面编码的那样),看看性能如何。生成的 IL 可能几乎相同,但在转换为 SQL.

时可能会有显着差异

我使用 this article 中描述的模型和上下文设置了一个测试项目,并按照您问题中的模式记录了两个不同查询的 SQL。我提出的查询是:

db.Blogs
    .Where(b => b.BlogId == 0)
    .Where(b => b.Name == "Foo");

db.Blogs
    .Where(b => b.BlogId == 0 && b.Name == "Foo");

为两个查询生成的 SQL 是相同的:

SELECT 
    [Extent1].[BlogId] AS [BlogId], 
    [Extent1].[Name] AS [Name]
    FROM [dbo].[Blogs] AS [Extent1]
    WHERE (0 = [Extent1].[BlogId]) AND (N'Foo' = [Extent1].[Name])

所以看起来(至少对于像这样的简单情况),无论哪种方式都没有显着的性能差异。我猜你可能会争辩说,如果你使用 multiple Where 方法,表达式 visitor 会花费更长的时间来检查你的树,但这在长 运行.

中可以忽略不计