Преимущество создания общего репозитория против конкретного репозитория для каждого объекта?

Мы разрабатываем приложение 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

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

Ответ 5

Имейте общий репозиторий, который обернут определенным репозиторием. Таким образом, вы можете управлять общедоступным интерфейсом, но при этом иметь преимущество повторного использования кода, имеющего общий репозиторий.

Ответ 6

public class UserRepository: репозиторий, IUserRepository

Не следует вводить IUserRepository, чтобы избежать воздействия на интерфейс. Как говорили люди, вам может не понадобиться полный стек CRUD и т.д.