大家都看到标题中的三个英文缩写了: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消息的形式传输到客户端)。
于是DTO,DPO,DMP就产生了。
1、传输实体,经过客户端传输给wcf。在服务端可能需要组合产生新的数据库持久化实体(区分添加用实体和更新用实体),这些实体经过ORM将数据持久化到数据库。
2、从数据库查询的数据,经过ORM映射产生的实体,然后经过处理,产生传输实体,传输给客户端。
3、数据传输实体dto,数据库持久化实体dpo,数据库映射实体dmo
【Blog】http://virusswb.cnblogs.com/
【MSN】[email protected]
【说明】转载请标明出处,谢谢
对于数据处理逻辑,上篇已经发布了又VB.net写的接口,这里公布一个又vb.net写的接口实现类
本来想发布类工厂操作,但是我的工程采用了配置文件注入方式,所以代码又点长,就先发sql数据库操作的实现类。
Imports System.Data
Imports System.Data.SqlClient
Imports System.Data.Common
''' <summary>
''' 简单数据操作底层类SQL数据库
''' </summary>
''' <remarks>
''' 该类执行简单的数据操作支持SQL
''' 开发者:欧阳寒玟
''' 开发时间:2009-01-25
''' 修改时间:2010-10-25
''' 作者网站:http://www.coldwin.org
''' </remarks>
Public Class SQLHelper
Implements IDbHelper
Private conStr As String
Private conn As SqlConnection ''连接对象
Private cmd As SqlCommand ''Command对象
Private _isTran As Boolean = False ''是否执行事务
#Region "准备工作"
''' <summary>
''' 获取或设置链接字符串
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property ConnectionString As String Implements IDbHelper.ConnectionString
Get
Return conStr
End Get
Set(ByVal value As String)
conStr = value
End Set
End Property
''' <summary>
''' 获取或设置连接对象
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property Connection As IDbConnection Implements IDbHelper.Connection
Get
Return conn
End Get
Set(ByVal value As IDbConnection)
conn = value
End Set
End Property
''' <summary>
''' 是否执行事务
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property isTranSaction As Boolean Implements IDbHelper.isTranSaction
Get
Return _isTran
End Get
Set(ByVal value As Boolean)
_isTran = value
End Set
End Property
''' <summary>
''' 获取或设置数据执行事务
''' </summary>
''' <value></value>
''' <returns></returns>
''' <remarks></remarks>
Public Property TranSaction As IDbTransaction Implements IDbHelper.TranSaction
Get
'isTranSaction = True
Return cmd.Transaction
End Get
Set(ByVal value As IDbTransaction)
isTranSaction = True
cmd.Transaction = value
End Set
End Property
''' <summary>
''' 关闭Connection对象
''' </summary>
''' <remarks></remarks>
Public Sub Close() Implements IDbHelper.Close
If conn.State = ConnectionState.Open And isTranSaction = False Then
conn.Close()
End If
End Sub
''' <summary>
''' 默认读取配置文件的链接字符串
''' </summary>
''' <remarks></remarks>
Public Sub New()
Try
conStr = Configuration.ConfigurationManager.AppSettings("sql").ToString()
Catch ex As Exception
Try
conStr = System.Configuration.ConfigurationManager.ConnectionStrings("sql").ToString()
Catch exp As Exception
CWF.DocumentService.ServerSession(Of String).log.Save(exp.Message)
End Try
End Try
conn = New SqlConnection(ConnectionString)
cmd = New SqlCommand()
cmd.Connection = conn
Try
conn.Open()
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Sub
''' <summary>
''' 带参数实例化
''' </summary>
''' <param name="constring">链接字符串</param>
''' <remarks></remarks>
Public Sub New(ByVal constring As String)
ConnectionString = constring
conn = New SqlConnection(ConnectionString)
cmd = New SqlCommand()
cmd.Connection = conn
Try
conn.Open()
Catch ex As Exception
Throw New Exception(ex.Message)
End Try
End Sub
#End Region
Private Function Exec(ByVal Command As SqlCommand, ByVal CmdTxt As String, ByVal commondType As CommandType) As Integer
Command.CommandText = CmdTxt
Command.CommandType = commondType
Try
If conn.State = ConnectionState.Closed Then conn.Open()
Return Command.ExecuteNonQuery()
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
Private Function Exec(ByVal Command As SqlCommand, ByVal CmdTxt As String, ByVal commondType As CommandType, ByVal Parameters() As SqlParameter) As Integer
Command.CommandText = CmdTxt
Command.CommandType = commondType
Command.Parameters.AddRange(CType(Parameters, SqlParameter()))
Try
If conn.State = ConnectionState.Closed Then conn.Open()
Return Command.ExecuteNonQuery()
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
Private Function qureys(ByVal Command As SqlCommand, ByVal CmdTxt As String, ByVal commandType As CommandType) As DataTable
Dim dt As New DataTable
Command.CommandText = CmdTxt
Command.CommandType = commandType
Dim DataAdapter As New SqlDataAdapter(Command)
If conn.State = ConnectionState.Closed Then conn.Open()
Try
DataAdapter.Fill(dt)
Return dt
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
Private Function qureys(ByVal Command As SqlCommand, ByVal CmdTxt As String, ByVal commandType As CommandType, ByVal Parameters() As SqlParameter) As DataTable
Dim dt As New DataTable
Command.CommandText = CmdTxt
Command.CommandType = commandType
Command.Parameters.AddRange(CType(Parameters, SqlParameter()))
Dim DataAdapter As New SqlDataAdapter(Command)
If conn.State = ConnectionState.Closed Then conn.Open()
Try
DataAdapter.Fill(dt)
Return dt
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
Private Function qureysDataset(ByVal Command As SqlCommand, ByVal CmdTxt As String, ByVal commandType As CommandType) As DataSet
Dim dt As New DataSet
Command.CommandText = CmdTxt
Command.CommandType = commandType
Dim DataAdapter As New SqlDataAdapter(Command)
If conn.State = ConnectionState.Closed Then conn.Open()
Try
DataAdapter.Fill(dt)
Return dt
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
Private Function qureysDataset(ByVal Command As SqlCommand, ByVal CmdTxt As String, ByVal commandType As CommandType, ByVal Parameters() As SqlParameter) As DataSet
Dim dt As New DataSet
Command.CommandText = CmdTxt
Command.CommandType = commandType
Command.Parameters.AddRange(CType(Parameters, SqlParameter()))
Dim DataAdapter As New SqlDataAdapter(Command)
If conn.State = ConnectionState.Closed Then conn.Open()
Try
DataAdapter.Fill(dt)
Return dt
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
''' <summary>
''' 执行无参数存储过程
''' </summary>
''' <param name="ProcName">存储过程名称</param>
''' <remarks></remarks>
Public Sub ExcuteProc(ByVal ProcName As String) Implements IDbHelper.ExcuteProc
Exec(cmd, ProcName, CommandType.StoredProcedure)
End Sub
''' <summary>
''' 执行带参数的存储过程
''' </summary>
''' <param name="ProcName">存储过程名称</param>
''' <param name="Parameters">参数数组</param>
''' <remarks></remarks>
Public Sub ExcuteProc(ByVal ProcName As String, ByVal Parameters() As IDbDataParameter) Implements IDbHelper.ExcuteProc
Exec(cmd, ProcName, CommandType.StoredProcedure, CType(Parameters, SqlParameter()))
End Sub
''' <summary>
''' 执行无参数sql语句
''' </summary>
''' <param name="cmdTxt">执行语句</param>
''' <returns>影响行数</returns>
''' <remarks></remarks>
Public Function Execute(ByVal cmdTxt As String) As Integer Implements IDbHelper.Execute
Return Exec(cmd, cmdTxt, CommandType.Text)
End Function
''' <summary>
''' 执行带参数的sql语句
''' </summary>
''' <param name="cmdTxt">命令语句</param>
''' <param name="Parameters">参数</param>
''' <returns>影响行数</returns>
''' <remarks></remarks>
Public Function Execute(ByVal cmdTxt As String, ByVal Parameters() As IDbDataParameter) As Integer Implements IDbHelper.Execute
Return Exec(cmd, cmdTxt, CommandType.Text, Parameters)
End Function
''' <summary>
''' 启用内部事务执行多条命令
''' </summary>
''' <param name="cmdTxt">命令数组</param>
''' <param name="cmdType">执行类型</param>
''' <returns>影响行数</returns>
''' <remarks></remarks>
Public Function ExecuteNonQuery(ByVal cmdTxt() As String, ByVal cmdType As System.Data.CommandType) As Integer Implements IDbHelper.ExecuteNonQuery
cmd.CommandType = cmdType
Dim tran As SqlTransaction = Nothing
Dim num As Integer = 0
Try
tran = conn.BeginTransaction
For Each st As String In cmdTxt
If st = "" Then Exit For
If conn.State = ConnectionState.Closed Then
conn.Open()
End If
cmd.CommandText = st
cmd.Transaction = tran
num += cmd.ExecuteNonQuery()
Next
tran.Commit()
Catch ex As Exception
tran.Rollback()
num = 0
Throw New Exception(ex.Message)
Finally
cmd.Parameters.Clear()
Close()
End Try
Return num
End Function
''' <summary>
''' 执行无参数sql命令返回第一行第一列
''' </summary>
''' <param name="cmdTxt">sql命令</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function ExecuteScalar(ByVal cmdTxt As String) As Object Implements IDbHelper.ExecuteScalar
cmd.CommandText = cmdTxt
If conn.State = ConnectionState.Closed Then conn.Open()
Try
Return cmd.ExecuteScalar()
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
''' <summary>
''' 执行带参数sql命令返回第一行第一列
''' </summary>
''' <param name="cmdTxt">sql命令</param>
''' <param name="Parameters" >参数</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function ExecuteScalar(ByVal cmdTxt As String, ByVal Parameters() As IDbDataParameter) As Object Implements IDbHelper.ExecuteScalar
cmd.CommandText = cmdTxt
cmd.Parameters.AddRange(CType(Parameters, SqlParameter()))
If conn.State = ConnectionState.Closed Then conn.Open()
Try
Return cmd.ExecuteScalar()
Catch ex As Exception
Throw New Exception(ex.Message)
Finally
Close()
End Try
End Function
''' <summary>
''' 执行无参数命令返回DataTable
''' </summary>
''' <param name="cmdTxt">SQL命令</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function Query(ByVal cmdTxt As String) As System.Data.DataTable Implements IDbHelper.Query
Return Me.qureys(cmd, cmdTxt, CommandType.Text)
End Function
''' <summary>
''' 执行带参数命令返回DataTable
''' </summary>
''' <param name="cmdTxt">SQL</param>
''' <param name="Parameters">参数</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function Query(ByVal cmdTxt As String, ByVal Parameters() As IDbDataParameter) As System.Data.DataTable Implements IDbHelper.Query
Return Me.qureys(cmd, cmdTxt, CommandType.Text, Parameters)
End Function
''' <summary>
''' 执行无参数命令返回DataSet
''' </summary>
''' <param name="cmdTxt">SQL命令</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function QueryDataSet(ByVal cmdTxt As String) As System.Data.DataSet Implements IDbHelper.QueryDataSet
Return Me.qureysDataset(cmd, cmdTxt, CommandType.Text)
End Function
''' <summary>
''' 执行带参数命令返回DataSet
''' </summary>
''' <param name="cmdTxt">SQL</param>
''' <param name="Parameters">参数</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function QueryDataSet(ByVal cmdTxt As String, ByVal Parameters() As Object) As System.Data.DataSet Implements IDbHelper.QueryDataSet
Return Me.qureysDataset(cmd, cmdTxt, CommandType.Text, Parameters)
End Function
''' <summary>
''' 执行无参数存储过程返回DataTable
''' </summary>
''' <param name="ProcName">存储过程名</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function QueryProc(ByVal ProcName As String) As System.Data.DataTable Implements IDbHelper.QueryProc
Return Me.qureys(cmd, ProcName, CommandType.StoredProcedure)
End Function
''' <summary>
''' 执行带参数存储过程返回DataTable
''' </summary>
''' <param name="ProcName">存储过程名</param>
''' <param name="Parameters">参数</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function QueryProc(ByVal ProcName As String, ByVal Parameters() As IDbDataParameter) As System.Data.DataTable Implements IDbHelper.QueryProc
Return Me.qureys(cmd, ProcName, CommandType.StoredProcedure, Parameters)
End Function
''' <summary>
''' 执行带无数存储过程返回DataSet
''' </summary>
''' <param name="ProcName">存储过程名</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function QueryProcDataSet(ByVal ProcName As String) As System.Data.DataSet Implements IDbHelper.QueryProcDataSet
Return Me.qureysDataset(cmd, ProcName, CommandType.StoredProcedure)
End Function
''' <summary>
''' 执行带无数存储过程返回DataSet
''' </summary>
''' <param name="ProcName">存储过程名</param>
''' <param name="Parameters">参数名</param>
''' <returns></returns>
''' <remarks></remarks>
Public Function QueryProcDataSet(ByVal ProcName As String, ByVal Parameters() As IDbDataParameter) As System.Data.DataSet Implements IDbHelper.QueryProcDataSet
Return Me.qureysDataset(cmd, ProcName, CommandType.StoredProcedure, Parameters)
End Function
End Class
其实为什么选择vb写数据持久层,自己也不太清楚,大概是以前一直觉得vb是数据库的亲家,那是acc时代的事情了,现在不太清楚,但是我发现一个问题,vs里(我用vs2010)写vb比写C#快,智能提示很完善,C#手动打的代码很多,VB却大多能提示出来,连try catch都是自动补全的。不知道这个可以设置成一样不,但是可以确定一点就是,VB写的注释在折叠后能显示注释的标题如下图显示:
让人看起来很方便,C#没法做到这点吗?
我是个代码狂,不太喜欢去摆弄开发工具,IED对我来说差不多就是做智能提示写代码和编译的工具,让人见笑了。