В конечном счете, LINQ для IEnumerable<T>
- это реализация каналов и фильтров. IEnumerable<T>
- это API потокового - это означает, что данные лениво возвращаются, когда вы их запрашиваете (через блоки итераторов), вместо загрузки всего сразу и возврата большого буфера записей.
Это означает, что ваш запрос:
var qry = from row in source // IEnumerable<T>
where row.Foo == "abc"
select new {row.ID, row.Name};
есть:
var qry = source.Where(row => row.Foo == "abc")
.Select(row = > new {row.ID, row.Name});
когда вы перечислите это, оно будет лениво потреблять данные. Вы можете увидеть это графически с помощью Jon Skeet Visual LINQ . Единственные вещи, которые ломают трубу, это вещи, которые вызывают буферизацию; OrderBy
, GroupBy
и т. Д. Для работы с большими объемами Джон и я работали над Push LINQ для выполнения агрегатов без буферизации в таких сценариях.
IQueryable<T>
(доступно большинству инструментов ORM - LINQ-to-SQL, Entity Framework, LINQ-to-NHibernate) - немного другой зверь; Поскольку ядро базы данных будет выполнять большую часть тяжелой работы, есть вероятность, что большинство шагов уже выполнено - все, что осталось, это потребить IDataReader
и спроецировать это на объекты / значения - но это все еще обычно труба (IQueryable<T>
реализует IEnumerable<T>
), если вы не позвоните .ToArray()
, .ToList()
и т. д.
Что касается использования на предприятии ... На мой взгляд то, что можно использовать IQueryable<T>
для написания составных запросов внутри хранилища, но они не должны оставлять хранилище - так как это сделало бы внутреннюю работу хранилища подчиненной вызывающей стороне, так что вы не смогли бы правильно выполнить модульное тестирование / профиль / оптимизацию / и т. д. Я взял на себя умные действия в хранилище, но возвращаю списки / массивы. Это также означает, что мой репозиторий не знает о реализации.
Это позор - так как соблазн "вернуть" IQueryable<T>
из метода репозитория довольно велик; например, это позволит вызывающей стороне добавить пейджинг / фильтры / и т. д., но помните, что они на самом деле еще не использовали данные. Это делает управление ресурсами болью. Кроме того, в MVC и т. Д. Вам необходимо убедиться, что контроллер вызывает .ToList()
или аналогичный, чтобы не представление, управляющее доступом к данным (иначе, опять же, вы не можете объединить проверьте контроллер правильно).
A безопасное (IMO) использование фильтров в DAL может быть следующим:
public Customer[] List(string name, string countryCode) {
using(var ctx = new CustomerDataContext()) {
IQueryable<Customer> qry = ctx.Customers.Where(x=>x.IsOpen);
if(!string.IsNullOrEmpty(name)) {
qry = qry.Where(cust => cust.Name.Contains(name));
}
if(!string.IsNullOrEmpty(countryCode)) {
qry = qry.Where(cust => cust.CountryCode == countryCode);
}
return qry.ToArray();
}
}
Здесь мы добавили фильтры на лету, но ничего не произойдет, пока мы не позвоним ToArray
. На этом этапе данные получены и возвращены (используя контекст данных в процессе). Это может быть полностью проверено модулем. Если мы сделали нечто подобное, но только что вернули IQueryable<T>
, вызывающая сторона может сделать что-то вроде:
var custs = customerRepository.GetCustomers()
.Where(x=>SomeUnmappedFunction(x));
И вдруг наш DAL начинает выходить из строя (не может перевести SomeUnmappedFunction
в TSQL и т. Д.). Тем не менее, вы можете сделать много интересных вещей в хранилище.
Единственная болевая точка здесь заключается в том, что это может подтолкнуть вас к нескольким перегрузкам для поддержки различных шаблонов вызовов (с / без подкачки и т. Д.). Пока необязательные / именованные параметры не поступят, я считаю, что лучший ответ здесь - использовать методы расширения в интерфейсе; Таким образом, мне нужна только одна конкретная реализация репозитория:
class CustomerRepository {
public Customer[] List(
string name, string countryCode,
int? pageSize, int? pageNumber) {...}
}
interface ICustomerRepository {
Customer[] List(
string name, string countryCode,
int? pageSize, int? pageNumber);
}
static class CustomerRepositoryExtensions {
public static Customer[] List(
this ICustomerRepository repo,
string name, string countryCode) {
return repo.List(name, countryCode, null, null);
}
}
Теперь у нас есть виртуальные перегрузки (как методы расширения) для ICustomerRepository
- так что наш абонент может использовать repo.List("abc","def")
без указания подкачки.
Наконец - без LINQ использование труб и фильтров становится намного более болезненным. Вы будете писать какой-то текстовый запрос (TSQL, ESQL, HQL). Вы можете явно добавлять строки, но это не очень "труба / фильтр". «Criteria API» немного лучше - но не так элегантно, как LINQ.