Должен ли репозиторий возвращать IEnumerable<T>, IQueryable<T> или List<T>?
Я хотел бы сделать свое приложение максимально гибким, но не копаться в дыре, сделав мой интерфейс слишком конкретным.
Какой тип объекта является лучшим для хранилища? IEnumerable, IQueryable или List?
Технологии, которые я собираюсь использовать,
Кэширование Azure App Fabric
Entity Framework 4.1
Возможно Windows Server AppFabric
4 ответа
Я бы сказал, создайте свой DAL, используя IQueryable, и передайте его, убедитесь, что ваш объект предполагает, что время жизни - это запрос. Таким образом, вы получите выгоду от отложенного выполнения, но подвержены риску неэффективных запросов к базе данных.
Затем убедитесь, что вы тестируете производительность своего приложения (или хотя бы части, которые с наибольшей вероятностью получат трафик) и видите шаблоны доступа к данным. Создайте специализированные методы в вашем DAL, чтобы получить полностью материализованные объекты и сделать эти запросы предварительно скомпилированными.
образец интерфейса хранилища будет как
public interface IDataContext
{
void Add<T>(T entity) where T : BaseEntity;
void Delete<T>(T entity) where T : BaseEntity;
IQueryable<T> Find<T>(Expression<Func<T, bool>> where) where T : BaseEntity;
}
где BaseEntity является базовым классом для всех наших классов, похоже, этот класс не сопоставлен ни с одной таблицей в БД
public abstract class BaseEntity
{
public int Id { get; set; }
public DateTime CreateDateTime { get; set; }
public string CreateUser { get; set; }
public DateTime ModDateTime { get; set; }
public string ModUser { get; set; }
public byte[] RowVersion { get; set; }
}
Expression<Func<T, bool>>
будет передавать все выражение в ваш репозиторий, а не просто Func, так как EF работает над выражением для генерации SQL-запроса, типичное использование будет
ICollection<WFGroup> wgGroups = this.dataContext.Find<WFGroup>((w) => true).ToList();
где WFGroup - это класс, производный от BaseEntity, я обычно использую ленивую загрузку и прокси и не отсоединяю / не присоединяю объекты к контексту.
Это зависит от того, хотите ли вы выполнять какие-либо будущие запросы к объекту и должны ли они быть в памяти или нет:
- Если будут запросы в будущем, и БД должна выполнить работу, верните IQueryable.
- Если должны быть будущие запросы, и это должно быть сделано в памяти, верните IEnumerable.
- Если больше не будет запросов, и все данные нужно будет прочитать, верните IList, ICollection и т. Д.
Это очень хорошая недавняя статья, которая освещает это, а именно под заголовком "Репозитории, которые возвращают IQueryable". Вот что он говорит:
Одна из причин, по которой мы используем шаблон репозитория, заключается в инкапсуляции толстых запросов. Эти запросы затрудняют чтение, понимание и тестирование действий в контроллерах ASP.NET MVC. Кроме того, по мере роста вашего приложения увеличивается вероятность повторения толстого запроса в нескольких местах. С помощью шаблона репозитория мы инкапсулируем эти запросы в классы репозитория. Результат - более тонкие, чистые, более удобные в обслуживании и более простые в тестировании действия. Рассмотрим этот пример:
var orders = context.Orders .Include(o => o.Details) .ThenInclude(d => d.Product) .Where(o => o.CustomerId == 1234);
Здесь мы напрямую используем DbContext без шаблона репозитория. Когда ваши методы репозитория возвращают IQueryable, кто-то другой получит этот IQueryable и создаст запрос поверх него. Вот результат:
var orders = repository.GetOrders() .Include(o => o.Details) .ThenInclude(d => d.Product) .Where(o => o.CustomerId == 1234);
Вы видите разницу между этими двумя фрагментами кода? Разница только в первой строке. В первом примере мы используем context.Orders, во втором мы используем repository.GetOrders(). Итак, какую проблему решает этот репозиторий? Ничего такого!
Ваши репозитории должны возвращать доменные объекты. Таким образом, метод GetOrders() должен возвращать IEnumerable. При этом второй пример можно переписать так:
var orders = repository.GetOrders (1234);
Увидеть разницу?
В результате я добавил следующее соглашение в моей команде:
Для методов класса репозитория никогда не возвращайте объект IQueryable. Всегда сначала перечисляйте или конвертируйте его (например, ToArray, ToList, AsEnumerable).
Причина в том, что IQueryable позволит вызывающей стороне использовать это и в конечном итоге изменить SQL-запрос, который выполняется в базе данных. Это может быть опасно с точки зрения производительности БД, но это больше касается SoC. Вызывающая сторона не заботится об источнике данных; он просто хочет данные.
Насколько вероятно, что вам когда-либо потребуется вернуть пользовательскую реализацию IEnumerable
(не коллекция) из вашего DAL? (Чтобы ответить на этот вопрос, посмотрите на свои предыдущие проекты и посчитайте, сколько из тех или yield return
у вас есть вокруг.)
Если ответ "не очень", я просто вернусь ICollection
или даже массивы (если вы хотите предотвратить непреднамеренное изменение результатов запроса.) В крайнем случае, если вам когда-либо понадобится изменить запрос на "потоковые" результаты с пользовательским IEnumerable
Вы всегда можете заставить старый метод вызывать новый и материализовать результаты, чтобы сохранить совместимость со старыми клиентами.