今天和大家谈的是我对于实体的一些认识,难免有偏颇之初,还请各位指出。
大家都看到标题中的三个英文缩写了:DTO,DMO,DPO。DTO大家应该还是熟悉的,Data Transfer Ojbect(数据传输对象)。研究过DDD(Domain Driven Design领域驱动设计)的人应该了解过DTO。是用来传输数据的对象,应为领域对象虽然有数据(属性),但是领域对象上面还带有操作,在某些场合不适合进行传输,因为有些时候传输还需要序列化,而且也不是所有的领域对象属性都可以暴露给调用端的,而且有些属性可能要合并,可能要分解,之后才有利于调用端的使用,加上其他一些的业务原因,于是就有了专门用来传输数据的DTO,只有属性,没有操作,必要的时候加上序列化标记,实现远程调用。
DMO和DPO是我自己创造的名词,在后面再解释给大家听。
刚开始看到别人用来表达数据对象的类的时候,都是只有属性,没有操作,而且大都是和数据库表一一对应的。可以单独建立一个项目,名字叫做Project.Entity。在独立的一个项目中保存所有用到的实体,其他的项目添加对Project.Entity项目的引用。
就拿User举个例子吧。
public class User { public Guid UserID { get; set; } public string Username { get; set; } }
也有人喜欢在实体类添加后缀,方便标识,因为有可能领域对象也叫做User,但是觉得都叫做User又有点别扭,不好区分。就像分层的话,可能会有UserBll,UserDal之类的类产生。(在这里不讨论这些类是否合适,只是说明这种现象,但是如果大家有这方面的好思路,也可以分享一下)
然后不管是添加还是修改,还是查询显示,都是用这个User实体。这个在做ASP.NET或者WinForm的时候还可以。反正客户端和服务端都可以引用这个类库,不影响开发。
有一点,我个人比较喜欢将一些查询条件作为一个实体的属性。这样查询方法就不需要很多参数了,只有一个实体作为参数就可以了。而且在后面如果条件有变化,只要在实体添加属性,方法的内部添加或者删除参数的处理就可以了。至少调用端不用做任何修改。如果每个添加都作为一个参数的话,增加或者删除参数,服务端修改方法,调用端就需要修改调用的地方。也能减少一些开发工作吧,而且在界面的查询条件很多的时候,不至于查询方法的参数列表也很长。
例如用户查询,我就会建立一个UserFind实体。下面的例子中说明界面有两个条件,一个是用户姓名,一个是注册时间。
public class UserFind { public string Username { get; set; } public DateTime RegDate { get; set; } }
问题1
产生的问题就是添加的时候肯定是每个属性都要添加到数据库,修改的时候,有些属性不用或者是不能修改,允许部分的修改。还有就是显示的时候,可能还需要其他的属性,例如User实体下面可能会有一些集合属性,例如用户全部地址,邮件个数,是否有新邮件,是否有新订单之类的需求,就要在User实体添加其他属性。但是这些属性在添加和修改的时候都用不到。如果这几种情况还是共享一个实体的话,容易引起误用。而且增加沟通,需要告诉做添加功能的人,你只要添加属性1,2,3就可以了。告诉做显示的人,你需要每个属性都赋值。告诉做修改的人,属性2,3,4,5是可以修改的。
还有那个查询实体,例如查询订单吧。用户查询订单,只能查询自己的订单。
public class OrderFind { public string OrderSeqNo { get; set; } public DateTime PlaceDate { get; set; } }
根据订单编号和下单时间查询。
可是后台查询用户订单的时候,可能会需要用户的信息,例如查询姓名为“张三”的订单。好吧,修改这个查询实体。
public class OrderFind { public string OrderSeqNo { get; set; } public DateTime PlaceDate { get; set; } public string Username { get; set; } }
问题2
同样,这就需要在开发用户查询订单的时候忽略Username属性,在开发后台查询订单的时候加上Username属性。
是不是这两个功能也应该区分一下呢?
于是我就有了下面的总结。
1、实体的专用性
1) 尽量的保持实体的专用性,也就是一个功能的方法,虽然和两外一个方法的返回结果类似,可能只需要添加一两个属性,这样的情况,重新建立实体,方便后面可能对这两个方法返回内容的修改不至于相互影响。
2) 尽量保持一个实体中的每一个属性,每一个被赋值的属性,将来都会用到,否则减少实体的属性,或者新建一个实体,使用正好合适的属性个数。
3) 分离添加和显示用的实体,因为添加可能不是每个字段都需要赋值,或者一些值是默认值。
4) 分离不同类型的用户使用的实体,尽管是相同的功能。可以在类名添加ForPlanter之类的后缀来解决。因为不同用户关注的点不同,关注的属性肯定不相同。而且修改也不影响其他类型用户的使用。
最近的一个项目是WCF+Silverlight。一个类库项目不行了,需要添加两个类库项目,因为Silverlight不能添加普通类库引用,Silverlight有自己的类库项目模板。
当然了,实体的代码还是可以共享的,但是类库必须是两个。
在简单学习了DDD之后,发现里面的一个概念叫做DTO,数据传输对象。觉得其他和我的Project.Entity项目比较像,反正功能都是用来承载数据,不带有任何操作。甚至连数据验证都没有,数据验证可以通过attribute,集合独立的模块来完成。
问题2已经有了解决方案,就是保持实体的专用,减少混用。
可是问题1呢?
后来我们想减少代码的开发量,尤其是在增、删、改、查的数据库操作方面,想要引进一些工具类库,来帮助我们完成数据库的操作,使得我们可以专注于业务方面的开发。
这方面的类库有很多,例如:NHibernate,还有微软的Entity Framework等。引入这些时候我发现问题1变得更加突出了。要不然就会有大量的冗余字段被读取,甚至被网络传输(WCF+Silverlight的数据需要经过网络,SOAP消息的形式传输到客户端)。
主要是我想实现增删改查的类库化,就是用类库实现普通的增删改查,不要每次写一遍DbConnection之类的代码了,可以集中于业务代码。方式呢?我更加倾向于attribute,通过attribute,结合反射实现持久化的自动化,和查询实体,查询结果的自动化。
于是DTO(Data Transfer Object),DPO(Data Persistence Object),DMO(Data Mapping Object)的结构就产生了。
1、传输实体,经过客户端传输给wcf。在服务端可能需要组合产生新的数据库持久化实体(区分添加用实体和更新用实体),这些实体经过ORM将数据持久化到数据库。
2、从数据库查询的数据,经过ORM映射产生的实体,然后经过处理,产生传输实体,传输给客户端。
3、数据传输实体dto,数据库持久化实体dpo,数据库映射实体dmo
4、DTO,就是用来传输数据的,可能需要添加序列化标记,或者是wcf的序列化DataContract,没有逻辑,只有属性值。
5、DPO,就是前端的数据需要持久化到数据库,尽量保证每一个属性都会被持久化,都会用到。
6、DMP,就是从数据库获取的数据,直接通过attribute映射成为实体或者实体集合。