Ответ 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>
будет выполняться в этой коллекции.
Надеюсь, это было бы немного полезно ответить на ваш вопрос.