Преимущество создания общего репозитория против конкретного репозитория для каждого объекта?
Мы разрабатываем приложение ASP.NET MVC и теперь создаем классы репозитория/службы. Мне интересно, есть ли какие-либо существенные преимущества для создания универсального интерфейса IRepository, который реализуется всеми репозиториями, причем каждый репозиторий имеет свой собственный уникальный интерфейс и набор методов.
Например: общий интерфейс IRepository может выглядеть (взятый из этого ответа):
public interface IRepository : IDisposable
{
T[] GetAll<T>();
T[] GetAll<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter);
T GetSingle<T>(Expression<Func<T, bool>> filter, List<Expression<Func<T, object>>> subSelectors);
void Delete<T>(T entity);
void Add<T>(T entity);
int SaveChanges();
DbTransaction BeginTransaction();
}
Каждый репозиторий будет реализовывать этот интерфейс, например:
- CustomerRepository: IRepository
- ProductRepository: IRepository
- и др.
Альтернативой, который мы выполнили в предыдущих проектах, будет:
public interface IInvoiceRepository : IDisposable
{
EntityCollection<InvoiceEntity> GetAllInvoices(int accountId);
EntityCollection<InvoiceEntity> GetAllInvoices(DateTime theDate);
InvoiceEntity GetSingleInvoice(int id, bool doFetchRelated);
InvoiceEntity GetSingleInvoice(DateTime invoiceDate, int accountId); //unique
InvoiceEntity CreateInvoice();
InvoiceLineEntity CreateInvoiceLine();
void SaveChanges(InvoiceEntity); //handles inserts or updates
void DeleteInvoice(InvoiceEntity);
void DeleteInvoiceLine(InvoiceLineEntity);
}
Во втором случае выражения (LINQ или иначе) будут полностью содержаться в реализации Репозитория, кто бы ни реализовал эту услугу, просто должен знать, какую функцию репозитория вызывать.
Я думаю, я не вижу преимущества написания всего синтаксиса выражения в классе службы и передачи в репозиторий. Разве это не означает, что во многих случаях дублирующийся код LINQ дублируется?
Например, в нашей старой системе выставления счетов мы называем
InvoiceRepository.GetSingleInvoice(DateTime invoiceDate, int accountId)
из нескольких различных сервисов (Клиент, Счет, Счет и т.д.). Это кажется намного более чистым, чем запись следующего в нескольких местах:
rep.GetSingle(x => x.AccountId = someId && x.InvoiceDate = someDate.Date);
Единственный недостаток, который я вижу в использовании конкретного подхода, состоит в том, что мы могли бы получить множество перестановок функций Get *, но это все же кажется предпочтительным для толкания логики выражения в классы Service.
Что мне не хватает?
Ответы
Ответ 1
Это проблема, старая, как и шаблон репозитория. Недавнее введение LINQ IQueryable
, равномерное представление запроса, вызвало много дискуссий по этой теме.
Я предпочитаю определенные репозитории самостоятельно, после того, как очень усердно работал над созданием общей структуры репозитория. Независимо от того, какой умный механизм я пытался, я всегда оказывался в той же проблеме: репозиторий является частью моделируемого домена, и этот домен не является общим. Не каждый объект может быть удален, но не каждый объект может быть добавлен, а не каждый объект имеет репозиторий. Запросы сильно различаются; API-интерфейс репозитория становится таким же уникальным, как и сам объект.
Я часто использую шаблон, чтобы иметь определенные интерфейсы репозитория, но базовый класс для реализаций. Например, используя LINQ to SQL, вы можете сделать:
public abstract class Repository<TEntity>
{
private DataContext _dataContext;
protected Repository(DataContext dataContext)
{
_dataContext = dataContext;
}
protected IQueryable<TEntity> Query
{
get { return _dataContext.GetTable<TEntity>(); }
}
protected void InsertOnCommit(TEntity entity)
{
_dataContext.GetTable<TEntity>().InsertOnCommit(entity);
}
protected void DeleteOnCommit(TEntity entity)
{
_dataContext.GetTable<TEntity>().DeleteOnCommit(entity);
}
}
Замените DataContext
на ваш выбор по отдельности. Пример реализации может быть:
public interface IUserRepository
{
User GetById(int id);
IQueryable<User> GetLockedOutUsers();
void Insert(User user);
}
public class UserRepository : Repository<User>, IUserRepository
{
public UserRepository(DataContext dataContext) : base(dataContext)
{}
public User GetById(int id)
{
return Query.Where(user => user.Id == id).SingleOrDefault();
}
public IQueryable<User> GetLockedOutUsers()
{
return Query.Where(user => user.IsLockedOut);
}
public void Insert(User user)
{
InsertOnCommit(user);
}
}
Обратите внимание, что открытый API-интерфейс репозитория не позволяет удалять пользователей. Кроме того, обнажение IQueryable
- это целая другая червь из червей - в этой теме так много мнений, как кнопки пупка.
Ответ 2
Я действительно немного не согласен с сообщением Брайана. Я думаю, он прав, что в конечном счете все очень уникально и так далее. Но в то же время большинство из них выходит, когда вы разрабатываете дизайн, и я нахожу, что получаю общий репозиторий и использую его при разработке моей модели, я могу быстро получить приложение, а затем реорганизовать его на большую специфику, поскольку я нахожу необходимо сделать это.
Итак, в таких случаях я часто создавал общий IRepository, у которого есть полный стек CRUD, и это позволяет мне быстро играть с API и позволять людям играть с UI и проводить интеграционные и пользовательские приемочные испытания в параллели. Затем, когда я нахожу, что мне нужны конкретные запросы в репо и т.д., Я начинаю заменять эту зависимость с конкретным, если это необходимо, и оттуда оттуда. Один из основных факторов. легко создавать и использовать (и, возможно, подключаться к db в памяти или статическим объектам или издеваемым объектам или что-то еще).
Тем не менее, то, что я начал делать в последнее время, разрушает поведение. Итак, если вы используете интерфейсы для IDataFetcher, IDataUpdater, IDataInserter и IDataDeleter (например), вы можете смешивать и сопоставлять, чтобы определять свои требования через интерфейс, а затем иметь реализации, которые заботятся о некоторых или всех из них, и я могу все еще внедряйте реализацию make-it-all, пока я создаю приложение.
Пол
Ответ 3
Я предпочитаю определенные репозитории, которые получают из общего репозитория (или список общих репозиториев, чтобы указать точное поведение) с сигналами переопределяемых методов.
Ответ 4
У Грега Янга был отличный пост на эту тему.
http://codebetter.com/gregyoung/2009/01/16/ddd-the-generic-repository/
Ответ 5
Имейте общий репозиторий, который обернут определенным репозиторием. Таким образом, вы можете управлять общедоступным интерфейсом, но при этом иметь преимущество повторного использования кода, имеющего общий репозиторий.
Ответ 6
public class UserRepository: репозиторий, IUserRepository
Не следует вводить IUserRepository, чтобы избежать воздействия на интерфейс. Как говорили люди, вам может не понадобиться полный стек CRUD и т.д.