Чтобы вернуть IQueryable<T> или нет, вернуть IQueryable<T>
У меня есть класс репозитория, который оборачивает мой LINQ to SQL Data Context. Класс репозитория является классом бизнес-линии, который содержит всю логику уровня данных (и кеширование и тому подобное).
Вот мой v1 моего интерфейса репо.
public interface ILocationRepository
{
IList<Location> FindAll();
IList<Location> FindForState(State state);
IList<Location> FindForPostCode(string postCode);
}
Но чтобы справиться с поиском по страницам для FindAll, я спорю о том, использовать ли IQueryable
Каковы преимущества и недостатки предоставления IQueryable из хранилища данных?
Любая помощь очень ценится.
3 ответа
Плюсы; компонуемости:
- абоненты могут добавлять фильтры
- абоненты могут добавить пейджинг
- абоненты могут добавить сортировку
- так далее
Минусы; без проверяемость:
- Ваш репозиторий больше не является надлежащим образом тестируемым модулем; Вы не можете положиться на: это работает, b: что он делает;
- вызывающая сторона может добавить непереводимую функцию (т.е. нет отображения TSQL; разрывы во время выполнения)
- вызывающая сторона может добавить фильтр / сортировку, которая заставляет его работать как собака
- Так как абоненты ожидают
IQueryable<T>
чтобы быть компонуемым, он исключает нескомпозиционные реализации или заставляет вас написать свой собственный поставщик запросов для них - это означает, что вы не можете оптимизировать / профилировать DAL
Для стабильности я взял на себя не выставлять IQueryable<T>
или же Expression<...>
в моих репозиториях. Это означает, что я знаю, как ведет себя репозиторий, и мои верхние уровни могут использовать макеты, не беспокоясь "поддерживает ли реальный репозиторий это?" (форсирование интеграционных тестов).
Я все еще использую IQueryable<T>
и т.д. внутри хранилища - но не за границей. Я разместил еще несколько мыслей на эту тему здесь. Настроить параметры разбиения на страницы в интерфейсе хранилища также просто. Вы можете даже использовать методы расширения (в интерфейсе), чтобы добавить необязательные параметры подкачки, чтобы у конкретных классов был только один реализуемый метод, но для вызывающей стороны может быть 2 или 3 перегрузки.
Как упоминалось в предыдущем ответе, разоблачение IQueryable дает вызывающим абонентам доступ к игре с самим IQueryable, что является или может стать опасным.
Первой обязанностью инкапсуляции бизнес-логики является поддержание целостности вашей базы данных.
Вы можете продолжить выставлять IList и можете изменить ваши параметры следующим образом, вот как мы делаем...
public interface ILocationRepository
{
IList<Location> FindAll(int start, int size);
IList<Location> FindForState(State state, int start, int size);
IList<Location> FindForPostCode(string postCode, int start, int size);
}
если размер == -1, тогда вернуть все...
Альтернативный способ...
Если вы все еще хотите вернуть IQueryable, то вы можете вернуть IQueryable из List внутри своих функций... например...
public class MyRepository
{
IQueryable<Location> FindAll()
{
List<Location> myLocations = ....;
return myLocations.AsQueryable<Location>;
// here Query can only be applied on this
// subset, not directly to the database
}
}
Первый метод имеет преимущество перед памятью, потому что вы будете возвращать меньше данных вместо всех.
Я рекомендую использовать IEnumerable
вместо IList
С этим у вас будет больше гибкости.
Таким образом, вы сможете получить от Db только ту часть данных, которую вы действительно собираетесь использовать, без дополнительной работы, выполненной в вашем хранилище.
Образец:
// Repository
public interface IRepository
{
IEnumerable<Location> GetLocations();
}
// Controller
public ActionResult Locations(int? page)
{
return View(repository.GetLocations().AsPagination(page ?? 1, 10);
}
Который очень чистый и простой.