用Dotnet做开发,不少程序员都在为是用DataSet,DataTable,DataRow(以下简用:DotNet数据对象)作为项目的数据承载对象还是使用自定义的数据类和自定数据集合而犯难,社区中也有相关话题的不少讨论。前者作为Ado.net标准的数据集对象,本身有非常强大的功能,但也存在不少的问题,如:弱类型,非面向对象,数据类对象体积相对较大等。所以不少的设计人员选择了使用了自定义数据类和数据集作为自己项目的数据承载对象,解决上面的问题的同时也出现了一些其它的问题,比如:数据类编写起来麻烦,花时间且没有技术含量,降低开发效率,无法享受到Ado.net提供的很多数据操作的便利如(数据绑定的离线排序,离线查询,如此等等),如果直接使用Ado.net进行数据访问的话,还需要写比较多的数据访问层的代码,大大降低了开发效率。关于自定义数据类的话题,MSDN上有一篇很好的文章。《掌握 ASP.NET 之路:自定义实体类简介》
本篇就是的主题就是来讨论如何更好地设计自定义数据,让它结合自定义数据对象与DotNet数据对象无缝结合,发挥各自优点,以利于我们在使用时得心应手。当初考虑这样做主要是原因是要使用IBatisNet作为持久层工具,而不管是IBatisNet还是NHibernate都是使用自定义数据类与数据集合,它们都与DotNet数据对象天生没有联系的,如果使用它,而我就无法使用DotNet数据对象了,这样就有点偏离DotNet的味道了,而在需要的时候再通过一些方法去转换得到DotNet数据对象,就显得麻烦而且复杂得多。
上面的费话引出了我要解决的问题,那下面就详细介绍一下如何去设计这个数据类吧。基本原理就是,表现用自定义的数据类作为表现形式,而内部存储数据的部分使用DotNet数据对象作为数据容器。利用自定义数据类的Property,封装对DataRow的每一列的读取。我们可以这么理解,每一个自定义对象对应数据库中的每一行记录(DataRow),而一个数据集合就对应数据库的一个表(DataTable)。数据类的定义就由:
2{
3 private int id;
4
5 public int ID
6 {
7 get { return id; }
8 set { id = value; }
9 }
10}
变为类似于:
2{
3 private DataRow m_dataRow;
4
5 public int ID
6 {
7 get
8 {
9 if (m_dataRow["ID"] == DBNull.Value)
10 {
11 return 0;
12 }
13 return (int)m_dataRow["ID"];
14 }
15 set
16 {
17 m_dataRow["ID"] = value;
18 }
19 }
20}
上面(代码2)的数据类就一个雏形,但是还存在一些问题。DataRow从哪里来的?因为对DataRow来说,它本身是不能够实例化的,必须通过DataTable的NewRow方法生成一个与DataTable对象数据(表)结构相对应的数据行。那么是不是可以在数据类定义一个DataTable实体,只做为数据一个架构存放在那边。数据类的定义修改为(抽象类的定义,作为一个基类,构造数据结构的方法留子类对实现,以便后面的多态应用):
2{
3 private DataTable m_dataTable;
4 private DataRow m_dataRow;
5
6 public AbstractDataObject()
7 {
8 m_dataTable = BuildSchema();
9 m_dataRow = m_dataTable.NewRow();
10 }
11 protected abstract DataTable BuildSchema();
12
13 public int ID
14 {
15 get
16 {
17 if (m_dataRow["ID"] == DBNull.Value)
18 {
19 return 0;
20 }
21 return (int)m_dataRow["ID"];
22 }
23 set
24 {
25 m_dataRow["ID"] = value;
26 }
27 }
28}
这样(代码3)的定义是可行,并且也能工作。但是一眼看就能看出它的问题,那就是在每个数据类的对象都要生成并且保存有一份DataTable,而作用仅仅是为了实例化一个DataRow,从实际效果上来看,这样的代价实在太大了,因为DataTable本身是一个重对象,实例化它,保存它都需要比较大时间和空间上的损耗。最开始就是这样设计的,但是当前我从数据库读取了30000多条数据的时候,机器已经的内存,CPU已经到极限了,也就是这样的设计是不合理的。那是不是可以在数据类里定义一个公共的数据容器,同一个数据类生成的对象都使用这个容器,这样性能上的损耗就可以降到最低。原来生成10000个对象,就要创建10000个DataTable,而现在就只要生成一个,而且不管在什么情况下都只要生成一个。
2{
3 private static DataTable m_dataTable = BuildSchema();
4 private DataRow m_dataRow;
5
6 public DataObject()
7 {
8 m_dataRow = m_dataTable.NewRow();
9 }
10 protected static DataTable BuildSchema()
11 {
12 DataTable m_dataTable = new DataTable();
13 DataColumn m_dc = new DataColumn("ID", typeof(int));
14 m_dataTable.Columns.Add(m_dc);
15 }
16 public int ID
17 {
18 get
19 {
20 if (m_dataRow["ID"] == DBNull.Value)
21 {
22 return 0;
23 }
24 return (int)m_dataRow["ID"];
25 }
26 set
27 {
28 m_dataRow["ID"] = value;
29 }
30 }
31}
如果单单是定义一个数据对象,这样(代码4)的设计完全是可以。但是有一个问题是Datatable的对象是静态的,因为每个数据类的结构都是不一样的,这样对我就无法实现多态,而多态对于后面要定义的数据集合是至关重要的,因为数据集合对于每个数据类都是一样的,只需要定义一个数据集合而不用每个数据类对应一个数据集合。要实现上面的功能,并且还要实现多态,最终的数据类定义为:
2public abstract class DataObjectBase : ISerializable
3{
4 private DataTable m_dataTable;
5 private DataRow m_dataRow;
6
7 /// <summary>
8 /// Initializes a new instance of the <see cref="T:DataObjectBase"/> class.
9 /// </summary>
10 public DataObjectBase()
11 {
12 }
13 /// <summary>
14 /// </summary>
15 /// <value></value>
16 public System.Data.DataRow ObjectRow
17 {
18 get
19 {
20 return m_dataRow;
21 }
22 set
23 {
24 m_dataRow = value;
25 }
26 }
27 /// <summary>
28 /// Initializes a new instance of the <see cref="T:DataObjectBase"/> class.
29 /// <remarks>反序列化构造函数</remarks>
30 /// </summary>
31 /// <param name="info">The info.</param>
32 /// <param name="context">The context.</param>
33 protected DataObjectBase(SerializationInfo info, StreamingContext context)
34 {
35 DataTable dt = info.GetValue("DataTable", typeof(DataTable)) as DataTable;
36 this.m_dataRow = dt.Rows[0];
37 }
38 ISerializable Members
49
50 public DataTable DataContainer
51 {
52 get { return m_dataTable; }
53 protected set { m_dataTable = value; }
54 }
55}
这是一个数据类的抽象基类,让它实现ISerializable接口,支持序列化,子类不需做任何事就可以支持序列化了,至于序列化,对每一个对象的序列,只要序列对应的那个DataRow就行了。(当然了Serializable属性是必不可少的。)
2 /// Initializes a new instance of the <see cref="T:DemoDataObject"/> class.
3 /// </summary>
4 public DemoDataObject()
5 : base()
6 {
7 DataContainer = ObjectSchemaClass.Instance.DataContainer;
8 ObjectRow = DataContainer.NewRow();
9 }
10 /// <summary>
11 /// Initializes a new instance of the <see cref="T:DemoDataObject"/> class.
12 /// </summary>
13 /// <param name="p_dataRow">The p_data row.</param>
14 public DemoDataObject(DataRow p_dataRow)
15 {
16 ObjectRow = p_dataRow;
17 }
18
19 /// <summary>
20 /// 返回序列化构造函数
21 /// </summary>
22 /// <param name="info">The info.</param>
23 /// <param name="context">The context.</param>
24 protected DemoDataObject(SerializationInfo info, StreamingContext context)
25 : base(info, context)
26 {
27 }
28 /// <summary>
29 /// Gets or sets the ID.
30 /// </summary>
31 /// <value>The ID.</value>
32 public int ID
33 {
34 get
35 {
36 if (ObjectRow[STR_Id_FIELD] != null)
37 return (int)ObjectRow[STR_Id_FIELD];
38 return 0;
39 }
40 set { ObjectRow[STR_Id_FIELD] = value; }
41 }
42}
每个数据类里,内嵌一个私有类,将它定义为单实例的,做为数据容器。
以上的就是一个支持数据类与DotNet数据对象无无缝结合的完整设计思路。其间由构思到实现,再到优化,都对数据类结构进行比较大变动,目前比较稳定了,也项目中暂时够用。在Vs2005进行了测试,生成100万个对象,仍然不至于造成的大量损耗,内存占用率与操作的时间都与普通对象相差不会太多。有兴趣的朋以也可以帮我做个测试,发现其中的一些问题,提出修改意见。在此先谢过了。
说到这边,还没有解决一个很大的问题,那就是编写数据类的代码太多了,特别上面的这样设计,代码更是成倍的增长,也就是开发效率的问题。对此,我是使用CodeSmith,自定写一个代码生成模板,用CodeSmith根据数据库中的表的字段定义生成一个与之对应的数据类。利用它就可以做到不用写一行代码就可以定义一个数据类,保证效率的同时,也保证了正确性。
关于数据类的定义,就介绍到这,下次将是介绍如何定义相应的数据集合。
快速索引:
与DotNet数据对象结合的自定义数据对象设计 (二) 数据集合与DataTable