关联的设计

关联本身不是一个模式,但它在领域建模的过程中非常重要,所以需要在探讨各种模式之前,先讨论一下对象之间的关联该如何设计。我觉得对象的关联的设计可以遵循如下的一些原则:

  1. 关联尽量少,对象之间的复杂的关联容易形成对象的关系网,这样对于我们理解和维护单个对象很不利,同时也很难划分对象与对象之间的边界;另外,同时减少关联有助于简化对象之间的遍历;
  2. 对多的关联也许在业务上是很自然的,通常我们会用一个集合来表示1对多的关系。但我们往往也需要考虑到性能问题,尤其是当集合内元素非常多的时候,此时往往需要通过单独查询来获取关联的集合信息;
  3. 关联尽量保持单向的关联;
  4. 在建立关联时,我们需要深入去挖掘是否存在关联的限制条件,如果存在,那么最好把这个限制条件加到这个关联上;往往这样的限制条件能将关联化繁为简,即可以将多对多简化为1对多,或将1对多简化为1对1;

实体(Entity)

实体就是领域中需要唯一标识的领域概念。因为我们有时需要区分是哪个实体。有两个实体,如果唯一标识不一样,那么即便实体的其他所有属性都一样,我们也认为他们两个不同的实体;因为实体有生命周期,实体从被创建后可能会被持久化到数据库,然后某个时候又会被取出来。所以,如果我们不为实体定义一种可以唯一区分的标识,那我们就无法区分到底是这个实体还是哪个实体。另外,不应该给实体定义太多的属性或行为,而应该寻找关联,发现其他一些实体或值对象,将属性或行为转移到其他关联的实体或值对象上。比如Customer实体,他有一些地址信息,由于地址信息是一个完整的有业务含义的概念,所以,我们可以定义一个Address对象,然后把Customer的地址相关的信息转移到Address对象上。如果没有Address对象,而把这些地址信息直接放在Customer对象上,并且如果对于一些其他的类似Address的信息也都直接放在Customer上,会导致Customer对象很混乱,结构不清晰,最终导致它难以维护和理解;

值对象(Value Object)

在领域中,并不是没一个事物都必须有一个唯一标识,也就是说我们不关心对象是哪个,而只关心对象是什么。就以上面的地址对象Address为例,如果有两个Customer的地址信息是一样的,我们就会认为这两个Customer的地址是同一个。也就是说只要地址信息一样,我们就认为是同一个地址。用程序的方式来表达就是,如果两个对象的所有的属性的值都相同我们会认为它们是同一个对象的话,那么我们就可以把这种对象设计为值对象。因此,值对象没有唯一标识,这是它和实体的最大不同。另外值对象在判断是否是同一个对象时是通过它们的所有属性是否相同,如果相同则认为是同一个值对象;而我们在区分是否是同一个实体时,只看实体的唯一标识是否相同,而不管实体的属性是否相同;值对象另外一个明显的特征是不可变,即所有属性都是只读的。因为属性是只读的,所以可以被安全的共享;当共享值对象时,一般有复制和共享两种做法,具体采用哪种做法还要根据实际情况而定;另外,我们应该给值对象设计的尽量简单,不要让它引用很多其他的对象,因为他只是一个值,就像int a = 3;那么”3”就是一个我们传统意义上所说的值,而值对象其实也可以和这里的”3”一样理解,也是一个值,只不过是用对象来表示。所以,当我们在C#语言中比较两个值对象是否相等时,会重写GetHashCode和Equals这两个方法,目的就是为了比较对象的值;值对象虽然是只读的,但是可以被整个替换掉。就像你把a的值修改为”4”(a = 4;)一样,直接把”3”这个值替换为”4”了。值对象也是一样,当你要修改Customer的Address对象引用时,不是通过Customer.Address.Street这样的方式来实现,因为值对象是只读的,它是一个完整的不可分割的整体。我们可以这样做:Customer.Address = new Address(…);

领域服务(Domain Service)

领域中的一些概念不太适合建模为对象,即归类到实体对象或值对象,因为它们本质上就是一些操作,一些动作,而不是事物。这些操作或动作往往会涉及到多个领域对象,并且需要协调这些领域对象共同完成这个操作或动作。如果强行将这些操作职责分配给任何一个对象,则被分配的对象就是承担一些不该承担的职责,从而会导致对象的职责不明确很混乱。但是基于类的面向对象语言规定任何属性或行为都必须放在对象里面。所以我们需要寻找一种新的模式来表示这种跨多个对象的操作,DDD认为服务是一个很自然的范式用来对应这种跨多个对象的操作,所以就有了领域服务这个模式。和领域对象不同,领域服务是以动词开头来命名的,比如资金转帐服务可以命名为MoneyTransferService。当然,你也可以把服务理解为一个对象,但这和一般意义上的对象有些区别。因为一般的领域对象都是有状态和行为的,而领域服务没有状态只有行为。需要强调的是领域服务是无状态的,它存在的意义就是协调领域对象共完成某个操作,所有的状态还是都保存在相应的领域对象中。我觉得模型(实体)与服务(场景)是对领域的一种划分,模型关注领域的个体行为,场景关注领域的群体行为,模型关注领域的静态结构,场景关注领域的动态功能。这也符合了现实中出现的各种现象,有动有静,有独立有协作。

领域服务还有一个很重要的功能就是可以避免领域逻辑泄露到应用层。因为如果没有领域服务,那么应用层会直接调用领域对象完成本该是属于领域服务该做的操作,这样一来,领域层可能会把一部分领域知识泄露到应用层。因为应用层需要了解每个领域对象的业务功能,具有哪些信息,以及它可能会与哪些其他领域对象交互,怎么交互等一系列领域知识。因此,引入领域服务可以有效的防治领域层的逻辑泄露到应用层。对于应用层来说,从可理解的角度来讲,通过调用领域服务提供的简单易懂但意义明确的接口肯定也要比直接操纵领域对象容易的多。这里似乎也看到了领域服务具有Façade的功能,呵呵。

说到领域服务,还需要提一下软件中一般有三种服务:应用层服务、领域服务、基础服务。

应用层服务

  1. 获取输入(如一个XML请求);
  2. 发送消息给领域层服务,要求其实现转帐的业务逻辑;
  3. 领域层服务处理成功,则调用基础层服务发送Email通知;

领域层服务

  1. 获取源帐号和目标帐号,分别通知源帐号和目标帐号进行扣除金额和增加金额的操作;
  2. 提供返回结果给应用层;

基础层服务

按照应用层的请求,发送Email通知;

所以,从上面的例子中可以清晰的看出,每种服务的职责;

聚合及聚合根(Aggregate,Aggregate Root)

聚合,它通过定义对象之间清晰的所属关系和边界来实现领域模型的内聚,并避免了错综复杂的难以维护的对象关系网的形成。聚合定义了一组具有内聚关系的相关对象的集合,我们把聚合看作是一个修改数据的单元。

聚合有以下一些特点:

  1. 每个聚合有一个根和一个边界,边界定义了一个聚合内部有哪些实体或值对象,根是聚合内的某个实体;
  2. 聚合内部的对象之间可以相互引用,但是聚合外部如果要访问聚合内部的对象时,必须通过聚合根开始导航,绝对不能绕过聚合根直接访问聚合内的对象,也就是说聚合根是外部可以保持 对它的引用的唯一元素;
  3. 聚合内除根以外的其他实体的唯一标识都是本地标识,也就是只要在聚合内部保持唯一即可,因为它们总是从属于这个聚合的;
  4. 聚合根负责与外部其他对象打交道并维护自己内部的业务规则;
  5. 基于聚合的以上概念,我们可以推论出从数据库查询时的单元也是以聚合为一个单元,也就是说我们不能直接查询聚合内部的某个非根的对象;
  6. 聚合内部的对象可以保持对其他聚合根的引用;
  7. 删除一个聚合根时必须同时删除该聚合内的所有相关对象,因为他们都同属于一个聚合,是一个完整的概念;

关于如何识别聚合以及聚合根的问题:

我觉得我们可以先从业务的角度深入思考,然后慢慢分析出有哪些对象是:

  1. 有独立存在的意义,即它是不依赖于其他对象的存在它才有意义的;
  2. 可以被独立访问的,还是必须通过某个其他对象导航得到的;

如何识别聚合?

我觉得这个需要从业务的角度深入分析哪些对象它们的关系是内聚的,即我们会把他们看成是一个整体来考虑的;然后这些对象我们就可以把它们放在一个聚合内。所谓关系是内聚的,是指这些对象之间必须保持一个固定规则,固定规则是指在数据变化时必须保持不变的一致性规则。当我们在修改一个聚合时,我们必须在事务级别确保整个聚合内的所有对象满足这个固定规则。作为一条建议,聚合尽量不要太大,否则即便能够做到在事务级别保持聚合的业务规则完整性,也可能会带来一定的性能问题。有分析报告显示,通常在大部分领域模型中,有70%的聚合通常只有一个实体,即聚合根,该实体内部没有包含其他实体,只包含一些值对象;另外30%的聚合中,基本上也只包含两到三个实体。这意味着大部分的聚合都只是一个实体,该实体同时也是聚合根。

如何识别聚合根?

如果一个聚合只有一个实体,那么这个实体就是聚合根;如果有多个实体,那么我们可以思考聚合内哪个对象有独立存在的意义并且可以和外部直接进行交互。

工厂(Factory)

DDD中的工厂也是一种体现封装思想的模式。DDD中引入工厂模式的原因是:有时创建一个领域对象是一件比较复杂的事情,不仅仅是简单的new操作。正如对象封装了内部实现一样(我们无需知道对象的内部实现就可以使用对象的行为),工厂则是用来封装创建一个复杂对象尤其是聚合时所需的知识,工厂的作用是将创建对象的细节隐藏起来。客户传递给工厂一些简单的参数,然后工厂可以在内部创建出一个复杂的领域对象然后返回给客户。领域模型中其他元素都不适合做这个事情,所以需要引入这个新的模式,工厂。工厂在创建一个复杂的领域对象时,通常会知道该满足什么业务规则(它知道先怎样实例化一个对象,然后在对这个对象做哪些初始化操作,这些知识就是创建对象的细节),如果传递进来的参数符合创建对象的业务规则,则可以顺利创建相应的对象;但是如果由于参数无效等原因不能创建出期望的对象时,应该抛出一个异常,以确保不会创建出一个错误的对象。当然我们也并不总是需要通过工厂来创建对象,事实上大部分情况下领域对象的创建都不会太复杂,所以我们只需要简单的使用构造函数创建对象就可以了。隐藏创建对象的好处是显而易见的,这样可以不会让领域层的业务逻辑泄露到应用层,同时也减轻了应用层的负担,它只需要简单的调用领域工厂创建出期望的对象即可。

仓储(Repository)

  1. 仓储被设计出来的目的是基于这个原因:领域模型中的对象自从被创建出来后不会一直留在内存中活动的,当它不活动时会被持久化到数据库中,然后当需要的时候我们会重建该对象;重建对象就是根据数据库中已存储的对象的状态重新创建对象的过程;所以,可见重建对象是一个和数据库打交道的过程。从更广义的角度来理解,我们经常会像集合一样从某个类似集合的地方根据某个条件获取一个或一些对象,往集合中添加对象或移除对象。也就是说,我们需要提供一种机制,可以提供类似集合的接口来帮助我们管理对象。仓储就是基于这样的思想被设计出来的;
  2. 仓储里面存放的对象一定是聚合,原因是之前提到的领域模型中是以聚合的概念去划分边界的;聚合是我们更新对象的一个边界,事实上我们把整个聚合看成是一个整体概念,要么一起被取出来,要么一起被删除。我们永远不会单独对某个聚合内的子对象进行单独查询或做更新操作。因此,我们只对聚合设计仓储。
  3. 仓储还有一个重要的特征就是分为仓储定义部分和仓储实现部分,在领域模型中我们定义仓储的接口,而在基础设施层实现具体的仓储。这样做的原因是:由于仓储背后的实现都是在和数据库打交道,但是我们又不希望客户(如应用层)把重点放在如何从数据库获取数据的问题上,因为这样做会导致客户(应用层)代码很混乱,很可能会因此而忽略了领域模型的存在。所以我们需要提供一个简单明了的接口,供客户使用,确保客户能以最简单的方式获取领域对象,从而可以让它专心的不会被什么数据访问代码打扰的情况下协调领域对象完成业务逻辑。这种通过接口来隔离封装变化的做法其实很常见。由于客户面对的是抽象的接口并不是具体的实现,所以我们可以随时替换仓储的真实实现,这很有助于我们做单元测试。
  4. 尽管仓储可以像集合一样在内存中管理对象,但是仓储一般不负责事务处理。一般事务处理会交给一个叫“工作单元(Unit Of Work)”的东西。关于工作单元的详细信息我在下面的讨论中会讲到。
  5. 另外,仓储在设计查询接口时,可能还会用到规格模式(Specification Pattern),我见过的最厉害的规格模式应该就是LINQ以及DLINQ查询了。一般我们会根据项目中查询的灵活度要求来选择适合的仓储查询接口设计。通常情况下只需要定义简单明了的具有固定查询参数的查询接口就可以了。只有是在查询条件是动态指定的情况下才可能需要用到Specification等模式。

设计领域模型的一般步骤

  1. 根据需求建立一个初步的领域模型,识别出一些明显的领域概念以及它们的关联,关联可以暂时没有方向但需要有(1:1,1:N,M:N)这些关系;可以用文字精确的没有歧义的描述出每个领域概念的涵义以及包含的主要信息;
  2. 分析主要的软件应用程序功能,识别出主要的应用层的类;这样有助于及早发现哪些是应用层的职责,哪些是领域层的职责;
  3. 进一步分析领域模型,识别出哪些是实体,哪些是值对象,哪些是领域服务;
  4. 分析关联,通过对业务的更深入分析以及各种软件设计原则及性能方面的权衡,明确关联的方向或者去掉一些不需要的关联;
  5. 找出聚合边界及聚合根,这是一件很有难度的事情;因为你在分析的过程中往往会碰到很多模棱两可的难以清晰判断的选择问题,所以,需要我们平时一些分析经验的积累才能找出正确的聚合根;
  6. 为聚合根配备仓储,一般情况下是为一个聚合分配一个仓储,此时只要设计好仓储的接口即可;
  7. 走查场景,确定我们设计的领域模型能够有效地解决业务需求;
  8. 考虑如何创建领域实体或值对象,是通过工厂还是直接通过构造函数;
  9. 停下来重构模型。寻找模型中觉得有些疑问或者是蹩脚的地方,比如思考一些对象应该通过关联导航得到还是应该从仓储获取?聚合设计的是否正确?考虑模型的性能怎样,等等;

领域建模是一个不断重构,持续完善模型的过程,大家会在讨论中将变化的部分反映到模型中,从而是模型不断细化并朝正确的方向走。领域建模是领域专家、设计人员、开发人员之间沟通交流的过程,是大家工作和思考问题的基础。

在分层架构中其他层如何与领域层交互

从经典的领域驱动设计分层架构中可以看出,领域层的上层是应用层,下层是基础设施层。那么领域层是如何与其它层交互的呢?

对于会影响领域层中领域对象状态的应用层功能

一般应用层会先启动一个工作单元,然后:

  1. 对于修改领域对象的情况,通过仓储获取领域对象,调用领域对象的相关业务方法以完成业务逻辑处理;
  2. 对于新增领域对象的情况,通过构造函数或工厂创建出领域对象,如果需要还可以继续对该新创建的领域对象做一些操作,然后把该新创建的领域对象添加到仓储中;
  3. 对于删除领域对象的情况,可以先把领域对象从仓储中取出来,然后将其从仓储中删除,也可以直接传递一个要删除的领域对象的唯一标识给仓储通知其移除该唯一标识对应领域对象;
  4. 如果一个业务逻辑涉及到多个领域对象,则调用领域层中的相关领域服务完成操作;

注意,以上所说的所有领域对象都是只聚合根,另外在应用层需要获取仓储接口以及领域服务接口时,都可以通过IOC容器获取。最后通知工作单元提交事务从而将所有相关的领域对象的状态以事务的方式持久化到数据库;

关于Unit of Work(工作单元)的几种实现方法

  1. 基于快照的实现,即领域对象被取出来后,会先保存一个备份的对象,然后当在做持久化操作时,将最新的对象的状态和备份的对象的状态进行比较,如果不相同,则认为有做过修改,然后进行持久化;这种设计的好处是对象不用告诉工作单元自己的状态修改了,而缺点也是显而易见的,那就是性能可能会低,备份对象以及比较对象的状态是否有修改的过程在当对象本身很复杂的时候,往往是一个比较耗时的步骤,而且要真正实现对象的深拷贝以及判断属性是否修改还是比较困难的;
  2. 不基于快照,而是仓储的相关更新或新增或删除接口被调用时,仓储通知工作单元某个对象被新增了或更新了或删除了。这样工作单元在做数据持久化时也同样可以知道需要持久化哪些对象了;这种方法理论上不需要ORM框架的支持,对领域模型也没有任何倾入性,同时也很好的支持了工作单元的模式。对于不想用高级ORM框架的朋友来说,这种方法挺好;
  3. 不基于快照,也不用仓储告诉工作单元数据更改了。而是采用AOP的思想,采用透明代理的方式进行一个拦截。在NHibernate中,我们的属性通常要被声明为virtual的,一个原因就是NHibernate会生成一个透明代理,用于拦截对象的属性被修改时,自动通知工作单元对象的状态被更新了。这样工作单元也同样知道需要持久化哪些对象了。这种方法对领域模型的倾入性不大,并且能很好的支持工作单元模式,如果用NHibernate作为ORM,这种方法用的比较多;
  4. 一般是微软用的方法,那就是让领域对象实现.NET框架中的INotifiyPropertyChanged接口,然后在每个属性的set方法的最后一行调用OnPropertyChanged的方法从而显示地通知别人自己的状态修改了。这种方法相对来说对领域模型的倾入性最强。

对于不会影响领域层中领域对象状态的查询功能

可以直接通过仓储查询出所需要的数据。但一般领域层中的仓储提供的查询功能也许不能满足界面显示的需要,则可能需要多次调用不同的仓储才能获取所需要显示的数据;其实针对这种查询的情况,我在后面会讲到可以直接通过CQRS的架构来实现。即对于查询,我们可以在应用层不调用领域层的任何东西,而是直接通过某个其他的用另外的技术架构实现的查询引擎来完成查询,比如直接通过构造参数化SQL的方式从数据库一个表或多个表中查询出任何想要显示的数据。这样不仅性能高,也可以减轻领域层的负担。领域模型不太适合为应用层提供各种查询服务,因为往往界面上要显示的数据是很多对象的组合信息,是一种非对象概念的信息,就像报表;

为什么面向对象比面向过程更能适应业务变化

对象将需求用类一个个隔开,就像用储物箱把东西一个个封装起来一样,需求变了,分几种情况,最严重的是大变,那么每个储物箱都要打开改,这种方法就不见得有好处;但是这种情况发生概率比较小,大部分需求变化都是局限在一两个储物箱中,那么我们只要打开这两个储物箱修改就可以,不会影响其他储物柜了。

而面向过程是把所有东西都放在一个大储物箱中,修改某个部分以后,会引起其他部分不稳定,一个BUG修复,引发新的无数BUG,最后程序员陷入焦头烂额,如日本东京电力公司员工处理核危机一样,心力交瘁啊。

所以,我们不能粗粒度看需求变,认为需求变了,就是大范围变,万事万物都有边界,老子说,无欲观其缴,什么事物都要观察其边界,虽然需求可以用“需求”这个名词表达,谈到需求变了,不都意味着最大边界范围的变化,这样看问题容易走极端。

其实就是就地画圈圈——边界。我们小时候写作文分老三段也是同样道理,各自职责明确,划分边界明确,通过过渡句实现承上启下——接口。为什么组织需要分不同部门,同样是边界思维。画圈圈容易,但如何画才难,所以OO中思维非常重要。

需求变化所引起的变化是有边界,若果变化的边界等于整个领域,那么已经是完全不同的项目了。要掌握边界,是需要大量的领域知识的。否则,走进银行连业务职责都分不清的,如何画圈圈呢?

面向过程是无边界一词的(就算有也只是最大的边界),它没有要求各自独立,它可以横跨边界进行调用,这就是容易引起BUG的原因,引起BUG不一定是技术错误,更多的是逻辑错误。分别封装就是画圈圈了,所有边界都以接口实现。不用改或者小改接口,都不会牵一发动全身。若果面向过程中考虑边界,那么也就已经上升到OO思维,即使用的不是对象语言,但对象已经隐含其中。说白了,面向对象与面向过程最大区别就是:分解。边界的分解。从需求到最后实现都贯穿。

面向对象的实质就是边界划分,封装,不但对需求变化能够量化,缩小影响面;因为边界划分也会限制出错的影响范围,所以OO对软件后期BUG等出错也有好处。

软件世界永远都有BUG,BUG是清除不干净的,就像人类世界永远都存在不完美和阴暗面,问题关键是:上帝用空间和时间的边界把人类世界痛苦灾难等不完美局限在一个范围内;而软件世界如果你不采取OO等方法进行边界划分的话,一旦出错,追查起来情况会有多糟呢?

软件世界其实类似人类现实世界,有时出问题了,探究原因一看,原来是两个看上去毫无联系的因素导致的,古人只好经常求神拜佛,我们程序员在自己的软件上线运行时,大概心里也在求神拜佛别出大纰漏,如果我们的软件采取OO封装,我们就会坦然些,肯定会出错,但是我们已经预先划定好边界,所以,不会产生严重后果,甚至也不会出现难以追查的魔鬼BUG。

领域驱动设计的其他一些主题

上面只是涉及到DDD中最基本的内容,DDD中还有很多其他重要的内容在上面没有提到,如:

  1. 模型上下文、上下文映射、上下文共享;
  2. 如何将分析模式和设计模式运用到DDD中;
  3. 一些关于柔性设计的技巧;
  4. 如果保持模型完整性,以及持续集成方面的知识;
  5. 如何精炼模型,识别核心模型以及通用子领域;

这些主题都很重要,因为篇幅有限以及我目前掌握的知识也有限,并且为了突出这篇文章的重点,所以不对他们做详细介绍了,大家有兴趣的可以自己阅读一下。

工作单元

工作单元的目标是维护变化的对象列表。使用IUnitOfWorkRepository负责对象的持久化,使用IUnitOfWork收集变化的对象,并将变化的对象放到各自的增删改列表中,

最后Commit,Commit时需要循环遍历这些列表,并由Repository来持久化。

代码实现如下:

  实体接口

namespace Notify.Infrastructure.DomainBase
{
/// <summary>
/// 实体接口
/// </summary>
public interface IEntity
{
/// <summary>
/// key
/// </summary>
object Key { get; }
}
}

  EntityBase,领域类的基类。

namespace Notify.Infrastructure.DomainBase
{
/// <summary>
/// 实体抽象类
/// </summary>
public abstract class EntityBase : IEntity
{
/// <summary>
/// 标识key
/// </summary>
private readonly object key; /// <summary>
/// Initializes a new instance of the <see cref="EntityBase"/> class.
/// 默认构造函数 default constructor
/// </summary>
protected EntityBase()
: this(null)
{
} /// <summary>
/// Initializes a new instance of the <see cref="EntityBase"/> class.
/// </summary>
/// <param name="key">key</param>
protected EntityBase(object key)
{
this.key = key;
} /// <summary>
/// Gets the key.
/// </summary>
public object Key
{
get
{
return this.key;
}
} /// <summary>
/// The equals.
/// </summary>
/// <param name="other">
/// The other.
/// </param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
protected bool Equals(EntityBase other)
{
return Equals(this.Key, other.Key);
} /// <summary>
/// The equals.
/// </summary>
/// <param name="entity">
/// The entity.
/// </param>
/// <returns>
/// The <see cref="bool"/>.
/// </returns>
public override bool Equals(object entity)
{
if (entity == null || !(entity is EntityBase))
{
return false;
} return this == (EntityBase)entity;
} /// <summary>
/// The get hash code.
/// </summary>
/// <returns>
/// The <see cref="int"/>.
/// </returns>
public override int GetHashCode()
{
return this.Key != null ? this.Key.GetHashCode() : ;
} /// <summary>
/// The ==.
/// </summary>
/// <param name="left">
/// The base 1.
/// </param>
/// <param name="right">
/// The base 2.
/// </param>
/// <returns>
/// </returns>
public static bool operator ==(EntityBase left, EntityBase right)
{
if ((object)left == null && (object)right == null)
{
return true;
} if ((object)left == null || (object)right == null)
{
return false;
} if (left.Key == null && left.Key == null)
{
return true;
} return left.Key.ToString() == right.Key.ToString();
} /// <summary>
/// The !=.
/// </summary>
/// <param name="left">
/// The base 1.
/// </param>
/// <param name="right">
/// The base 2.
/// </param>
/// <returns>
/// </returns>
public static bool operator !=(EntityBase left, EntityBase right)
{
return !(left == right);
}
} /// <summary>
/// The entity.
/// </summary>
/// <typeparam name="T">
/// </typeparam>
public abstract class EntityBase<T> : EntityBase
{
/// <summary>
/// Initializes a new instance of the <see cref="EntityBase{T}"/> class.
/// Initializes a new instance of the <see cref="EntityBase"/> class.
/// 默认构造函数 default constructor
/// </summary>
protected EntityBase()
: base(null)
{
} /// <summary>
/// Initializes a new instance of the <see cref="EntityBase{T}"/> class.
/// Initializes a new instance of the <see cref="EntityBase"/> class.
/// </summary>
/// <param name="key">
/// key
/// </param>
protected EntityBase(T key)
: base(key)
{
} /// <summary>
/// Gets the key.
/// </summary>
public new T Key
{
get
{
T @default = default(T);
if (base.Key == null)
{
return @default;
} return (T)base.Key;
}
}
}
}

  工作单元接口:简单的工作单元接口 只有2个方法一个提交一个回滚

using System;
using System.Data; namespace Notify.Infrastructure.UnitOfWork
{
/// <summary>
/// 工作单元接口
/// </summary>
public interface IUnitOfWork : IDisposable
{
/// <summary>
/// SQL执行
/// </summary>
IDbCommand Command { get; } /// <summary>
/// 提交
/// </summary>
void Complete(); /// <summary>
/// 回滚
/// </summary>
void Rollback();
}
}

简单的工作单元实现

using System.Data;
using Notify.Infrastructure.UnitOfWork; namespace Notify.DbCommon.UnitOfWork
{
/// <summary>
/// 工作单元
/// </summary>
public class UnitOfWork : IUnitOfWork
{
/// <summary>
/// Initializes a new instance of the <see cref="UnitOfWork"/> class.
/// 设置事务隔离级别
/// </summary>
/// <param name="name"> The connection String.</param>
public UnitOfWork(string name)
{
this.Conn = DbFactories.GetConnection(name);
this.Command = this.Conn.CreateCommand();
this.Transaction = this.Conn.BeginTransaction();
this.Command.Transaction = this.Transaction;
} /// <summary>
/// 链接
/// </summary>
protected IDbConnection Conn { get; set; } /// <summary>
/// 事务
/// </summary>
protected IDbTransaction Transaction { get; set; } /// <summary>
/// SQL执行
/// </summary>
/// <summary>
/// IDbCommand
/// </summary>
public IDbCommand Command { get; private set; } /// <summary>
/// 提交事务
/// </summary>
public virtual void Complete()
{
this.Transaction.Commit();
} /// <summary>
/// 回滚事务
/// </summary>
public virtual void Rollback()
{
this.Transaction.Rollback();
} /// <summary>
/// 释放资源
/// </summary>
public virtual void Dispose()
{
if (this.Transaction != null)
{
this.Transaction.Dispose();
this.Transaction = null;
} if (this.Command != null)
{
this.Command.Dispose();
this.Command = null;
} if (this.Conn != null)
{
this.Conn.Dispose();
this.Conn.Close();
this.Conn = null;
}
}
}
}

  复杂一点的工作单元实现

  就是需要手动将要执行的方法注册到工作单元 这时候就需要一个 注册工作单元接口

using System;
using Notify.Infrastructure.DomainBase;
using Notify.Infrastructure.RepositoryFramework; namespace Notify.Infrastructure.UnitOfWork
{
/// <summary>
/// InvokeMethod
/// </summary>
/// <param name="entity">
/// The entity.
/// </param>
public delegate void InvokeMethod(IEntity entity); /// <summary>
/// 注册工作单元接口
/// </summary>
public interface IPowerUnitOfWork : IUnitOfWork
{
/// <summary>
/// 注册新增实体工作单元仓储接口
/// </summary>
/// <param name="entity">待新增实体接口</param>
/// <param name="repository">工作单元仓储接口</param>
void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository); /// <summary>
/// 注册修改实体工作单元仓储接口
/// </summary>
/// <param name="entity">待修改实体接口</param>
/// <param name="repository">工作单元仓储接口</param>
void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository); /// <summary>
/// 注册删除实体工作单元仓储接口
/// </summary>
/// <param name="entity">待删除实体接口</param>
/// <param name="repository">工作单元仓储接口</param>
void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository); /// <summary>
/// 注册一个其他非基础的增删改工作单元仓储接口
/// </summary>
/// <param name="entity">待操作实体接口</param>
/// <param name="methodName">自定义委托</param>
void RegisterInvokeMethod(IEntity entity, InvokeMethod methodName); /// <summary>
/// 注册一个非继承聚合根的其他非基础的增删改工作单元仓储接口
/// </summary>
/// <param name="entity">待操作实体接口</param>
/// <param name="methodName">Action委托</param>
void RegisterAction(object entity, Action<object> methodName); /// <summary>
/// 注册一个非继承聚合根的其他非基础的增删改工作单元仓储接口
/// </summary>
/// <param name="entity">待操作实体接口</param>
/// <param name="methodName">Func委托</param>
void RegisterFunc(object entity, Func<object, object> methodName);
}
}

复杂的工作单元则需要 实现注册工作单元接口 并且继承简单工作单元基类 扩展实现(开放封闭原则)

using System;
using System.Collections.Generic;
using Notify.Code.Write;
using Notify.Infrastructure.DomainBase;
using Notify.Infrastructure.RepositoryFramework;
using Notify.Infrastructure.UnitOfWork; namespace Notify.DbCommon.UnitOfWork
{
/// <summary>
/// 工作单元
/// </summary>
public class PowerUnitOfWork : UnitOfWork, IPowerUnitOfWork
{
/// <summary>
/// 新增实体工作单元
/// </summary>
private readonly Dictionary<IEntity, IUnitOfWorkRepository> m_addedEntities; /// <summary>
/// 修改实体工作单元
/// </summary>
private readonly Dictionary<IEntity, IUnitOfWorkRepository> m_changedEntities; /// <summary>
/// 删除实体工作单元
/// </summary>
private readonly Dictionary<IEntity, IUnitOfWorkRepository> m_deletedEntities; /// <summary>
/// 其他非基础的增删改
/// </summary>
private readonly Dictionary<IEntity, InvokeMethod> m_invokeEntities; /// <summary>
/// 非继承聚合根的其他非基础的增删改工作单元
/// </summary>
private readonly Dictionary<object, Action<object>> m_action; /// <summary>
/// 非继承聚合根的其他非基础的增删改工作单元
/// </summary>
private readonly Dictionary<object, Func<object, object>> m_func; /// <summary>
/// Initializes a new instance of the <see cref="PowerUnitOfWork"/> class.
/// </summary>
/// <param name="connectionSetting">
/// The connection setting.
/// </param>
public PowerUnitOfWork(string connectionSetting)
: base(connectionSetting)
{
this.m_addedEntities = new Dictionary<IEntity, IUnitOfWorkRepository>();
this.m_changedEntities = new Dictionary<IEntity, IUnitOfWorkRepository>();
this.m_deletedEntities = new Dictionary<IEntity, IUnitOfWorkRepository>();
this.m_invokeEntities = new Dictionary<IEntity, InvokeMethod>();
this.m_action = new Dictionary<object, Action<object>>();
this.m_func = new Dictionary<object, Func<object, object>>();
} /// <summary>
/// 提交工作单元
/// </summary>
public override void Complete()
{
try
{
foreach (IEntity entity in this.m_deletedEntities.Keys)
{
this.m_deletedEntities[entity].PersistDeletedItem(entity);
} foreach (IEntity entity in this.m_addedEntities.Keys)
{
this.m_addedEntities[entity].PersistNewItem(entity);
} foreach (IEntity entity in this.m_changedEntities.Keys)
{
this.m_changedEntities[entity].PersistUpdatedItem(entity);
} foreach (IEntity entity in this.m_invokeEntities.Keys)
{
this.m_invokeEntities[entity](entity);
} foreach (var entity in this.m_action)
{
entity.Value(entity.Key);
} foreach (var entity in this.m_func)
{
entity.Value(entity.Key);
} base.Complete();
}
catch (Exception exception)
{
this.Rollback();
LogService.WriteLog(exception, "工作单元提交失败");
throw;
}
finally
{
this.Dispose();
this.Clear();
}
} /// <summary>
/// 注册新增实体工作单元仓储接口
/// </summary>
/// <param name="entity">待新增实体接口</param>
/// <param name="repository">工作单元仓储接口</param>
public void RegisterAdded(IEntity entity, IUnitOfWorkRepository repository)
{
this.m_addedEntities.Add(entity, repository);
} /// <summary>
/// 注册修改实体工作单元仓储接口
/// </summary>
/// <param name="entity">待修改实体接口</param>
/// <param name="repository">工作单元仓储接口</param>
public void RegisterChanged(IEntity entity, IUnitOfWorkRepository repository)
{
this.m_changedEntities.Add(entity, repository);
} /// <summary>
/// 注册删除实体工作单元仓储接口
/// </summary>
/// <param name="entity">待删除实体接口</param>
/// <param name="repository">工作单元仓储接口</param>
public void RegisterRemoved(IEntity entity, IUnitOfWorkRepository repository)
{
this.m_deletedEntities.Add(entity, repository);
} /// <summary>
/// 注册一个其他非基础的增删改工作单元仓储接口
/// </summary>
/// <param name="entity">待操作实体接口</param>
/// <param name="methodName">自定义委托</param>
public void RegisterInvokeMethod(IEntity entity, InvokeMethod methodName)
{
this.m_invokeEntities.Add(entity, methodName);
} /// <summary>
/// 注册一个非继承聚合根的其他非基础的增删改工作单元仓储接口
/// </summary>
/// <param name="entity">待操作实体接口</param>
/// <param name="methodName">Action委托</param>
public void RegisterAction(object entity, Action<object> methodName)
{
this.m_action.Add(entity, methodName);
} /// <summary>
/// 注册一个非继承聚合根的其他非基础的增删改工作单元仓储接口
/// </summary>
/// <param name="entity">待操作实体接口</param>
/// <param name="methodName">Func委托</param>
public void RegisterFunc(object entity, Func<object, object> methodName)
{
this.m_func.Add(entity, methodName);
} /// <summary>
/// 清除
/// </summary>
private void Clear()
{
this.m_addedEntities.Clear();
this.m_changedEntities.Clear();
this.m_deletedEntities.Clear();
this.m_invokeEntities.Clear();
this.m_action.Clear();
this.m_func.Clear();
}
}
}

  上篇--仓储基础接口

  下篇--仓储实现

EsayUI + MVC + ADO.NET(工作单元)的更多相关文章

  1. EsayUI + MVC + ADO.NET(项目架构)

    项目目录   1.项目架构: http://www.cnblogs.com/liuxiaoji/p/3922506.html  2.仓储基础接口: http://www.cnblogs.com/liu ...

  2. EsayUI + MVC + ADO.NET(仓储基类)

      该篇主要讲解工作单元的运用 和DbHlper抽象封装 工作单元仓储基类:BaseRepository (DbHlper抽象封装)  仓储接口(CRUD):IRepository (CRUD接口) ...

  3. EsayUI + MVC + ADO.NET(仓储基础接口)

      1.RepositoryFramework(仓储接口:无外乎就是CRUD) 1.IAddRepository(添加接口) using System; namespace Notify.Infras ...

  4. ABP框架 - 工作单元

    文档目录 本节内容: 简介 在ABP中管理连接和事务 约定的工作单元 UnitOfWork 特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 非事务性工作单元 工作单元方法调用另 ...

  5. 5.在MVC中使用泛型仓储模式和工作单元来进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  6. MVC+EF 理解和实现仓储模式和工作单元模式

    MVC+EF 理解和实现仓储模式和工作单元模式 原文:Understanding Repository and Unit of Work Pattern and Implementing Generi ...

  7. 在MVC中使用泛型仓储模式和工作单元来进行增删查改

    原文链接:http://www.c-sharpcorner.com/UploadFile/3d39b4/crud-operations-using-the-generic-repository-pat ...

  8. ABP(现代ASP.NET样板开发框架)系列之12、ABP领域层——工作单元(Unit Of work)

    点这里进入ABP系列文章总目录 基于DDD的现代ASP.NET开发框架--ABP系列之12.ABP领域层——工作单元(Unit Of work) ABP是“ASP.NET Boilerplate Pr ...

  9. ABP理论学习之工作单元(Unit of Work)

    返回总目录 本篇目录 公共连接和事务管理方法 ABP中的连接和事务管理 仓储类 应用服务 工作单元 工作单元详解 关闭工作单元 非事务的工作单元 工作单元方法调用其它 工作单元作用域 自动保存 IRe ...

随机推荐

  1. [LeetCode] 207 Course Schedule_Medium tag: BFS, DFS

    There are a total of n courses you have to take, labeled from 0 to n-1. Some courses may have prereq ...

  2. RMAN备份与恢复实践(转)

    1   RMAN备份与恢复实践 1.1  备份 1.1.1 对数据库进行全备 使用backup database命令执行备份 RMAN> BACKUP DATABASE; 执行上述命令后将对目标 ...

  3. L1正则化和L2正则化

    L1正则化可以产生稀疏权值矩阵,即产生一个稀疏模型,可以用于特征选择 L2正则化可以防止模型过拟合(overfitting):一定程度上,L1也可以防止过拟合 一.L1正则化 1.L1正则化 需注意, ...

  4. NLP总览

    一.自然语言处理概述 1)自然语言处理:利用计算机为工具,对书面实行或者口头形式进行各种各样的处理和加工的技术,是研究人与人交际中以及人与计算机交际中的演员问题的一门学科,是人工智能的主要内容. 2) ...

  5. zend studio9破解版

    一.下载window http://downloads.zend.com/studio-eclipse/9.0.3/ZendStudio-9.0.3.msi 下载linux64 http://down ...

  6. linuxbash 父进程 子进程

    linux登陆linux,就获得一个bash,之后你的bash就是一个独立的进程,被称为pid的就是,之后你在bash下面执行的任何命令都是由这个bash所衍生的,那些被执行的命令被称为子进程.子进程 ...

  7. Windows批处理程序bat

    @echo off    关闭回显,否则脚本中的命令都会输出,关闭后只显示结果. setlocal ENABLEDELAYEDEXPANSION 在for循环中变量扩展时需要用到 copy /Y ms ...

  8. redis 哨兵机制环境搭建

    Redis哨兵机制,一主二从 注:Redis哨兵切换,建议一主多从 一.一主二从 教程步骤:https://www.cnblogs.com/zwcry/p/9046207.html 二.哨兵配置(se ...

  9. SQL学习之SQL注入总结

    Sql注入定义: 就是通过把sql命令插入到web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行的sql命令的目的. sql注入分类: 基于联合查询 基于错误回显 基于盲注,分时间盲 ...

  10. 什么叫集群、分布式,分布式与集群有什么区别?(康神sf讲座学习笔记)

    集群是物理形态,分布式是工作方式. 只要一堆机器放在那里,就是集群.比如Nginx后面的十台服务器,就是一个集群 分布式将任务放在多个物理隔离的节点上进行. 分布式中各个子节点互不通信,统一受管控中心 ...