工作单位+存储库模式:商业交易概念的下降

Unit of WorkRepository Pattern是当今相当普遍的用法。 正如马丁·福勒( Martin Fowler) 所言 ,使用物UoW的目的是形成一个商业交易,同时UoW知道存储库实际上是如何工作的(持续的无知)。 我已经回顾了许多实现; 并忽略具体的细节(具体/抽象类,接口……),它们或多或less类似于下面的内容:

 public class RepositoryBase<T> { private UoW _uow; public RepositoryBase(UoW uow) // injecting UoW instance via constructor { _uow = uow; } public void Add(T entity) { // Add logic here } // +other CRUD methods } public class UoW { // Holding one repository per domain entity public RepositoryBase<Order> OrderRep { get; set; } public RepositoryBase<Customer> CustomerRep { get; set; } // +other repositories public void Commit() { // Psedudo code: For all the contained repositories do: store repository changes. } } 

现在我的问题:

UoW公开方法Commit来存储更改。 另外,由于每个存储库都有一个共享的UoW实例, UoW每个Repository都可以访问UoW上的方法Commit 。 通过一个存储库调用它,所有其他存储库也会存储它们的更改; 因此整个交易概念崩溃的结果是:

 class Repository<T> : RepositoryBase<T> { private UoW _uow; public void SomeMethod() { // some processing or data manipulations here _uow.Commit(); // makes other repositories also save their changes } } 

我认为这是不允许的。 考虑到UoW (业务事务)的目的, Commit方法应该仅暴露给启动业务事务的人员 ,例如业务层。 令我感到吃惊的是,我找不到任何文章解决这个问题。 在他们所有的Commit可以被任何正在被注入的回购调用。

PS:我知道我可以告诉我的开发人员不要在Repository调用Commit ,但可信的架构比可信任的开发人员更可靠!

我同意你的顾虑。 我更喜欢有一个工作单位,最外层的function是打开一个工作单元,决定是否提交或中止。 被调用的函数可以打开一个工作范围单元,如果有的话可以自动join到环境中,否则创build一个新的工作单元。

我使用的UnitOfWorkScope的实现受到TransactionScope如何工作的启发。 使用环境/范围的方法也消除了dependency injection的需要。

执行查询的方法如下所示:

 public static Entities.Car GetCar(int id) { using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Reading)) { return uow.DbContext.Cars.Single(c => c.CarId == id); } } 

写入的方法如下所示:

 using (var uow = new UnitOfWorkScope<CarsContext>(UnitOfWorkScopePurpose.Writing)) { Car c = SharedQueries.GetCar(carId); c.Color = "White"; uow.SaveChanges(); } 

请注意, uow.SaveChanges()调用只会对数据库进行实际保存(如果这是根(最))范围。 否则,它将被解释为“可以投票”,即根作用域将被允许保存更改。

UnitOfWorkScope的整个实现可在以下urlfind: http : //coding.abel.nu/2012/10/make-the-dbcontext-ambient-with-unitofworkscope/

让你的UoW的仓库成员。 不要让你的存储库“看到”你的UoW。 让UoW处理交易。

不要传入UnitOfWork ,传入具有所需方法的接口。 如果您想要的话,仍然可以在原始的具体UnitOfWork实现中实现该接口:

 public interface IDbContext { void Add<T>(T entity); } public interface IUnitOfWork { void Commit(); } public class UnitOfWork : IDbContext, IUnitOfWork { public void Add<T>(T entity); public void Commit(); } public class RepositoryBase<T> { private IDbContext _c; public RepositoryBase(IDbContext c) { _c = c; } public void Add(T entity) { _c.Add(entity) } } 

编辑

发布后,我有一个重新思考。 在UnitOfWork实现中暴露Add方法意味着它是两个模式的组合。

我在自己的代码中使用entity framework,在DbContext使用的DbContext被描述为“工作单元和存储库模式的组合”。

我认为把两者分开比较好,这意味着我需要两个DbContext包装,一个用于Unit Of Work位,一个用于Repository位。 我在RepositoryBase进行仓库封装。

关键的区别是我没有将UnitOfWork传递给存储库,我传递了DbContext 。 这意味着BaseRepository可以访问BaseRepository上的SaveChanges 。 由于意图是自定义存储库应该inheritanceBaseRepository ,他们也可以访问一个DbContext 。 因此,开发人员可能会在使用该DbContext的自定义存储库中添加代码。 所以我想我的“包装”是有点漏…

那么值得为DbContext创build另一个包装器,它可以传递给存储库构造器来closures这个包装器呢? 不确定是…

传递DbContext的示例:

实施知识库和工作单位

entity framework中的存储库和工作单元

John Papa的原始源代码

在.NET中,数据访问组件通常会自动login到环境事务。 因此, 事务内部的保存更改与事务的保存分离以保持更改

换句话说,如果你创build一个事务范围,你可以让开发人员保存尽可能多的。 直到事务提交,数据库的可观察状态才会被更新(当然,可观察到的取决于事务隔离级别)。

这显示了如何在c#中创build事务作用域:

 using (TransactionScope scope = new TransactionScope()) { // Your logic here. Save inside the transaction as much as you want. scope.Complete(); // <-- This will complete the transaction and make the changes permanent. } 

最近我也在研究这种devise模式,并且通过使用工作单元和通用存储库模式,我能够为存储库实现提取工作单元“保存更改”。 我的代码如下:

 public class GenericRepository<T> where T : class { private MyDatabase _Context; private DbSet<T> dbset; public GenericRepository(MyDatabase context) { _Context = context; dbSet = context.Set<T>(); } public T Get(int id) { return dbSet.Find(id); } public IEnumerable<T> GetAll() { return dbSet<T>.ToList(); } public IEnumerable<T> Where(Expression<Func<T>, bool>> predicate) { return dbSet.Where(predicate); } ... ... } 

基本上我们所做的就是传递数据上下文,并使用entity framework的dbSet方法来获取基本的Get,GetAll,Add,AddRange,Remove,RemoveRange和Where。

现在我们将创build一个通用接口来公开这些方法。

 public interface <IGenericRepository<T> where T : class { T Get(int id); IEnumerable<T> GetAll(); IEnumerabel<T> Where(Expression<Func<T, bool>> predicate); ... ... } 

现在我们要为entity framework中的每个实体创build一个接口,并从IGenericRepositoryinheritance,以便接口将期望在inheritance的存储库中实现方法签名。

例:

 public interface ITable1 : IGenericRepository<table1> { } 

你将遵循与你的所有实体相同的模式。 您还将在这些接口中添加特定于实体的任何function签名。 这将导致存储库需要实现GenericRepository方法和接口中定义的任何自定义方法。

对于知识库,我们将像这样实施它们。

 public class Table1Repository : GenericRepository<table1>, ITable1 { private MyDatabase _context; public Table1Repository(MyDatabase context) : base(context) { _context = context; } } 

在上面的示例库中,我创build了table1存储库,并inheritance了“table1”types的GenericRepository,然后从ITable1接口inheritance。 这将自动为我实现通用的dbSet方法,因此只允许我关注自定义存储库方法(如果有的话)。 当我将dbContext传递给构造函数时,我也必须将dbContext传递给基类Generic Repository。

现在,我将从这里开始创build工作单元库和接口。

 public interface IUnitOfWork { ITable1 table1 {get;} ... ... list all other repository interfaces here. void SaveChanges(); } public class UnitOfWork : IUnitOfWork { private readonly MyDatabase _context; public ITable1 Table1 {get; private set;} public UnitOfWork(MyDatabase context) { _context = context; // Initialize all of your repositories here Table1 = new Table1Repository(_context); ... ... } public void SaveChanges() { _context.SaveChanges(); } } 

我在自定义控制器上处理我的事务范围,使我的系统中的所有其他控制器都inheritance自己 这个控制器inheritance了默认的MVC控制器。

 public class DefaultController : Controller { protected IUnitOfWork UoW; protected override void OnActionExecuting(ActionExecutingContext filterContext) { UoW = new UnitOfWork(new MyDatabase()); } protected override void OnActionExecuted(ActionExecutedContext filterContext) { UoW.SaveChanges(); } } 

通过这种方式实现你的代码。 每次在动作开始时向服务器发出请求,都会创build一个新的UnitOfWork,并自动创build所有的存储库,并使其可以在控制器或类中的UoWvariables中访问。 这也将从您的存储库中删除您的SaveChanges(),并将其放置在UnitOfWork存储库中。 最后这个模式能够通过dependency injection在整个系统中只使用一个dbContext。

如果您担心单一上下文的父/子更新,则可以使用存储过程来更新,插入和删除函数,并为您的访问方法使用entity framework。

意识到这个问题已经有一段时间了,人们可能已经晚年死去,转到pipe理层等等,但是这里却是如此。

从数据库,事务控制器和两阶段提交协议中获取灵感,下列模式更改应该适用于您。

  1. 实现EAA书中Fowler's P中描述的工作单元界面,但是将资源库注入到每个UoW方法中。
  2. 将工作单元注入到每个存储库操作中。
  3. 每个存储库操作都会调用相应的UoW操作并注入自身。
  4. 在存储库中实现两个阶段提交方法CanCommit(),Commit()和Rollback()。
  5. 如果需要的话,在UoW上提交可以在每个存储库上运行Commit,或者它可以提交给数据存储本身。 它也可以实现一个2阶段提交,如果这是你想要的。

完成这些后,您可以根据您如何实现存储库和UoW来支持多种不同的configuration。 例如从没有事务的简单数据存储,单个RDBM,多个异构数据存储等。数据存储及其交互可以在存储库中或在UoW中,视情况需要。

 interface IEntity { int Id {get;set;} } interface IUnitOfWork() { void RegisterNew(IRepsitory repository, IEntity entity); void RegisterDirty(IRepository respository, IEntity entity); //etc. bool Commit(); bool Rollback(); } interface IRepository<T>() : where T : IEntity; { void Add(IEntity entity, IUnitOfWork uow); //etc. bool CanCommit(IUnitOfWork uow); void Commit(IUnitOfWork uow); void Rollback(IUnitOfWork uow); } 

无论数据库实现如何,用户代码始终是相同的,如下所示:

 // ... var uow = new MyUnitOfWork(); repo1.Add(entity1, uow); repo2.Add(entity2, uow); uow.Commit(); 

回到原来的post。 因为我们是将UoW注入每个repo操作的方法,所以UoW不需要存储在每个存储库中,这意味着Repository上的Commit()可以被删除,UoW上的Commit做实际的DB提交。

是的,这个问题是我关心的,这是我如何处理它。

首先,在我的理解领域模型不应该知道工作单位。 域模型由接口(或抽象类)组成,并不意味着事务存储的存在。 事实上,它根本不知道任何存储的存在。 因此,术语领域模型

工作单元出现在域模型实现层中。 我想这是我的术语,我的意思是通过合并数据访问层实现域模型接口的一个层。 通常,我使用ORM作为DAL,因此它内置了UoW(entity frameworkSaveChanges或SubmitChanges方法来提交挂起的更改)。 但是,那个属于DAL,并不需要任何发明者的魔力。

另一方面,你指的是你需要在域模型实现层中使用的UoW,因为你需要抽象掉“提交到DAL的变更”部分。 为此,我将与Anders Abel的解决scheme(recursionscropes)一起,因为这解决了两件事情需要一次性解决:

  • 如果聚合是范围的发起者,则需要支持将聚合保存为一个事务。
  • 如果聚合不是范围的发起者,但是它是其中的一部分,则需要支持将聚合保存为父事务的一部分。