Microsoft NLayerApp案例理论与实践 - 基础结构层(数据访问部分)

发布时间:2026/7/5 3:58:26
Microsoft NLayerApp案例理论与实践 - 基础结构层(数据访问部分) Unit Of WorkPoEAAUnit Of WorkUoW模式在企业应用架构中被广泛使用它能够将Domain Model中对象状态的变化收集起来并在适当的时候在同一数据库连接和事务处理上下文中一次性将对象的变更提交到数据中。在没有引入UoW之前你可以在每次增加、删除对象或者更改对象状态后直接调用数据库以保存对象的变化但这样做会导致应用程序对数据库这一外部技术架构的频繁访问严重影响了系统性能。这就好像我们打开Notepad进行文字编辑一样我们完全可以每输入一个字符就按下CtrlS保存一次但这样做非常耗时也没必要我们通常的做法可能是每完成一个段落的编辑输入字符、删除字符或者更改字符等再保存一次那么Notepad就会在我们编辑段落的时候跟踪段落及其中字符的变化情况最后一次性将这些变更写到硬盘上。从UoW的模式描述上看它有点像数据库事务Transaction因为它们都具有“提交”和“回滚”的操作。但从语义上讲它并不能等同于数据库事务。我觉得应该这样理解我们可以将UoW看成是一个事务对象但它不是数据库事务它的事务性体现在能够在一个原子操作中将对象一次性提交给持久化机制或者如果在提交过程中出现问题它还能将对象返回到提交前的状态。不仅如此UoW还具有跟踪领域对象变化的功能它能够跟踪某一个业务步骤范围内领域对象的变化情况正如上面的例子中每个段落的编辑就可以看成是一个业务步骤那么在这个业务步骤中编辑段落的过程中UoW会对领域对象进行跟踪而在业务步骤完成之时完成段落编辑之时UoW就会对跟踪到的变更做一次性提交。从上面的分析让我们大致了解到UoW与仓储一样本身应该是属于Domain Model的它的设计应该是技术无关的也就是常说的POCO或者IPOCO因为它跟踪的是Domain Model中领域对象的变化情况当然一个更好的设计应该是使用Separated InterfacePoEAA模式将UoW接口与仓储的接口一起设计在Domain Model中。从UoW的实现上来看NLayerApp采用了Entity Framework的一些特性并基于Entity Framework的模型利用T4自动化产生代码。目前我们不要去关心在NLayerApp中是如何使用T4产生这些代码的我们需要关心的是为什么需要产生这些代码。有关Visual Studio中的模型项目、Domain Specific LanguageDSL以及T4代码自动化生成我们在此将不作讨论。有兴趣的朋友可以参考我前面的文章《在Visual Studio 2010中使用Modeling Project定制DSL以及自动化代码生成》。以下是NLayerApp中与UoW相关的类关系图在了解NLayerApp的UoW执行机制之前首先让我们了解一下NLayerApp中与UoW相关的三个接口。IObjectWithChangeTracker接口该接口下只定义了一个ObjectChangeTracker的属性在NLayerApp中所有的实体都要实现IObjectWithChangeTracker接口以向外界主要是UoW和仓储提供ObjectChangeTracker实例。ObjectChangeTracker的主要功能就是记录当前实体中的状态变化。比如实体的当前状态、变更前所有属性的原始数据、向集合属性添加的所有对象、从集合属性中删除的所有对象等等。当仓储通过Unit Of Work来注册已变更的实体时Unit Of Work会使用ObjectChangeTracker所提供的信息来向Entity Framework进行变更注册。INotifyPropertyChanged接口NLayerApp的实体不仅实现了IObjectWithChangeTracker接口同时还实现了INotifyPropertyChanged接口。实现这个接口的主要目的就是为了在实体的某个属性发生变化时能及时地将这种变化记录在ObjectChangeTracker中。因此只要客户程序通过实体的属性来改变实体的状态时实体本身就会将状态变化记录到ObjectChangeTracker中。IRepository接口IRepository接口是定义在Domain Model层的接口之所以在此提及是因为对象的持久化过程是通过仓储完成的而持久化又离不开UoW。在NLayerApp中IRepository接口有一个IUnitOfWork的属性因此所有的仓储都必须实现这个属性以便Repository能够在UoW中记录对象的变更信息。从NLayerApp的源代码可以看到其实仓储本身并不负责将实体保存到数据库的这一具体任务它只是通过IObjectWithChangeTracker接口将需要保存的对象设置为相应的状态并向UoW注册对象变更剩下的与数据库打交道的任务则是由UoW完成的通过这些信息我们可以了解到NLayerApp中的实体都是各自管理自己的变更记录称之为“自跟踪实体”Self-Tracking EntitiesSTE。其实从DDD的角度来看STE并不是一个很好的设计因为它给Domain Model带来了太多技术关注点。例如在实现STE的时候当你向Customer添加一个Order时你需要首先判断Customer的ObjectChangeTracker中是否已经将该Order标记为“删除”状态了如果是这样的话那么你需要将这个Order从ObjectChangeTracker的“删除”列表中移去。类似这样的业务逻辑本不应该放在Domain Model中。此外NLayerApp为了迎合Entity Framework的需求所实现的STE也并非纯粹的与技术无关的。UoW的实现也是如此比如在上面的类图中我们可以很明显地看到MainModuleUnitOfWork是ObjectContext的子类。现在我们将思路串联起来以修改Customer为例从整个架构服务端的最上层Distributed Service层开始看看Unit Of Work与仓储是如何协作的。1、DistributedServices.MainModule项目MainModuleService类通过使用位于应用层的CustomerManagementService实现Customer信息的变更12345678910111213141516publicvoidChangeCustomer(Customer customer){try{//Resolve root dependency and perform operationICustomerManagementService customerService IoCFactory.Instance.CurrentContainer.ResolveICustomerManagementService();customerService.ChangeCustomer(customer);}catch(ArgumentNullException ex){// ......}}上述代码通过IoCFactory从IoC容器中获得ICustomerManagementService的具体实现有关NLayerApp中IoC容器的实现请参考前一篇文章。2、Application.MainModule项目CustomerManagementService类实现了ICustomerManagementService接口同时实现了ChangeCustomer方法。在该方法中首先通过CustomerRepository的UnitOfWork属性获得UoW然后调用仓储的Modify方法以将要更改的Customer实体注册到UoW中同时改变了Customer实体的状态。最后使用UoW的CommitAndRefreshChanges方法将变更的实体对象提交到数据库123456789101112publicvoidChangeCustomer(Customer customer){if(customer (Customer)null)thrownewArgumentNullException(customer);IUnitOfWork unitOfWork _customerRepository.UnitOfWorkasIUnitOfWork;_customerRepository.Modify(customer);unitOfWork.CommitAndRefreshChanges();}值得一提的是在CustomerManagementService中CustomerRepository以构造器注入的方式获得实例化的12345678910111213141516171819/// summary/// Create new instance/// /summary/// param namecustomerRepositoryCustomer repository dependency,/// intented to be resolved with dependency injection/param/// param namecountryRepositoryCountry repository dependency,/// intended to be resolved with dependency injection/parampublicCustomerManagementService(ICustomerRepository customerRepository,ICountryRepository countryRepository){if(customerRepository (ICustomerRepository)null)thrownewArgumentNullException(customerRepository);if(countryRepository (ICountryRepository)null)thrownewArgumentNullException(countryRepository);_customerRepository customerRepository;_countryRepository countryRepository;}3、Infrastructure.Data.Core项目Repository类的Modify方法首先将当前状态不是Deleted的实体设置为“Modified”同时在UoW中通过RegisterChanges调用以向UoW注册该实体12345678910111213141516171819202122publicvirtualvoidModify(TEntity item){//check argumentsif(item (TEntity)null)thrownewArgumentNullException(item, Resources.Messages.exception_ItemArgumentIsNull);//Set modifed state if change tracker is enabled and state is not deletedif(item.ChangeTracker !null((item.ChangeTracker.State ObjectState.Deleted) ! ObjectState.Deleted)){item.MarkAsModified();}//apply changes for item object_CurrentUoW.RegisterChanges(item);_TraceManager.TraceInfo(string.Format(CultureInfo.InvariantCulture,Resources.Messages.trace_AppliedChangedItemRepository,typeof(TEntity).Name));}4、Infrastructure.Data.MainModule项目MainModuleUnitOfWork类的RegisterChanges方法简单地利用Entity Framework所提供的机制向Entity Framework注册对象状态变更。这是Entity Framework技术实现的细节内容我们在此也不去深入分析其中的实现方式了12345publicvoidRegisterChangesTEntity(TEntity item)whereTEntity :class, IObjectWithChangeTracker{this.CreateObjectSetTEntity().ApplyChanges(item);}5、Infrastructure.Data.MainModule项目MainModuleUnitOfWork类的CommitAndRefreshChanges方法通过Entity Framework将变更提交到数据库同时将实体对象的状态设置为“未更改”1234567891011121314151617181920212223publicvoidCommitAndRefreshChanges(){try{//Default option is DetectChangesBeforeSavebase.SaveChanges();//accept all changes in STE entities attached in contextIEnumerableIObjectWithChangeTracker steEntities (fromentryinthis.ObjectStateManager.GetObjectStateEntries(~EntityState.Detached)whereentry.Entity !null(entry.EntityasIObjectWithChangeTracker !null)selectentry.EntityasIObjectWithChangeTracker);steEntities.ToList().ForEach(ste ste.MarkAsUnchanged());}catch(OptimisticConcurrencyException ex){//......}}整个执行过程我们可以使用下面的序列图来表示