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

Posted

技术标签:

【中文标题】存储库和工作单元模式 - 如何保存更改【英文标题】:Repository and Unit of Work patterns - How to save changes 【发布时间】:2012-12-25 03:49:56 【问题描述】:

尽管这种问题被问了很多次,但我仍在努力理解存储库和工作单元模式之间的关系。基本上我仍然不明白哪个部分会保存/提交数据更改 - 存储库或工作单元?

由于我看到的每个示例都与将这些与数据库/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 实现中,对于这种情况,它也将负责数据更改跟踪?对我来说,这表明存储库可以读取文件,而工作单元必须写入文件,但逻辑现在分为两个地方。

【问题讨论】:

【参考方案1】:

Repository 可以在没有 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 方法将所有更改持久保存到数据库(在本例中为文件)。

所以,当您在 Repository 中调用 Add/Update/Remove 时,它只会更改实体的状态,将其标记为已添加、已删除或已脏...当您调用 Commit 时,Unit Of Work 将遍历存储库并执行实际持久性:

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

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

【讨论】:

我认为这个解释对我来说是最清楚的。阅读其他人所说的话,这表明如果我只有一个数据存储(例如数据库),那么我可能会不使用工作单元,并且为了简单起见只使用 IRepository.Save()。但是如果我确实有多个数据存储,那么工作单元将持有对每个存储库的引用,Commit() 将调用每个IRepository.Save() 方法,从而让每个存储库处理它自己的逻辑。听起来对吗? 它仍然需要一个关系数据库才能工作。每个 repo 可能有自己的事务,这没问题,但如果有一次你在云上保存并且应用程序崩溃,你最终会得到不一致的状态。毕竟,工作单元只是一个概念,问题在于实际的实现,这在很大程度上取决于您的设置。因此,对于 EF 来说,一个很好的解决方案对于 NoSql 来说会失败,而一个可靠的整体解决方案对于应用程序的需求来说可能过于复杂。【参考方案2】:

我实际上对此很陌生,但没有人更明智地发布:

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

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

据我所知,UoW 的重点是跨多个存储库管理保存(它们组合在一起是我们要提交的逻辑工作单元)。

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

【讨论】:

感谢您对喜欢这个问题的评论。关于这些模式的示例,我所见过的只是如何将它与 EF 或 NHibernate 之类的东西一起使用,而不是任何其他数据存储 - 毕竟它不必是数据库。开始看到工作单元更多的是一次管理多个数据存储/存储库,感谢您的解释 旧帖子,但如果有人感兴趣,可以使用 INotifyPropertyChanged 和 PropertyChanged.Fody 库来实现具有更改跟踪的自定义数据存储,该库会在构建时自动将通知代码注入到自动属性中。我已经将该工具用于类似的任务,它真的很强大。【参考方案3】:

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

作为准则,UoW 应该提交内容,但是在上述情况下,提交只是一种错觉,因为您有 3 个非常不同的内容要更新。输入最终一致性,这意味着所有事物最终都会保持一致(与您使用 RDBMS 时不同)。

UoW 在消息驱动架构中被称为 Saga。关键是每个 saga 位都可以在不同的时间执行。仅当所有 3 个存储库都更新时,Saga 才会完成。

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

所以,如果您确定您只使用一个 rdbms,请使用与 UoW 的事务并将关联的连接传递到每个存储库。最后,UoW 会调用 commit。

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

【讨论】:

哦,萨加斯......另一件事要阅读:) 感谢您的建议【参考方案4】:

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

仅在提交 UoW 时写入。

您需要做的是让存储库将所有 IO 操作排入 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 个存储库将非常繁琐,更糟糕的是,可能导致不一致的读写被覆盖), 混合使用两者的优点是易于跟踪状态更改以及易于处理并发和一致的问题。 为了更好地理解,您可以参考链接: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

【讨论】:

以上是关于存储库和工作单元模式 - 如何保存更改的主要内容,如果未能解决你的问题,请参考以下文章

实体框架使用 Codefirst、通用存储库、工作单元模式保存多对多关系

带有存储库和工作单元的 ASP.NET 标识

反应式存储库不保存对象

编辑完成时自动将单元格中的更改保存到对象?

如何使自定义单元格中的 UILabel 显示保存在 UITableView 中的更改

RestKit:创建一个对象而不保存它