Я использовал смесь # 2 и # 3, но, по возможности, предпочитаю строгий общий репозиторий (более строгий, чем предлагается в ссылке для № 3). # 1 не годится, потому что он плохо работает с модульным тестированием.
Если у вас есть более мелкий домен или вам нужно сузить, какие сущности, которые ваш домен разрешает запрашивать, я полагаю, что # 2- или # 3, который определяет интерфейсы репозитория сущности, которые сами реализуют общий репозиторий, имеет смысл. Тем не менее, я считаю утомительным и ненужным писать интерфейс и конкретную реализацию для каждого объекта, который я хочу запросить. Что хорошего public interface IFooRepository : IRepository<Foo>
(опять же, если мне не нужно ограничивать разработчиков набором разрешенных совокупных корней)?
Я просто определить свой общий интерфейс хранилища, с Add
, Remove
, Get
, GetDeferred
, Count
и Find
методы (Найти возвращает IQueryable
интерфейс позволяет LINQ), создают конкретную общую реализацию, и называют его в день. Я сильно полагаюсь на Find
и таким образом LINQ. Если мне нужно использовать конкретный запрос несколько раз, я использую методы расширения и записываю запрос с помощью LINQ.
Это покрывает 95% моих потребностей в персистентности. Если мне нужно выполнить какое-то действие настойчивости, которое невозможно сделать в общем случае, я использую самодельный API ICommand
. Например, скажем, что я работаю с NHibernate, и мне нужно выполнить сложный запрос как часть моего домена, или, возможно, мне нужно выполнить команду bulk. API выглядит примерно так:
// marker interface, mainly used as a generic constraint
public interface ICommand
{
}
// commands that return no result, or a non-query
public interface ICommandNoResult : ICommand
{
void Execute();
}
// commands that return a result, either a scalar value or record set
public interface ICommandWithResult<TResult> : ICommand
{
TResult Execute();
}
// a query command that executes a record set and returns the resulting entities as an enumeration.
public interface IQuery<TEntity> : ICommandWithResult<IEnumerable<TEntity>>
{
int Count();
}
// used to create commands at runtime, looking up registered commands in an IoC container or service locator
public interface ICommandFactory
{
TCommand Create<TCommand>() where TCommand : ICommand;
}
Теперь я могу создать интерфейс для представления конкретной команды.
public interface IAccountsWithBalanceQuery : IQuery<AccountWithBalance>
{
Decimal MinimumBalance { get; set; }
}
Я могу создать конкретную реализацию и использовать необработанный SQL, NHibernate HQL, что угодно, и зарегистрировать его с моим локатора службы.
Сейчас в моей бизнес-логике я могу сделать что-то вроде этого:
var query = factory.Create<IAccountsWithBalanceQuery>();
query.MinimumBalance = 100.0;
var overdueAccounts = query.Execute();
Вы также можете использовать шаблон спецификации с IQuery
строить содержательные, пользовательский ввод управляемых запросов, вместо того, чтобы интерфейс с миллионом путающие свойства, но предполагается, что вы не обнаруживаете, что шаблон спецификации запутывается сам по себе;).
Последний фрагмент головоломки - это когда ваш репозиторий должен выполнять определенную операцию репозитория pre-and-post. Теперь вы можете очень легко создать реализацию своего общего репозитория для определенного объекта, затем переопределить соответствующий метод (ы) и выполнить то, что вам нужно сделать, и обновить регистрацию IoC или локатора службы и выполнить с ней.
Однако иногда эта логика является сквозной и неудобной для реализации путем переопределения метода репозитория. Поэтому я создал IRepositoryBehavior
, что в основном представляет собой приемник событий. (Ниже всего лишь грубое определение с головы)
public interface IRepositoryBehavior
{
void OnAdding(CancellableBehaviorContext context);
void OnAdd(BehaviorContext context);
void OnGetting(CancellableBehaviorContext context);
void OnGet(BehaviorContext context);
void OnRemoving(CancellableBehaviorContext context);
void OnRemove(BehaviorContext context);
void OnFinding(CancellableBehaviorContext context);
void OnFind(BehaviorContext context);
bool AppliesToEntityType(Type entityType);
}
Теперь это поведение может быть чем угодно. Аудит, проверка безопасности, soft-delete, принудительное использование ограничений домена, проверка и т. Д. Я создаю поведение, регистрирую его с помощью IoC или локатора службы и изменяю свой общий репозиторий, чтобы взять коллекцию зарегистрированных IRepositoryBehavior
s и проверить каждое поведение против текущего типа репозитория и обернуть операцию в обработчиках pre/post для каждого применимого поведения.
Вот пример поведения мягкого удаления (soft-delete означает, что когда кто-то просит удалить объект, мы просто помечаем его как удаленный, чтобы он не возвращался снова, но на самом деле его физически не удаляли).
public SoftDeleteBehavior : IRepositoryBehavior
{
// omitted
public bool AppliesToEntityType(Type entityType)
{
// check to see if type supports soft deleting
return true;
}
public void OnRemoving(CancellableBehaviorContext context)
{
var entity = context.Entity as ISoftDeletable;
entity.Deleted = true; // when the NHibernate session is flushed, the Deleted column will be updated
context.Cancel = true; // set this to true to make sure the repository doesn't physically delete the entity.
}
}
Да, это в основном упрощенная и абстрактная реализация слушателей событий NHibernate, но вот почему мне это нравится.A) Я могу тестировать поведение, не внося NHibernate в изображение. B) Я могу использовать эти поведения вне NHibernate (скажем, репозиторий - это реализация клиента, которая обертывает вызовы службы REST). C) Слушатели событий NH могут быть настоящей болью в заднице ;)
Я бы сказал, что ссылка в номере 2 не является типичным шаблоном репозитория. Как правило, у вас есть репозиторий для каждого сводного корня в [DDD] (http://en.wikipedia.org/wiki/Domain-driven_design). Это хорошая [SO thread] (http://stackoverflow.com/questions/1958621/whats-an-aggregate-root) по этой теме. Пример в номере 2, как вы говорите, просто завершает таблицу. Похоже, что они реализуют шаблон только для реализации шаблона без реальной выгоды. Поэтому я согласился бы с тобой. – RobertMS
Возможно, вы правы. Однако при поиске в Интернете большинство примеров, которые я нашел, создавали отдельные обертки для каждого объекта, в том числе в некоторых моих книгах. В этом отношении код в ссылке, которую я разместил, выглядел типичным. Спасибо за ссылки. Я проверю их. –
@JonathanWood Здесь [решение мне больше нравится] (http://huyrua.wordpress.com/2010/07/13/entity-framework-4-poco-repository-and-specification-pattern/) (черт побери, я использую эта ссылка много). А именно, не общий интерфейс репозитория с общими методами. Это по-прежнему относительно тонкая оболочка вокруг «DbContext», но это позволяет упростить тестирование. –