C# 工作单位+;存储库模式:业务事务概念的衰落
将C# 工作单位+;存储库模式:业务事务概念的衰落,c#,java,architecture,repository-pattern,unit-of-work,C#,Java,Architecture,Repository Pattern,Unit Of Work,将工作单元和存储库模式相结合是当今应用相当广泛的一种模式。正如Martin Fowler所说,使用UoW的目的是形成业务事务,同时对存储库的实际工作方式一无所知(持续无知)。我已经回顾了许多实现;忽略具体细节(具体/抽象类、接口等),它们或多或少类似于以下内容: public class RepositoryBase<T> { private UoW _uow; public RepositoryBase(UoW uow) // injecting UoW insta
工作单元
和存储库模式
相结合是当今应用相当广泛的一种模式。正如Martin Fowler所说,使用UoW
的目的是形成业务事务,同时对存储库的实际工作方式一无所知(持续无知)。我已经回顾了许多实现;忽略具体细节(具体/抽象类、接口等),它们或多或少类似于以下内容:
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
应仅向启动业务事务的人公开,例如业务层。令我惊讶的是,我找不到任何关于这个问题的文章。在所有这些协议中,Commit
可以被任何正在注入的回购协议调用
PS:我知道我可以告诉我的开发人员不要在
存储库中调用Commit
,但是受信任的体系结构比受信任的开发人员更可靠 在.NET中,数据访问组件通常会自动登记到环境事务中。因此,以事务内方式保存更改与通过提交事务来保存更改是分开的
换言之,如果您创建一个事务范围,您可以让开发人员尽可能多地节省。直到事务被提交,数据库的可观察状态才会被更新(好的,什么是可观察的取决于事务隔离级别)
这显示了如何在c#中创建事务作用域:
使用(TransactionScope范围=新TransactionScope())
{
//这里是您的逻辑。在事务内部保存您想要的内容。
scope.Complete();//我同意您的担忧。我更喜欢使用环境工作单元,其中打开工作单元的最外层函数是决定是否提交或中止的函数。调用的函数可以打开一个工作单元作用域,该工作单元作用域在有环境UoW时自动登记,如果没有,则创建一个新的环境UoW
我使用的UnitOfWorkScope
的实现很大程度上受到了TransactionScope
工作原理的启发。使用环境/范围方法也消除了依赖注入的需要
执行查询的方法如下所示:
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();
}
// ...
var uow = new MyUnitOfWork();
repo1.Add(entity1, uow);
repo2.Add(entity2, uow);
uow.Commit();
publicstaticenties.cargetcar(int-id)
{
使用(var uow=新的UnitOfWorkScope(UnitOfWorkScopePurpose.Reading))
{
返回uow.DbContext.Cars.Single(c=>c.CarId==id);
}
}
写入的方法如下所示:
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();
}
// ...
var uow = new MyUnitOfWork();
repo1.Add(entity1, uow);
repo2.Add(entity2, uow);
uow.Commit();
使用(var-uow=newunitofworkscope(UnitOfWorkScopePurpose.Writing))
{
carc=sharedquerys.GetCar(carId);
c、 Color=“白色”;
uow.SaveChanges();
}
请注意,uow.SaveChanges()
调用只会在根(otermost)作用域的情况下实际保存到数据库。否则,将被解释为允许根作用域保存更改的“同意投票”
UnitOfWorkScope
的整个实现可在以下位置获得:不要传入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)
}
}
公共接口IDbContext
{
无效添加(T实体);
}
公共接口工作单元
{
无效提交();
}
公共类UnitOfWork:IDbContext,IUnitOfWork
{
公共无效添加(T实体);
公共无效提交();
}
公共类RepositoryBase
{
私人环境(c);
公共存储库(IDBC)
{
_c=c;
}
公共无效添加(T实体)
{
_c、 添加(实体)
}
}
编辑
发布后,我重新思考了一下。在UnitOfWork
实现中公开Add方法意味着它是这两种模式的组合
我在自己的代码中使用实体框架,其中使用的框架被描述为“工作单元和存储库模式的组合”
我认为最好将两者分开,这意味着我需要在DbContext
周围使用两个包装器,一个用于工作单元位,另一个用于存储库位。我在RepositoryBase
中进行存储库包装
关键的区别在于,我没有将工作单元
传递给存储库,而是传递了DbContext
。这意味着BaseRepository
可以访问DbContext
上的SaveChanges
。由于目的是自定义存储库应该继承BaseRepository
,因此他们也可以访问DbContext
。因此,开发人员可能会在使用该DbContext
的自定义存储库中添加代码。所以我想我的“包装器”有点泄漏
那么,是否值得为DbContext
创建另一个包装器,该包装器可以传递给存储库构造函数以关闭它呢?不确定它是否
传递DbContext的示例:
是的,这个问题是我关心的,下面是我如何处理它的
首先,在我的理解中,域模型不应该知道工作单元。域模型由接口(或抽象类)组成,并不意味着事务存储的存在。事实上,它根本不知道任何存储的存在。因此,术语域模型
工作单元存在于域模型实现层中。我想这是我的术语,我指的是通过合并数据访问层来实现域模型接口的层。通常,我使用ORM作为DAL,因此它带有内置UoW(实体框架SaveChanges或SubmitChanges方法来提交挂起的更改)。但是,该方法属于DAL
public interface ITable1 : IGenericRepository<table1>
{
}
public class Table1Repository : GenericRepository<table1>, ITable1
{
private MyDatabase _context;
public Table1Repository(MyDatabase context) : base(context)
{
_context = context;
}
}
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();
}
}
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();
}
}
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();
public class CustomerController : Controller
{
private readonly CustomerContext context; // injected
[HttpPost]
public IActionResult Update(CustomerUpdateDetails viewmodel)
{
// [Repository] acting like an in-memory domain object collection
var person = context.Person.Find(viewmodel.Id);
// [UnitOfWork] keeps track of everything you do during a business transaction
person.Name = viewmodel.NewName;
person.AnotherComplexOperationWithBusinessRequirements();
// [UnitOfWork] figures out everything that needs to be done to alter the database
context.SaveChanges();
}
}
public class PersonRepository
{
private readonly PersonDbContext context;
public Person GetClosestTo(GeoCoordinate location) {} // redacted
}
public class PersonController
{
private readonly PersonRepository repository;
private readonly PersonDbContext context; // requires to Equals repository.context
public IActionResult Action()
{
var person = repository.GetClosestTo(new GeoCoordinate());
person.DoSomething();
context.SaveChanges();
// repository.SaveChanges(); would save the injection of the DbContext
}
}
public class PersonEntity
{
[Key]
public int Id { get; set; }
public string Name { get; set; }
public bool IsValid { get; set; }
public ICollection<AddressEntity> Addresses { get; set; }
}
public class AddressEntity
{
[Key]
public int Id { get; set; }
public string Value { get; set; }
public DateTime Since { get; set; }
public PersonEntity Person { get; set; }
}
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public string CurrentAddressValue { get; private set; }
}
public class Person
{
public void EnsureEnrollable(IPersonRepository repository)
{
if(!repository.IsEnrollable(this))
{
throw new BusinessException<PersonError>(PersonError.CannotEnroll);
}
}
}
public class PersonRepository : IPersonRepository
{
private readonly PersonDbContext context;
public IEnumerable<Person> GetAll()
{
return context.Persons.AsNoTracking()
.Where(person => person.Active)
.ProjectTo<Person>().ToList();
}
public Person Enroll(Person person)
{
person.EnsureEnrollable(this);
context.Persons.Find(person.Id).Active = true;
context.SaveChanges(); // UPDATE statement
return person;
}
public bool IsEnrollable(Person person)
{
return context.Persons.Any(entity => entity.Id == person.Id && !entity.Active);
}
}