Вопрос

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

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

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

    (и т.д.),

Я начал внедрять методы репозитория, такие как FirstNameStartsWith(строковый префикс) и Младший после рождения (в течение года), в основном следуя многочисленным имеющимся примерам.

Затем я столкнулся с проблемой - что, если мне придется объединить несколько поисковых запросов?Каждый из моих методов поиска в репозитории, таких как описанный выше, возвращает только конечный набор реальных объектов домена.В поисках лучшего способа я начал писать методы расширения на 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);

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

Действительно ли это тот способ, которым нужно это сделать, или есть лучший способ достичь той же цели?

Это было полезно?

Решение

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

Затем, на уровне СЕРВИСОВ (вы используете n-уровневое решение, верно?:) ) я бы там занимался всеми "особыми" запросами.

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

Уровень хранилища

ContactRepository.cs

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

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

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

ОК..следующий слой.. УДАР НОГОЙ ...мы идем вверх (Начало кто-нибудь?)

Уровень сервисов

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();
}

Выполнено.

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

Примечания:-

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

Очки на вынос

  • Уровень сервисов обрабатывает все интеллектуальные функции.Именно здесь вы решаете, какие данные вам требуются.
  • Репозиторий представляет собой простой ВЫБОР * ИЗ ТАБЛИЦЫ или простую ВСТАВКУ / ОБНОВЛЕНИЕ в ТАБЛИЦУ.

Удачи :)

Другие советы

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

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

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

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

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

Некоторые общие правила, которым я следую в своем приложении (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).

Я надеюсь, что это поможет!

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

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

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

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

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

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 ( ДДД ) философии.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top