Преимущество создания общего хранилища по сравнению с конкретным хранилищем для каждого объекта? - PullRequest
130 голосов
/ 05 августа 2009

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

Что мне не хватает?

Ответы [ 5 ]

164 голосов
/ 05 августа 2009

Эта проблема такая же старая, как и сам шаблон репозитория. Недавнее представление 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 - это целая другая банка червей - мнений на эту тему столько же, сколько и пупков.

26 голосов
/ 20 марта 2010

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

Итак, в подобных случаях я часто создавал универсальный IRepository с полным стеком CRUD, который позволяет мне быстро поиграть с API и позволить людям играть с пользовательским интерфейсом и проводить интеграцию и приемочное тестирование пользователей. в параллели. Затем, когда я нахожу, что мне нужны конкретные запросы к репо и т. Д., Я начинаю заменять эту зависимость на конкретную, если это необходимо, и переходить оттуда. Один основной импл. его легко создавать и использовать (и, возможно, подключить к базе данных в памяти или статическим объектам, или смоделированным объектам или чему-либо еще).

Тем не менее, в последнее время я начал нарушать поведение. Таким образом, если вы делаете интерфейсы для IDataFetcher, IDataUpdater, IDataInserter и IDataDeleter (например), вы можете смешивать и сопоставлять, чтобы определять свои требования через интерфейс, а затем иметь реализации, которые заботятся о некоторых или всех из них, и я могу по-прежнему внедряйте реализацию «сделай все», чтобы использовать ее, пока я создаю приложение.

Пол

12 голосов
/ 05 августа 2009

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

5 голосов
/ 14 июня 2011

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

1 голос
/ 15 июля 2016

открытый класс UserRepository: Repository, IUserRepository

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

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