linq样式,链接where子句vs和运算符

时间:2011-03-04 01:45:00

标签: linq language-agnostic coding-style

写作是否存在(逻辑/性能)差异:

ATable.Where(x=> condition1 && condition2 && condition3)

ATable.Where(x=>condition1).Where(x=>condition2).Where(x=>condition3)

我一直在使用前者,但意识到使用后者,我可以阅读和复制部分查询,以便更容易在其他地方使用。 有什么想法吗?

1 个答案:

答案 0 :(得分:22)

简短回答
您应该在应用程序中执行您认为更具可读性和可维护性的内容,因为它们都将评估到同一个集合。

长答案 很长时间

Linq To Objects
ATable.Where(x=> condition1 && condition2 && condition3) 对于此示例由于只有一个谓词语句,编译器只需要生成一个委托和一个编译器生成的方法 来自反射器

if (CS$<>9__CachedAnonymousMethodDelegate4 == null)
{
    CS$<>9__CachedAnonymousMethodDelegate4 = new Func<ATable, bool>(null, (IntPtr) <Main>b__0);
}
Enumerable.Where<ATable>(tables, CS$<>9__CachedAnonymousMethodDelegate4).ToList<ATable>();

编译器生成的方法:

[CompilerGenerated]
private static bool <Main>b__0(ATable m)
{
    return ((m.Prop1 && m.Prop2) && m.Prop3);
}

正如您所看到的那样,由于只有一个Enumerable.Where<T>扩展方法,因此只有一个调用Where与委托的调用。


ATable.Where(x=>condition1).Where(x=>condition2).Where(x=>condition3)现在为此示例生成了更多代码。

    if (CS$<>9__CachedAnonymousMethodDelegate5 == null)
    {
        CS$<>9__CachedAnonymousMethodDelegate5 = new Func<ATable, bool>(null, (IntPtr) <Main>b__1);
    }
    if (CS$<>9__CachedAnonymousMethodDelegate6 == null)
    {
        CS$<>9__CachedAnonymousMethodDelegate6 = new Func<ATable, bool>(null, (IntPtr) <Main>b__2);
    }
    if (CS$<>9__CachedAnonymousMethodDelegate7 == null)
    {
        CS$<>9__CachedAnonymousMethodDelegate7 = new Func<ATable, bool>(null, (IntPtr) <Main>b__3);
    }
    Enumerable.Where<ATable>(Enumerable.Where<ATable>(Enumerable.Where<ATable>(tables, CS$<>9__CachedAnonymousMethodDelegate5), CS$<>9__CachedAnonymousMethodDelegate6), CS$<>9__CachedAnonymousMethodDelegate7).ToList<ATable>();

由于我们有三个链式扩展方法,我们还得到三个Func<T>以及三个编译器生成的方法。

[CompilerGenerated]
private static bool <Main>b__1(ATable m)
{
    return m.Prop1;
}

[CompilerGenerated]
private static bool <Main>b__2(ATable m)
{
    return m.Prop2;
}

[CompilerGenerated]
private static bool <Main>b__3(ATable m)
{
    return m.Prop3;
}

现在这看起来应该更慢,因为哎呀还有更多的代码。但是,由于所有执行都被推迟到GetEnumerator()被调用,我怀疑是否会出现明显的差异。

可能会影响效果的一些问题

  • 对链中的GetEnumerator的任何调用都将导致集合被迭代。 ATable.Where().ToList().Where().ToList()将导致在调用ToList时使用第一个谓词对第一个谓词进行迭代,然后使用第二个ToList进行另一次迭代。尝试将GetEnumerator调用到最后一刻,以减少迭代集合的次数。

Linq To Entities
由于我们现在使用IQueryable<T>,因此我们编译器生成的代码有点不同,因为我们使用的是Expresssion<Func<T, bool>>而不是普通Func<T, bool>

一体化的例子。
var allInOneWhere = entityFrameworkEntities.MovieSets.Where(m => m.Name == "The Matrix" && m.Id == 10 && m.GenreType_Value == 3);

这会产生一个声明。

IQueryable<MovieSet> allInOneWhere = Queryable.Where<MovieSet>(entityFrameworkEntities.MovieSets, Expression.Lambda<Func<MovieSet, bool>>(Expression.AndAlso(Expression.AndAlso(Expression.Equal(Expression.Property(CS$0$0000 = Expression.Parameter(typeof(MovieSet), "m"), (MethodInfo) methodof(MovieSet.get_Name)), ..tons more stuff...ParameterExpression[] { CS$0$0000 }));

最值得注意的是,我们最终得到一个解析为Expression.AndAlso件的表情树。而且就像预期的那样,我们只有一次调用Queryable.Where

var chainedWhere = entityFrameworkEntities.MovieSets.Where(m => m.Name == "The Matrix").Where(m => m.Id == 10).Where(m => m.GenreType_Value == 3);

我甚至不打算在编译器代码中粘贴这个,这很长。但总之,我们最终得到三个调用Queryable.Where(Queryable.Where(Queryable.Where()))和三个表达式。这是预期的,因为我们有三个链式Where条款。

生成的Sql
IEnumerable<T> IQueryable<T>一样,在调用枚举数之前也不会执行。因此,我们很高兴知道两者都产生相同的sql语句:

SELECT 
[Extent1].[AtStore_Id] AS [AtStore_Id], 
[Extent1].[GenreType_Value] AS [GenreType_Value], 
[Extent1].[Id] AS [Id], 
[Extent1].[Name] AS [Name]
FROM [dbo].[MovieSet] AS [Extent1]
WHERE (N'The Matrix' = [Extent1].[Name]) AND (10 = [Extent1].[Id]) AND (3 = [Extent1].[GenreType_Value])

可能会影响效果的一些问题

  • 对链中的GetEnumerator的任何调用都将导致调用sql,例如ATable.Where().ToList().Where()实际上将查询sql以查找与第一个谓词匹配的所有记录,然后使用linq将列表过滤到具有第二个谓词的对象。
  • 由于您提到提取谓词以使用其他位置,因此确定它们的格式为Expression<Func<T, bool>>,而不仅仅是Func<T, bool>。第一个可以解析为表达式树并转换为有效的sql,第二个将触发 ALL OBJECTS 返回,Func<T, bool>将在该集合上执行。

我希望这对回答你的问题有点帮助。