Работа с запросами в шаблоне репозитория с несколькими конкретными реализациями? - PullRequest
10 голосов
/ 26 октября 2011

Это скорее академическое любопытство, но я пытаюсь выяснить, как лучше всего выполнить следующее.

Представьте себе ситуацию, когда у вас есть Person объект

public class Person {
    public string Name {get;set;}
    public int Age {get;set;}
}

и контракт с хранилищем для получения их из некоторого постоянного хранилища ...

public class IPersonRepository {
    public IEnumerable<Person> Search(*** SOME_METHOD_SIGNATURE ***);
}

Ваше потребительское приложение действительно не заботится о конкретной реализации. Он просто соберет правильную конкретную реализацию из Unity / Ninject и начнет запрашивать.

IPersonRespository repo = GetConcreteImplementationFromConfig();
repo.Search( ... );

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

Вариант 1.

public IEnumerable<Person> Search(Expression<Func<Person, bool>> expression);

Это хорошо, потому что если вы используете контекст данных LINQ Capable (например, EntityFramework), вы можете просто передать выражение непосредственно в ваш контекст. Эта опция, кажется, падает, хотя, если ваша реализация должна использовать созданные вручную хранимые procs / sql / ADO.NET и т. Д ...

Вариант 2.

public IEnumerable<Person> Search(PersonSearch parameters);

public class PersonSearch {
    public int? Age {get;set;}
    public string FullName {get;set;}
    public string PartialName { get; set; }
}

Этот вариант кажется наиболее гибким (в том смысле, что он будет работать с Linq, Plain Old SQL.

Но это просто воняет "написанием собственного языка запросов", потому что вам нужно учитывать каждый возможный запрос, который может захотеть сделать потребитель. например Возраст> = 18 && Возраст <= 65 && Имя НРАВИТСЯ '% John%' </p>

Вариант 3.

Есть ли другие варианты?

Ответы [ 3 ]

6 голосов
/ 13 ноября 2011
  1. Ваше утверждение о том, что «вариант 1, по-видимому, не работает, хотя, если ваша реализация использует хранимые вручную procs / sql / ADO.NET и т. Д.», Неверно. Полностью возможно сгенерировать модель, аналогичную той, что описана в варианте 2, путем интерпретации выражения запроса с реализацией ExpressionVisitor.

  2. Если вы собираетесь вернуть IEnumerable<T> вместо IQueryable<T> из метода LINQ Search, вы должны включить некоторый механизм для поддержки (1) paging и ( 2) сортировка .

  3. Мне действительно не нравится вариант № 2. Это не делает явным то, что вы ищете. например Если возраст равен нулю, значит ли это, что вы ищете пользователей с нулевым возрастом или игнорируете параметр возраста? Что делать, если указаны и Имя, и Имя_частицы? С подходом LINQ вы можете делать такие вещи, как StartsWith, Contains, Equals и т. Д.

  4. Реализация шаблона репозитория должна абстрагировать логику доступа к данным и предоставлять бизнес-ориентированный интерфейс. Прямой доступ к репозиторию с помощью универсального интерфейса (Expression<Func<Person,bool>>) позволяет немного потерять его, поскольку интерфейс не передает намерение вызывающей стороне. Есть несколько способов сделать это лучше.

    • Реализация основанного на выражениях LINQ шаблона спецификации создает более строго типизированные запросы. Таким образом, вместо запросов к взрослым по Search(person => person.Age.HasValue && person.Age.Value > 18) вы бы использовали синтаксис спецификации, например Search(new PersonIsAdultSpecification());, где спецификация оборачивает базовое выражение LINQ, но предоставляет бизнес-ориентированный интерфейс.
      • Лично мне нравится этот подход, но Айенде называет его «архитектурой в бездне гибели» , потому что он может легко привести к чрезмерному проектированию. Его альтернативное предложение заключается в том, чтобы обернуть конкретные запросы, подобные этому, в методы расширения. Я думаю, что это, вероятно, одинаково жизнеспособно, но я предпочитаю иметь строго типизированный объект.
      • Самый простой способ сделать это - объявить реалистичные запросы, которые вы будете выполнять в интерфейсе IPersonRepository. Таким образом, интерфейс фактически объявит метод SearchForAdults().

В целом . Всякий раз, когда вы запрашиваете базу данных, вы должны сознательно пытаться получить определенные данные. Всякий раз, когда вы запрашиваете репозиторий, вы должны сознательно пытаться получить бизнес-объекты, которые удовлетворяют определенному бизнес-ограничению. Почему бы не сделать это ограничение бизнеса более явным? Определение хорошего интерфейса для любого сервиса зависит от потребителя этого сервиса. Не существует универсального интерфейса хранилища Magic Bullet, но вы можете создать лучший или худший в контексте вашего конкретного приложения. Идея состоит в том, чтобы вспомнить, почему вы в первую очередь используете шаблон хранилища, и это должно упростить логику и создать более интуитивную точку доступа.

1 голос
/ 13 ноября 2011

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

TResult Aggregate<TResult>(Func<IQueryable<TEntity>, TResult> aggregatorFunc);

Этот метод является чем-то вроде инструмента монстра, который вы можете использовать различными способами:

var countSomething = repository.Aggregate(x => x.Where(y => y.SomeProperty).Count());

Обратите внимание, что (в зависимости от реализации вашего репозитория) вызов Count () скомпилируется в счетчик SQL, поэтому это НЕ счетчик в памяти ().

1 голос
/ 12 ноября 2011

Я не уверен, что в этом случае существует такая вещь, как «официальный источник», но я руководил разработкой LINQ для Visual Basic, так что, возможно, это что-то значит.В любом случае вот мое мнение:

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

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

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...