我很难理解存储库和工作单元模式之间的关系,尽管这样的问题被多次询问。基本上我仍然不明白哪个部分会保存/提交数据更改 - 存储库或工作单元?
由于我看到的每个例子都与数据库/ OR映射器结合使用,所以让我们做一个更有趣的例子 - 让数据保存在数据文件中的文件系统中;根据模式,我应该能够做到这一点,因为数据的来源无关紧要。
所以对于一个基本实体:
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
实现,对于此方案,它也将负责数据更改跟踪?对我来说,这表明存储库可以读取文件,而工作单元必须写入文件,但逻辑现在被分成两个位置。答案 0 :(得分:25)
存储库可以在没有工作单元的情况下工作,因此它也可以使用Save方法。
public interface IRepository<T>
{
T Get(int id);
void Add(T entity);
void Update(T entity);
void Remove(T entity);
void Save();
}
当您有多个存储库(可能具有不同的数据上下文)时,将使用工作单元。它会跟踪事务中的所有更改,直到您调用Commit方法将所有更改保留到数据库(在本例中为文件)。
因此,当您在存储库中调用添加/更新/删除时,它只会更改实体的状态,将其标记为已添加,已移除或已丢弃...当您调用提交时,单位Of Work将遍历存储库并执行实际持久性:
如果存储库共享相同的数据上下文,则工作单元可以直接使用数据上下文以获得更高的性能(在这种情况下,打开和写入文件)。
如果存储库具有不同的数据上下文(不同的数据库或文件),则工作单元将在同一个TransactionScope中调用每个存储库的Save方法。
答案 1 :(得分:6)
我实际上对此非常陌生,但没有更聪明的人发布:
CRUD在存储库中发生的代码正如您所期望的那样,但是当调用Account.Add(例如)时,所有发生的事情都是将Account对象添加到稍后要添加的事物列表中(更改)跟踪)。
当调用unitOfWork.Save()时,允许存储库查看已更改内容的列表或UoW已更改内容的列表(取决于您选择如何实现模式)并采取适当的操作 - 因此,在您的情况下,可能会有一个List<Account> NewItemsToAdd
字段,该字段一直在根据对.Add()的调用来跟踪要添加的内容。当UoW表示可以保存时,存储库实际上可以将新项目保存为文件,如果成功,则清除要添加的新项目列表。
AFAIK UoW的目的是管理跨多个存储库的保存(它们是我们想要提交的逻辑工作单元)。
我真的很喜欢你的问题。 我已经将Uow / Repository Pattern与Entity Framework一起使用,它显示了EF实际执行的程度(上下文如何跟踪更改,直到最后调用SaveChanges)。要在您的示例中实现此设计模式,您需要编写相当多的代码来管理更改。
答案 2 :(得分:4)
如果您想自己动手,使用文件系统会使事情变得复杂。
仅在提交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直接启动事务,然后使用它来伪造工作单元。
交易支持
复杂,因为您必须能够回滚文件中的更改,并防止不同的线程/事务在同时进行的事务中访问相同的文件。
答案 3 :(得分:3)
Ehe,事情很棘手。想象一下这个场景:一个repo在db中保存了一些东西,另一个在文件系统上保存了东西,在云上保存了第三个东西。你是怎么做到的?
作为一个指导原则,UoW应该提交一些东西,但是在上面的场景中,Commit只是一种幻觉,因为你有三个非常不同的东西需要更新。输入最终一致性,这意味着所有事情最终都会保持一致(与您在RDBMS中使用的时间不同)。
UoW在消息驱动架构中被称为Saga。关键是每个传奇位都可以在不同的时间执行。只有当所有3个存储库都更新时,Saga才会完成。
你不经常看到这种方法,因为大部分时间你都会使用RDBMS,但是现在NoSql很常见,所以经典的事务方法非常有限。
因此,如果您确定只使用一个rdbms,请使用UoW的事务并将关联的连接传递给每个存储库。最后,UoW将调用commit。
如果您知道或希望您可能需要使用多个rdbms或不支持事务的存储,请尝试熟悉消息驱动的体系结构和saga概念。
答案 4 :(得分:1)
通常,存储库处理所有读取,而工作单元处理所有写入,但是肯定只能使用这两个中的一个来处理所有读取和写入 (但是如果仅使用存储库模式,维护可能的10个存储库将会非常繁琐,更糟糕的是,可能会导致不一致的读取和写入被覆盖), 混合使用两者的优点是易于跟踪状态变化和易于处理并发性和一致性问题。 为了更好地理解,您可以参考链接:Repository Pattern with Entity Framework 4.1 and Parent/Child Relationships 和 https://softwareengineering.stackexchange.com/questions/263502/unit-of-work-concurrency-how-is-it-handled