Должны ли хранилища подвергать IQueryable уровню обслуживания или выполнять фильтрацию в реализации?

Я пытаюсь выбрать лучший шаблон для доступа к данным в моем приложении MVC. В настоящее время, следуя серии хранилищ MVC, я использую репозитории, подвергая IQueryable уровню сервиса, который затем применяет фильтры. Первоначально я использовал LINQtoSQL, например.

public interface IMyRepository
{
  IQueryable<MyClass> GetAll();
}

Реализовано в:

public class LINQtoSQLRepository : IMyRepository
{
   public IQueryable<MyClass> GetAll()
   {
      return from table in dbContext.table
             select new MyClass
             {
                Field1 = table.field1,
                ... etc.
             }
   }
}

Фильтр для идентификаторов:

public static class TableFilters 
{       
   public static MyClass WithID(this IQueryable<MyClass> qry, string id)
   {
      return (from t in qry
              where t.ID == id
              select t).SingleOrDefault();
   }     
}

Вызывается из службы:

public class TableService
{
   public MyClass RecordsByID(string id)
   {
      return _repository.GetAll()
                        .WithID(id);
   }
}

У меня возникла проблема, когда я экспериментировал с реализацией репозитория с использованием Entity Framework с LINQ to Entities. Класс filter в моем проекте содержит более сложные операции, чем "WHERE... ==..." в приведенном выше примере, который, как мне кажется, требует разных реализаций в зависимости от поставщика LINQ. В частности, у меня есть требование выполнить предложение SQL "WHERE... IN...". Я могу реализовать это в классе фильтра, используя:

string[] aParams = // array of IDs
qry = qry.Where(t => aParams.Contains(t.ID));

Однако для того, чтобы выполнить это против Entity Framework, мне нужно предоставить решение, такое как BuildContainsExpression, привязанное к платформе Entity Framework, Это означает, что у меня должно быть две различные реализации этого конкретного фильтра, в зависимости от основного поставщика.

Буду признателен за любые советы о том, как я должен исходить отсюда. Мне показалось, что разоблачение IQueryable из моего репозитория позволит мне выполнять фильтры на нем, независимо от базового провайдера, что позволяет мне переключаться между провайдерами, если и когда это необходимо. Однако описанная выше проблема заставляет меня думать, что я должен выполнять всю свою фильтрацию в репозиториях и возвращать IEnumerable, IList или отдельные классы.

Большое спасибо, Matt

Ответ 1

Это очень популярный вопрос. Я постоянно спрашиваю себя. Я всегда чувствовал, что лучше возвращать IEnumerable, а не IQueryable из репозитория.

Целью репозитория является инкапсуляция инфраструктуры базы данных, поэтому клиенту не нужно беспокоиться о источнике данных. Однако, если вы вернете IQueryable, вы на милость потребителя относительно того, какой запрос будет запущен против вашего db, и будут ли они делать то, что поставщик LINQ не поддерживает.

Возьмите подкачку, например. Допустим, у вас есть объект Customer, и ваша база данных может иметь сотни тысяч клиентов. Какой код вы предпочитаете написать своему клиенту?

var customers = repos.GetCustomers().Skip(skipCount).Take(pageSize).ToList();

ИЛИ

var customers = repos.GetCustomers(pageIndex, pageSize);

В первом подходе вы запрещаете репозиторию ограничивать количество записей, извлеченных из источника данных. Кроме того, ваш потребитель должен вычислить skipCount.

Во втором подходе вы предоставляете вашему клиенту более грубоватый интерфейс. Теперь ваш репозиторий может обеспечить некоторые ограничения на pageSize, чтобы оптимизировать запрос. Вы также инкапсулируете вычисление skipCount.

Однако, если сказать, что в вашей ситуации ваш клиент является вашим сервисом. Поэтому я полагаю, что вопрос действительно сводится к разделению проблем. Где лучше выполнить такую ​​логику проверки? Хорошо, что ответ вполне может быть "на службе". Но как насчет ответа на вопрос "Где лучше содержать логику запросов?". Для меня ответ явно "The Repository". Это его предполагаемая область знаний.