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

Модели хранилища и единицы работы - Как сохранить изменения

Я изо всех сил пытаюсь понять отношения между репозиториями и модулями работы, несмотря на такой вопрос, который задают так много раз. По сути, я до сих пор не понимаю, какая часть будет сохранять/фиксировать изменения данных - репозиторий или блок работы?

Так как каждый пример, который я видел, связан с их использованием в сочетании с mapper/OR mapper, сделайте более интересный пример - сохраняйте данные в файловой системе в файлах данных; в соответствии с шаблонами я должен быть в состоянии сделать это, потому что, когда данные идут, не имеет значения.

Итак, для базового объекта:

public class Account
{
    public int Id { get; set; }
    public string Name { get; set; }
}

Я предполагаю, что будут использованы следующие интерфейсы:

public interface IAccountRepository
{
     Account Get(int id);
     void Add(Account account);
     void Update(Account account);
     void Remove(Account account);
}

public interface IUnitOfWork
{
    void Save();
}

И я думаю, что с точки зрения использования это будет выглядеть так:

IUnitOfWork unitOfWork = // Create concrete implementation here
IAccountRepository repository = // Create concrete implementation here

// Add a new account
Account account = new Account() { Name = "Test" };
repository.Add(account);

// Commit changes
unitOfWork.Save();

Принимая во внимание, что все данные будут сохраняться в файлах, где логика переходит к фактическому добавлению/обновлению/удалению этих данных?

  • Заходит ли он в репозиторий с помощью методов Add(), Update() и Remove()? Для меня логично иметь весь код, который читает/записывает файлы в одном месте, но тогда какая точка интерфейса IUnitOfWork?
  • Выполняется ли это в реализации IUnitOfWork, которая для этого сценария также будет отвечать за отслеживание изменений данных? Для меня это предполагает, что репозиторий может читать файлы, а блок работы должен записывать файлы, но теперь логика разделяется на два места.
4b9b3361

Ответ 1

Репозиторий может работать без Unit of Work, поэтому он также может иметь метод Save.

public interface IRepository<T>
{
     T Get(int id);
     void Add(T entity);
     void Update(T entity);
     void Remove(T entity);
     void Save();
}

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

Итак, когда вы вызываете Добавить/Обновить/Удалить в репозитории, он только изменяет статус объекта, помечает его как добавленный, удаленный или грязный... Когда вы вызываете Commit, Unit Of Work будет проходить через репозитории и выполнять фактическое сохранение:

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

  • Если у репозиториев есть другой контекст данных (разные базы данных или файлы), модуль работы вызовет каждый метод сохранения репозитория в том же TransactionScope.

Ответ 2

Я на самом деле совершенно новичок в этом, но как никому не мудрее отправил сообщение:

Код, который CRUD происходит в репозиториях, как и следовало ожидать, но когда вызывается Account.Add(например), все, что происходит, - это то, что объект Account добавлен в список вещей, которые будут добавлены позже (изменение отслеживается).

Когда unitOfWork.Save() называется репозиториям, разрешено просматривать их список изменений или список UoW того, что изменилось (в зависимости от того, как вы выбираете реализацию шаблона) и действовать соответствующим образом - поэтому в вашем case может быть поле List<Account> NewItemsToAdd, которое отслеживает, что добавлять на основе вызовов .Add(). Когда UoW говорит, что это нормально для сохранения, репозиторий может фактически сохранить новые элементы в виде файлов и в случае успешного удаления списка добавляемых новых элементов.

AFAIK точкой UoW является управление Save в нескольких хранилищах (которые объединены в логическую единицу работы, которую мы хотим зафиксировать).

Мне очень нравится ваш вопрос. Я использовал шаблон Uow/Repository с Entity Framework, и он показывает, сколько EF фактически делает (как контекст отслеживает изменения до тех пор, пока наконец не будет вызван SaveChanges). Чтобы реализовать этот шаблон проектирования в вашем примере, вам нужно написать довольно немного кода для управления изменениями.

Ответ 3

Эх, все сложно. Представьте себе этот сценарий: одно репо сохраняет что-то в db, другое - в файловой системе, а третье - в облаке. Как вы это совершаете?

В качестве ориентира UoW должен совершать вещи, однако в приведенном выше сценарии Commit - всего лишь иллюзия, поскольку у вас есть 3 очень разных вещи для обновления. Введите конечную согласованность, а это означает, что все вещи будут согласованы в конечном итоге (не в тот же момент, когда вы используете с РСУБД).

Этот UoW называется сагой в управляемой сообщениями архитектуре. Дело в том, что каждый бит саги может выполняться в разное время. Saga завершается только при обновлении всех 3 репозиториев.

Вы не видите такой подход так часто, потому что большую часть времени вы будете работать с РСУБД, но в настоящее время NoSql довольно распространен, поэтому классический транзакционный подход очень ограничен.

Итак, если вы уверены, что работаете только с ONE rdbms, используйте транзакцию с UoW и передайте связанное соединение с каждым репозиторием. В конце UoW вызовет commit.

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

Ответ 4

Использование файловой системы может усложнить ситуацию, если вы хотите сделать это самостоятельно.

Только писать, когда UoW зафиксирован.

Что вам нужно сделать, так это позволить репозиториям устанавливать все операции ввода-вывода в UnitOfWork. Что-то вроде:

public class UserFileRepository : IUserRepository
{
    public UserFileRepository(IUnitOfWork unitOfWork)
    {
        _enquableUow = unitOfWork as IEnquableUnitOfWork;
        if (_enquableUow == null) throw new NotSupportedException("This repository only works with IEnquableUnitOfWork implementations.");

    }

    public void Add(User user)
    {
        _uow.Append(() => AppendToFile(user));
    }

    public void Uppate(User user)
    {
        _uow.Append(() => ReplaceInFile(user));
    }
}

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

Причина, по которой вам не нужно делать это с репозиториями БД, заключается в том, что поддержка транзакций встроена в БД. Следовательно, вы можете сообщить БД, чтобы начать транзакцию напрямую, а затем просто использовать ее для подделки единицы работы.

Поддержка транзакций

Будет сложным, поскольку вы должны иметь возможность откатывать изменения в файлах, а также препятствовать доступу различных потоков/транзакций к тем же файлам во время одновременных транзакций.

Ответ 5

обычно, репозитории обрабатывают все чтения, а блок-работа обрабатывает все записи, но, безусловно, вы можете обрабатывать все чтения и записи, используя только одну из этих двух (но если использовать шаблон репозитория, будет очень утомительно поддерживать, возможно, 10 репозиториев, что еще хуже, может привести к тому, что будут несовместимы чтения и записи) Преимущество использования смеси заключается в простоте отслеживания изменений состояния и простоты обработки concurrency и последовательных проблем. для лучшего понимания вы можете ссылаться на ссылки: шаблон хранилища с инфраструктурой Entity Framework 4.1 и отношениями родителя/ребенка а также https://softwareengineering.stackexchange.com/info/263502/unit-of-work-concurrency-how-is-it-handled