Мы разрабатываем приложение 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.
Чего мне не хватает?
источник
Ответы:
Это проблема такая же старая, как и сам шаблон репозитория. Недавнее введение LINQ
IQueryable
, единообразного представления запроса, вызвало много дискуссий по этой самой теме.Я сам предпочитаю конкретные репозитории, после того, как очень много работал над созданием общей структуры репозиториев. Независимо от того, какой хитрый механизм я пробовал, у меня всегда возникала одна и та же проблема: репозиторий является частью моделируемого домена, а этот домен не является универсальным. Не каждую сущность можно удалить, не каждую сущность можно добавить, не у каждой сущности есть репозиторий. Запросы сильно различаются; API репозитория становится таким же уникальным, как и сама сущность.
Я часто использую шаблон, чтобы иметь определенные интерфейсы репозитория, но базовый класс для реализаций. Например, используя LINQ to SQL, вы можете:
Замените
DataContext
единицей работы по своему выбору. Пример реализации может быть:Обратите внимание, что общедоступный API репозитория не позволяет удалять пользователей. Кроме того, разоблачение
IQueryable
- это совершенно другая баня червей - мнений на эту тему столько же, сколько и пупков.источник
На самом деле я немного не согласен с сообщением Брайана. Думаю, он прав, что в конечном итоге все очень уникально и так далее. Но в то же время большая часть этого проявляется в процессе проектирования, и я обнаружил, что, установив общий репозиторий и используя его при разработке моей модели, я могу очень быстро создать приложение, а затем провести рефакторинг для большей специфичности, когда я нахожу нужно сделать так.
Поэтому в подобных случаях я часто создавал общий IRepository с полным стеком CRUD, что позволяет мне быстро играть с API и позволять людям играть с пользовательским интерфейсом и параллельно проводить интеграционное и приемлемое тестирование пользователей. Затем, когда я обнаружил, что мне нужны конкретные запросы в репо и т. Д., Я начинаю заменять эту зависимость на конкретную, если это необходимо, и переходить оттуда. Один базовый имп. легко создать и использовать (и, возможно, подключиться к базе данных в памяти или статическим объектам, имитируемым объектам или чему-то еще).
Тем не менее, то, что я начал делать в последнее время, - это ломать поведение. Итак, если вы создаете интерфейсы для IDataFetcher, IDataUpdater, IDataInserter и IDataDeleter (например), вы можете смешивать и согласовывать, чтобы определять свои требования через интерфейс, а затем иметь реализации, которые позаботятся о некоторых или всех из них, и я могу по-прежнему внедрять универсальную реализацию, чтобы использовать ее, пока я создаю приложение.
Павел
источник
GetById()
. Должен ли я использоватьIRepository<T, TId>
,GetById(object id)
или делать предположения и использоватьGetById(int id)
? Как будут работать составные ключи? Я задавался вопросом, является ли общий выбор по идентификатору стоящей абстракцией. Если нет, то что еще в общих репозиториях пришлось бы выразить неудобно? Это была аргументация абстрагирования реализации , а не интерфейса .Я предпочитаю определенные репозитории, которые происходят из общего репозитория (или списка общих репозиториев, чтобы указать точное поведение) с переопределяемыми сигнатурами методов.
источник
Иметь общий репозиторий, заключенный в конкретный репозиторий. Таким образом, вы можете управлять общедоступным интерфейсом, но при этом иметь преимущество повторного использования кода, исходящего из общего репозитория.
источник
открытый класс UserRepository: Repository, IUserRepository
Разве вы не должны вводить IUserRepository, чтобы избежать раскрытия интерфейса. Как уже говорили люди, вам может не понадобиться полный стек CRUD и т. Д.
источник