Linq стиль, цепочка, где пункт против и оператор - PullRequest
12 голосов
/ 04 марта 2011

Есть ли (логическая / производительность) разница в написании:

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

или

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

Я использовал первый, но понял, что с последним я могу читать и копировать части запроса, чтобы их было проще использовать в другом месте. Есть мысли?

1 Ответ

22 голосов
/ 04 марта 2011

Краткий ответ Вы должны делать то, что, по вашему мнению, более читабельно и легко поддерживается в вашем приложении, поскольку оба будут оценивать одну и ту же коллекцию.

Длинный ответ довольно длинный

Линк к объектам 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>();

Поскольку у нас есть три цепных метода Extension, мы также получаем три Func<T> s и три метода, сгенерированных компилятором.

[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);

Я даже не буду вставлять код компилятора для этого, вплоть до long.Но вкратце мы получаем три вызова на 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, второй вызовет ВСЕ ОБЪЕКТЫ возвращено и Func<T, bool> будет выполнено в этой коллекции.

Я надеюсьЭто было немного полезно, чтобы ответить на ваш вопрос.

...