Подтвердить что ты не робот

Методы репозитория или расширение IQueryable

У меня есть репозитории (например, ContactRepository, UserRepository и т.д.), которые инкапсулируют доступ к данным в модель домена.

Когда я искал поиск данных, например.

  • найти контакт, чье имя начинается с XYZ
  • контакт, день рождения которого после 1960

    (и т.д.),

Я начал внедрять методы репозитория, такие как FirstNameStartsWith (префикс строки) и YoungerThanBirthYear (int year), в основном следуя многочисленным примерам.

Затем я попал в проблему - что, если мне нужно объединить несколько запросов? Каждый из моих методов поиска репозитория, например, выше, возвращает только конечный набор реальных объектов домена. В поисках лучшего способа я начал писать методы расширения на IQueryable <T> , например. это:

public static IQueryable<Contact> FirstNameStartsWith(
               this IQueryable<Contact> contacts, String prefix)
{
    return contacts.Where(
        contact => contact.FirstName.StartsWith(prefix));
}        

Теперь я могу делать такие вещи, как

ContactRepository.GetAll().FirstNameStartsWith("tex").YoungerThanBirthYear(1960);

Однако я обнаружил, что пишу методы расширения (и изобретаю сумасшедшие классы, такие как ContactsQueryableExtensions, и я теряю "хорошую группировку", имея все в соответствующем репозитории.

Это действительно способ сделать это, или есть лучший способ достичь той же цели?

4b9b3361

Ответ 1

@Alex - я знаю, что это старый вопрос, но то, что я бы делал, позволяло бы репозиторию делать действительно простые вещи. Это означает, что вы получите все записи для таблицы или представления.

Затем, в слое "УСЛУГИ" (вы используете n-уровневое решение, верно?:)), я бы обрабатывал все "специальные" материалы запроса.

Хорошо, пример времени.

Уровень репозитория

ContactRepository.cs

public IQueryable<Contact> GetContacts()
{
    return (from q in SqlContext.Contacts
            select q).AsQueryable();
}

Приятный и простой. SqlContext - это экземпляр вашего EF Context.., который имеет Entity на нем, называемый Contacts.. который является в основном вашим классом контактов sql.

Это означает, что этот метод в основном делает: SELECT * FROM CONTACTS... но он не попадает в базу данных с этим запросом.. это только запрос прямо сейчас.

Хорошо.. следующий слой.. KICK... вверх, мы идем (Inception any?)

Уровень обслуживания

ContactService.cs

public  ICollection<Contact> FindContacts(string name)
{
    return FindContacts(name, null)
}

public ICollection<Contact> FindContacts(string name, int? year)
{
   IQueryable<Contact> query = _contactRepository.GetContacts();

   if (!string.IsNullOrEmpty(name))
   {
       query = from q in query
               where q.FirstName.StartsWith(name)
               select q;
   }

   if (int.HasValue)
   {
       query = from q in query
               where q.Birthday.Year <= year.Value
               select q);
    }

    return (from q in query
            select q).ToList();
}

Готово.

Так что давайте повторим. Во-первых, мы начинаем с простого запроса "Получить все от контактов". Теперь, если у нас есть предоставленное имя, добавьте фильтр для фильтрации всех контактов по имени. Затем, если у нас есть год, мы отфильтровываем день рождения по году. И т.д. Наконец, мы попали в БД (с этим измененным запросом) и посмотрим, какие результаты мы вернем.

ПРИМЕЧАНИЯ: -

  • Я пропустил любую инъекцию зависимостей для простоты. Это более чем рекомендуется.
  • Это все pseduo-код. Untested (против компилятора), но вы получаете идею....

Точки выгрузки

  • Уровень сервисов обрабатывает все умения. Именно здесь вы решаете, какие данные вам нужны.
  • Репозиторий - это простой SELECT * FROM TABLE или простой INSERT/UPDATE в TABLE.

Удачи:)

Ответ 2

В последнее время я много думал об этом, начиная с моей нынешней работы. Я привык к репозиториям, они идут полным путем IQueryable, используя только голые репозитории костей, как вы предлагаете.

Я чувствую, что шаблон репо звучит и выполняет полуэффективное задание при описании того, как вы хотите работать с данными в домене приложения. Однако проблема, которую вы описываете, определенно происходит. Это становится беспорядочным, быстрым, сверх простого приложения.

Есть ли, возможно, способы переосмыслить, почему вы запрашиваете данные по-разному? Если нет, я действительно чувствую, что гибридный подход - лучший способ пойти. Создайте методы репо для повторного использования материала. На самом деле это имеет смысл. СУХОЙ и все такое. Но эти одноразовые? Почему бы не воспользоваться IQueryable и сексуальными вещами, которые вы можете с ним сделать? Как вы сказали, глупо создавать метод для этого, но это не значит, что вам не нужны данные. DRY действительно не применяется, не так ли?

Для этой цели потребуется дисциплина, но я считаю, что это правильный путь.

Ответ 3

Я понимаю, что это старо, но в последнее время я столкнулся с той же проблемой, и я пришел к такому же выводу, что и Чад: с небольшой дисциплиной гибрид методов расширения и методов репозитория, кажется, лучше всего работает.

Некоторые общие правила, которые я выполнял в моем приложении (Entity Framework):

Заказ запросов

Если метод используется только для упорядочения, я предпочитаю писать методы расширения, которые работают с IQueryable<T> или IOrderedQueryable<T> (чтобы использовать базового провайдера.), например.

public static IOrderedQueryable<TermRegistration> ThenByStudentName(
    this IOrderedQueryable<TermRegistration> query)
{
    return query
        .ThenBy(reg => reg.Student.FamilyName)
        .ThenBy(reg => reg.Student.GivenName);
}

Теперь я могу использовать ThenByStudentName() по мере необходимости в моем классе репозитория.

Запросы, возвращающие отдельные экземпляры

Если метод включает в себя запрос по примитивным параметрам, обычно требуется ObjectContext и не может быть легко сделана static. Эти методы я оставляю в своем репозитории, например.

public Student GetById(int id)
{
    // Calls context.ObjectSet<T>().SingleOrDefault(predicate) 
    // on my generic EntityRepository<T> class 
    return SingleOrDefault(student => student.Active && student.Id == id);
}

Однако, если вместо этого метод запрашивает EntityObject, используя его свойства навигации , его обычно можно сделать static довольно легко, и реализован как метод расширения. например.

public static TermRegistration GetLatestRegistration(this Student student)
{
    return student.TermRegistrations.AsQueryable()
        .OrderByTerm()
        .FirstOrDefault();
}

Теперь я могу удобно написать someStudent.GetLatestRegistration() без необходимости экземпляра репозитория в текущей области.

Запросы, возвращающие коллекции

Если метод возвращает некоторые IEnumerable, ICollection или IList, то мне нравится сделать его static, если это возможно, и оставить его в репозитории, даже если он использует свойства навигации. например.

public static IList<TermRegistration> GetByTerm(Term term, bool ordered)
{
    var termReg = term.TermRegistrations;
    return (ordered)
        ? termReg.AsQueryable().OrderByStudentName().ToList()
        : termReg.ToList();
}

Это связано с тем, что мои методы GetAll() уже хранятся в репозитории, и это помогает избежать суматохи методов расширения.

Еще одна причина не внедрять эти "сборщики" в качестве методов расширения - это то, что они потребуют более подробного наименования, чтобы иметь смысл, поскольку тип возврата не подразумевается. Например, последний пример станет GetTermRegistrationsByTerm(this Term term).

Надеюсь, это поможет!

Ответ 4

Шесть лет спустя я уверен, что @Alex решил свою проблему, но после прочтения принятого ответа я хотел добавить свои два цента.

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

Основная роль уровня сервиса заключается в том, чтобы придерживаться принципа разделения и логики команд, связанных с бизнес-функцией.

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

Рассмотрим два следующих сценария:

IQueryable<Vehicle> Vehicles { get; }

// raw data
public static IQueryable<Vehicle> OwnedBy(this IQueryable<Vehicle> query, int ownerId)
{
    return query.Where(v => v.OwnerId == ownerId);
}

// business purpose
public static IQueryable<Vehicle> UsedThisYear(this IQueryable<Vehicle> query)
{
    return query.Where(v => v.LastUsed.Year == DateTime.Now.Year);
}

Оба метода - это простые расширения запросов, но, тем не менее, они имеют разные роли. Первый - это простой фильтр, а второй - необходимость бизнеса (например, обслуживание или выставление счетов). В простом приложении можно реализовать их как в репозитории. В более идеалистической системе UsedThisYear лучше всего подходит для уровня обслуживания (и может даже быть реализована как обычный метод экземпляра), где он также может лучше облегчить стратегию разделения CQRS команд и запросов.

Ключевыми соображениями являются (а) основная цель вашего репозитория и (б) насколько вы любите придерживаться философии CQRS и DDD.