LINQ:一个“where”子句与多个链接“where子句”

时间:2015-04-27 15:33:24

标签: c# .net linq

我想知道下一件事:

我可以使用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();

两个查询都应该产生相同的输出,但我想知道这两个中的一个是否有任何优点/缺点。例如,一种方法是否产生较慢的数据库查询,或者它们是否会生成相同的sql-query?

2 个答案:

答案 0 :(得分:3)

我使用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])

所以看起来(至少对于像这样的简单情况),没有显着的性能差异。我想你可以说,如果你使用多Where方法,那么表达访问者需要花费更长的时间来检查你的树,但从​​长远来看这可以忽略不计。

答案 1 :(得分:0)

我在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 ms
  • 对于两个Where子句,19596 ms

这似乎表明第一个更快一点。这可能只是编译器在幕后做了一些魔术,但通过计算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$1$0000
IL_0021:  br.s        IL_0023
IL_0023:  ldloc.0     // CS$1$0000
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$1$0000
IL_000E:  br.s        IL_0010
IL_0010:  ldloc.0     // CS$1$0000
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$1$0000
IL_000E:  br.s        IL_0010
IL_0010:  ldloc.0     // CS$1$0000
IL_0011:  ret         

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

我的建议是针对实际的SQL数据库运行测试(就像我上面编写的那样),看看性能是什么样的。生成的IL可能几乎相同,但在转换为SQL时可能会有很大的不同。