37

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

由于我看到的每个示例都与将这些与数据库/或映射器结合使用有关,让我们做一个更有趣的示例 - 让数据以数据文件的形式保存到文件系统;根据模式,我应该能够做到这一点,因为数据的去向无关紧要。

所以对于一个基本实体:

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

5 回答 5

33

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 方法。

于 2013-01-10T18:03:15.840 回答
6

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

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

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

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

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

于 2013-01-10T17:33:40.390 回答
4

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

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

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

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

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

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

于 2013-01-11T08:25:16.377 回答
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));
    }
}

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

您不需要对数据库存储库执行此操作的原因是事务支持内置在数据库中。因此,您可以告诉数据库直接启动事务,然后使用它来伪造工作单元。

交易支持

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

于 2013-01-14T06:29:03.527 回答
1

通常,存储库处理所有读取,工作单元处理所有写入,但可以肯定的是,您可以只使用这两者之一来处理所有读取和写入(但如果只使用存储库模式,维护可能会非常繁琐10个存储库,更糟糕的是,可能导致不一致的读写被覆盖),混合使用两者的优点是易于跟踪状态更改以及易于处理并发和一致性问题。为了更好地理解,您可以参考链接:Repository Pattern with Entity Framework 4.1 and Parent/Child Relations and https://softwareengineering.stackexchange.com/questions/263502/unit-of-work-concurrency-how-is-it-handled

于 2015-05-05T10:05:33.087 回答