Подтвердить что ты не робот

Linq styling, цепочка where where vs and operator

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

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

или

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

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

4b9b3361

Ответ 1

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

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

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(), я сомневаюсь, что какая-либо заметная разница представится.

Некоторые Gotchas, которые могут влиять на производительность

  • Любой вызов 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])

Некоторые Gotchas, которые могут влиять на производительность

  • Любой вызов GetEnumerator в цепочке вызовет вызов sql, например. ATable.Where().ToList().Where() будет действительно запрашивать sql для всех записей, соответствующих первому предикату, а затем фильтровать список с linq для объектов со вторым предикатом.
  • Поскольку вы упоминаете извлечение предикатов для использования else где make, уверен, они имеют форму Expression<Func<T, bool>>, а не просто Func<T, bool>. Первый может быть проанализирован в дереве выражений и преобразован в действительный sql, второй приведет к возврату ВСЕ ОБЪЕКТОВ, а Func<T, bool> будет выполняться в этой коллекции.

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