存储库和工作单元模式——如何保存更改

本文关键字:保存更改 模式 单元 工作 存储 | 更新日期: 2023-09-27 17:52:15

我正在努力理解存储库和工作单元模式之间的关系,尽管这类问题被问了很多次。从本质上讲,我仍然不明白哪一部分将保存/提交数据更改-存储库还是工作单元?

因为我看到的每个例子都涉及到将这些与数据库/或映射器结合使用,让我们做一个更有趣的例子-让数据在数据文件中持久化到文件系统;根据模式,我应该能够做到这一点,因为数据的去向是无关紧要的。

对于基本实体:

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();

记住所有的数据都将被持久化到文件中,那么实际添加/更新/删除这些数据的逻辑在哪里呢?

  1. 是否通过Add(), Update()Remove()方法进入存储库?对我来说,在一个地方读取/写入文件的所有代码听起来是合乎逻辑的,但是IUnitOfWork接口的意义是什么?
  2. 它是否在IUnitOfWork实现中,对于这种情况也将负责数据更改跟踪?对我来说,这意味着存储库可以读取文件,而工作单元必须写入文件,但是逻辑现在被分成两个地方。

存储库和工作单元模式——如何保存更改

Repository可以在没有工作单元的情况下工作,所以它也可以有Save方法。

public interface IRepository<T>
{
     T Get(int id);
     void Add(T entity);
     void Update(T entity);
     void Remove(T entity);
     void Save();
}
当您有多个存储库(可能有不同的数据上下文)时使用

工作单元。它一直跟踪事务中的所有更改,直到您调用Commit方法将所有更改持久化到数据库(在本例中为文件)。

所以,当你在Repository中调用Add/Update/Remove时,它只会改变实体的状态,将其标记为Added, Removed或Dirty…当您调用Commit时,Unit Of Work将遍历存储库并执行实际的持久化:

  • 如果存储库共享相同的数据上下文,则工作单元可以直接与数据上下文一起工作以获得更高的性能(在这种情况下打开和写入文件)。

  • 如果存储库具有不同的数据上下文(不同的数据库或文件),则工作单元将在同一个TransactionScope中调用每个存储库的Save方法。

我实际上对这个很陌生,但是没有人更明智地发表过:

crud的代码如您所期望的那样发生在存储库中,但是当Account。调用Add(例如)时,所发生的只是将Account对象添加到稍后要添加的事物列表中(跟踪更改)。

当调用unitOfWork.Save()时,存储库被允许查看它们的更改列表或UoW的更改列表(取决于您如何选择实现模式)并适当地采取行动-因此在您的情况下,可能有一个List<Account> NewItemsToAdd字段一直在跟踪基于对. add()的调用添加的内容。当UoW表示可以保存时,存储库实际上可以将新项持久化为文件,如果成功,则清除要添加的新项列表。

我想UoW的要点是跨多个存储库管理Save(这些存储库组合起来就是我们想要提交的逻辑工作单元)。

我真的很喜欢你的问题。我在实体框架中使用了Uow/Repository模式,它显示了EF实际做了多少(上下文如何跟踪更改,直到SaveChanges最终被调用)。要在示例中实现此设计模式,您需要编写相当多的代码来管理更改。

唉,事情很棘手。想象一下这个场景:一个repo将一些东西保存在数据库中,另一个保存在文件系统中,第三个保存在云上。你是怎么做到的?

作为指导原则,UoW应该提交东西,但是在上面的场景中,commit只是一个错觉,因为你有3个非常不同的东西要更新。输入最终一致性,这意味着所有的东西最终都是一致的(不是在使用RDBMS的同一时刻)。

该UoW在消息驱动的体系结构中称为Saga。关键是每个传奇位可以在不同的时间执行。只有当所有3个存储库都更新时,Saga才完成。

你不会经常看到这种方法,因为大多数时候你会使用RDBMS,但是现在NoSql非常普遍,所以经典的事务方法非常有限。

因此,如果您确定只使用一个关系型数据库,请使用UoW的事务并将相关连接传递给每个存储库。最后,UoW将调用commit.

如果您知道或期望您可能必须使用多个rdbms或不支持事务的存储,请尝试熟悉消息驱动的体系结构和传奇概念。

如果您想自己动手的话,使用文件系统会使事情变得相当复杂。

仅在UoW提交时写入

你要做的是让存储库在UnitOfWork中排队等待所有IO操作。比如:

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));
    }
}

通过这样做,您可以同时将所有更改写入文件。

在DB存储库中不需要这样做的原因是事务支持是内置在DB中的。因此,你可以告诉DB直接启动一个事务,然后用它来创建一个工作单元。

将是复杂的,因为您必须能够回滚文件中的更改,并防止不同的线程/事务在并发事务期间访问相同的文件。

通常,存储库处理所有的读操作,而工作单元处理所有的写操作,但是可以肯定的是,您可以只使用这两者中的一个来处理所有的读操作和写操作(但如果只使用存储库模式,维护10个存储库会非常繁琐,更糟糕的是,可能会导致读写不一致被覆盖),混合使用两者的优点是易于跟踪状态变化,易于处理并发性和一致性问题。为了更好地理解,您可以参考链接:实体框架4.1的存储库模式和父/子关系和https://softwareengineering.stackexchange.com/questions/263502/unit-of-work-concurrency-how-is-it-handled