Подтвердить что ты не робот

Попытка упростить наш шаблон хранилища

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

Во-первых, есть некоторые контроллеры, в которых нам требуется более 10-15 репозиториев, все в одном контроллере. Конструктор становится довольно уродливым, когда спрашивает столько репозиториев. Вторая причуда проявляется после вызова методов в нескольких репозиториях. После выполнения работы с несколькими репозиториями нам нужно вызвать метод SaveChanges, но в каком репозитории мы его будем называть? В каждом хранилище есть один. Все репозитории имеют один и тот же экземпляр контекста данных Entity Framework, который был введен, поэтому выбор любого случайного хранилища для вызова save on будет работать. Это просто кажется грязным.

Я посмотрел шаблон "Unit Of Work" и придумал решение, которое, как я думаю, решает обе проблемы, но я не уверен на 100% в этом решении, поэтому любые отзывы приветствуются. Я создал класс под названием DataBucket (в основном потому, что мне не нравится называть его UnitOfWork. Это звучит забавно.).

// Slimmed down for readability
public class DataBucket
{
    private DataContext _dataContext;

    public IReportsRepository ReportRepository { get; set; }
    public IEmployeeRepository EmployeeRepository { get; set; }
    public IDashboardRepository DashboardRepository { get; set; }

    public DataBucket(DataContext dataContext,
        IReportsRepository reportsRepository,
        IEmployeeRepository employeeRepository,
        IDashboardRepository dashboardRepository)
    {
        _dataContext = dataContext;
        this.ReportRepository = reportsRepository;
        this.EmployeeRepository = employeeRepository;
        this.DashboardRepository = dashboardRepository;
    }

    public void SaveChanges()
    {
        _dataContext.SaveChanges();
    }
}

Это, похоже, решает обе проблемы. Теперь в ведро данных есть только один метод SaveChanges, и вы вводите только один объект - ведро данных. Затем вы получаете доступ ко всем хранилищам как свойствам. Ведро данных было бы немного грязным, поскольку оно принимало бы ВСЕ (легко 50 или более) наших репозиториев в его конструкторе.

Процесс добавления нового репозитория теперь включает в себя: создание интерфейса, создание репозитория, отображение интерфейса и репозитория в Ninject и добавление свойства в ведро данных и его заполнение.

Я подумал об альтернативе этому, чтобы устранить шаг сверху.

public class DataBucket
{
    private DataContext _dataContext;

    public IReportsRepository ReportRepository { get; set; }
    public IEmployeeRepository EmployeeRepository { get; set; }
    public IDashboardRepository DashboardRepository { get; set; }

    public DataBucket(DataContext dataContext)
    {
        _dataContext = dataContext;
        this.ReportRepository = new ReportsRepository(dataContext);
        this.EmployeeRepository = new EmployeeRepository(dataContext);
        this.DashboardRepository = new DashboardRepository(dataContext);
    }

    public void SaveChanges()
    {
        _dataContext.SaveChanges();
    }
}

Это довольно сильно устраняет все сопоставления репозитория в Ninject, потому что все они созданы в веществе данных. Итак, теперь шаги по добавлению нового репозитория включают: создание интерфейса, создание репозитория, добавление свойства в ведро данных и создание экземпляра.

Можете ли вы, ребята, увидеть недостатки этой модели? На первый взгляд гораздо удобнее использовать наши хранилища таким образом. Это проблема, которая была рассмотрена раньше? Если да, то каков наиболее общий и/или наиболее эффективный подход к этой проблеме?

Спасибо:)

4b9b3361

Ответ 1

Я думаю, что вы абсолютно правы в использовании шаблона Unit of Work. Это не только мешает вам использовать метод SaveChanges в каждом репозитории, но и предоставляет вам хороший способ обработки транзакций из кода, а не в самой базе данных. Я включил метод Rollback с моим UOW, так что, если бы существовало исключение, я мог бы отменить любые изменения, которые уже сделали операция на моем DataContext.

Одна вещь, которую вы могли бы сделать, чтобы предотвратить странные проблемы с зависимостями, состояла бы в группировании связанных репозиториев на своей собственной Единице работы, а не с помощью одного большого DataBucket, который хранит каждый репозиторий, который у вас есть (если это было вашим намерением). Каждое UOW должно быть доступно только на том же уровне, что и в репозиториях, и другие репозитории, вероятно, не должны зависеть от других UOW (ваши репозитории не должны использовать другие репозитории).

Если бы вы хотели стать еще более крупным пуристом шаблона, вы могли бы также структурировать ваши UOWs для представления именно этого, одного Единицы работы. Вы определяете их для представления конкретной операции в вашем домене и предоставляете ей репозитории, необходимые для завершения этой операции. Отдельные репозитории могут существовать более чем на одном UOW, если имеет смысл использовать несколько операций в вашем домене.

Например, a PlaceCustomerOrderUnitOfWork может потребоваться CustomerRepository, OrderRepository, BillingRepository и a ShippingRepository

An CreateCustomerUnitOfWork может потребоваться только CustomerRepository. В любом случае, вы можете легко передать эту зависимость своим потребителям, более тонкие интерфейсы для вашего UOW могут помочь вам настроить тестирование и уменьшить усилия для создания макета.

Ответ 2

Во-первых, есть некоторые контроллеры, в которых нам нужно от 10 до 15 репозиториев на одном контроллере.

Поприветствуйте шаблон Abstract factory. Вместо того, чтобы регистрировать все репозитории в Ninject и вводить их контроллерам, регистрируйте только одну реализацию factory, которая сможет предоставить любой репозиторий, который вам нужен - вы даже можете создать их лениво, только если контроллер действительно нуждается в них. Затем введите контроллер factory.

Да, это также имеет некоторые недостатки - вы даете разрешение контроллеру получить какой-либо репозиторий. Это проблема для вас? Вы можете всегда создавать несколько заводов для некоторых подсистем, если вам нужно или просто выставить несколько интерфейсов factory для единой реализации. Он по-прежнему не охватывает все случаи, но лучше, чем передать 15 параметров конструктору. Btw. вы уверены, что эти контроллеры не должны быть разделены?

Примечание. Это не антивирус.

После выполнения работы с несколькими репозиториями нам нужно вызвать метод SaveChanges, но какой репозиторий мы должны назвать?

Поздороваться с шаблоном "Единица работы". Единица работы - это логическая транзакция в вашем приложении. Он сохраняет все изменения от логической транзакции вместе. Репозиторий не должен нести ответственность за сохраняющиеся изменения - единица работы должна быть. Кто-то сказал, что DbContext - это реализация шаблона репозитория. Это не. Это реализация шаблона Unit of Work, а DbSet - реализация шаблона репозитория.

Вам нужен центральный класс, содержащий экземпляр контекста. Контекст также будет передан в репозитории, потому что им нужно получить данные, но только центральный класс (единица работы) предложит сохранить изменения. Он также может обрабатывать транзакцию базы данных, если вам, например, необходимо изменить уровень изоляции.

Где должна работать единица работы? Это зависит от, где организована логическая операция. Если операция организована непосредственно в действиях контроллера, вам также необходимо иметь единицу работы в действии и вызвать SaveChanges после выполнения всех изменений.

Если вы не заботитесь о разделении проблем слишком много, вы можете объединить единицу работы и factory в один класс. Это подводит нас к вашему DataBucket.

Ответ 3

Понятие каждого репозитория, имеющего SaveChanges, является ошибочным, потому что его вызов сохраняет все. Изменить часть DataContext невозможно, вы всегда сохраняете все. Таким образом, центральный класс DataContext - хорошая идея.

Кроме того, у вас может быть репозиторий с универсальными методами, которые могут работать с любым типом объекта (GetTable<T>, Query<T>,...). Это избавит вас от всех этих классов и объединит их в один (в основном, остается только DataBucket).

Возможно, вам даже не нужны репозитории: вы можете ввести сам DataContext! DataContext сам по себе является репозиторием и полноценным уровнем доступа к данным. Он не поддался насмешкам.

Если вы можете это сделать, это зависит от того, что вам нужно, "предоставить репозиторий".


Единственная проблема с тем, что класс DataBucket будет состоять в том, что этот класс должен знать обо всех сущностях и всех репозиториях. Таким образом, он сидит очень высоко в стеке программ (вверху). В то же время он используется в основном так, чтобы он сидел внизу. Подождите! Это цикл для всей кодовой базы.

Это означает, что все, используя его и все, что им используется, должно находиться в одной и той же сборке.

Ответ 4

В прошлом я создавал контейнеры для инъекций (я использовал Unity) и регистрировал контекст данных с помощью ContainerControlledLifetime. Таким образом, когда репозитории создаются, они всегда имеют один и тот же контекст данных, введенный в них. Затем я перехожу к этому контексту данных, и когда моя "Единица работы" завершена, я вызываю DataContext.SaveChanges(), удаляя все изменения из базы данных.

У этого есть некоторые другие преимущества, такие как (с EF) некоторое местное кэширование, так что если несколько репозиториев должны получить один и тот же объект, только первый репозиторий фактически вызывает обратную связь базы данных.

Это также хороший способ "доработать" изменения и убедиться, что они выполняются как одна атомная транзакция.