用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)。数据类的定义就由:
1
public
class
CommonDataObject
2
{
3
private int id;
4
5
public int ID
6
{
7
get { return id; }
8
set { id = value; }
9
}
10
}

2

3

4

5

6

7

8

9

10

变为类似于:
1
public
class
DataObject
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

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

上面(代码2)的数据类就一个雏形,但是还存在一些问题。
DataRow从哪里来的?因为对DataRow来说,它本身是不能够实例化的,必须通过DataTable的NewRow方法生成一个与DataTable对象数据(表)结构相对应的数据行。那么是不是可以在数据类定义一个DataTable实体,只做为数据一个架构存放在那边。数据类的定义修改为(抽象类的定义,作为一个基类,构造数据结构的方法留子类对实现,以便后面的多态应用):
1
public
abstract
class
AbstractDataObject
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
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

这样(代码3)的定义是可行,并且也能工作。但是一眼看就能看出它的问题,那就是在每个数据类的对象都要生成并且保存有一份
DataTable,而作用仅仅是为了实例化一个DataRow,从实际效果上来看,这样的代价实在太大了,因为DataTable本身是一个重对象,实例化它,保存它都需要比较大时间和空间上的损耗。最开始就是这样设计的,但是当前我从数据库读取了30000多条数据的时候,机器已经的内存,CPU已经到极限了,也就是这样的设计是不合理的。那是不是可以在数据类里定义一个公共的数据容器,同一个数据类生成的对象都使用这个容器,这样性能上的损耗就可以降到最低。原来生成10000个对象,就要创建10000个DataTable,而现在就只要生成一个,而且不管在什么情况下都只要生成一个。
1
public
class
DataObject
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
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

如果单单是定义一个数据对象,这样(代码4)的设计完全是可以。但是有一个问题是
Datatable的对象是静态的,因为每个数据类的结构都是不一样的,这样对我就无法实现多态,而多态对于后面要定义的数据集合是至关重要的,因为数据集合对于每个数据类都是一样的,只需要定义一个数据集合而不用每个数据类对应一个数据集合。要实现上面的功能,并且还要实现多态,最终的数据类定义为:
1
[Serializable]
2
public
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
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

49

50

51

52

53

54

55

这是一个数据类的抽象基类,让它实现
ISerializable接口,支持序列化,子类不需做任何事就可以支持序列化了,至于序列化,对每一个对象的序列,只要序列对应的那个DataRow就行了。(当然了Serializable属性是必不可少的。)
每个数据类里,内嵌一个私有类,将它定义为单实例的,做为数据容器。
以上的就是一个支持数据类与DotNet数据对象无无缝结合的完整设计思路。其间由构思到实现,再到优化,都对数据类结构进行比较大变动,目前比较稳定了,也项目中暂时够用。在
Vs2005进行了测试,生成100万个对象,仍然不至于造成的大量损耗,内存占用率与操作的时间都与普通对象相差不会太多。有兴趣的朋以也可以帮我做个测试,发现其中的一些问题,提出修改意见。在此先谢过了。
说到这边,还没有解决一个很大的问题,那就是编写数据类的代码太多了,特别上面的这样设计,代码更是成倍的增长,也就是开发效率的问题。对此,我是使用
CodeSmith,自定写一个代码生成模板,用CodeSmith根据数据库中的表的字段定义生成一个与之对应的数据类。利用它就可以做到不用写一行代码就可以定义一个数据类,保证效率的同时,也保证了正确性。
关于数据类的定义,就介绍到这,下次将是介绍如何定义相应的数据集合。
1
/// <summary>
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
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

在上篇Blog中介绍了如何定义一个与DataRow相结合的数据类,那么本篇将介绍如何定义一个与DataTable对应的数据集合。
在上篇Blog中介绍了如何定义一个与DataRow相结合的数据类,那么本篇将介绍如何定义一个与DataTable对应的数据集合。
在
DotNet中提供了一个CollectionBase作为我们定义强类型的数据集合的抽象类,在DotNet1.1中要定义一个强类型的数据集合就必须为每一种数据类定义一个对应的数据集合,在2.0中增加了泛型的功能后,这个问题得到了解决。又由于在目前的Ibatisnet版本中还不支持泛型的功能,所以数据集合可以选择从ArrayList或CollectionBase继承下来。但是不管是ArrayList还是CollectionBase都不支持序列化,也是他们都有没有
Serializable属性,那么实现的数据集合也将无法实现序列化,所以这边选择直接实现
IList接口,内部再实例化一个ArrayList做为数据容器。类的定义如下:
public
class ObjectList : IList, ISerializable{}
还有一需要解决的问题是,数据类如何与
DataTable对应起来呢?在数据类定义一个DataTable的属性和内部变量,做为存储数据的容器。
1
private
DataTable m_dataTable
=
null
;
2
/// <summary>
3
/// Gets the data table form.
4
/// </summary>
5
/// <value>The data table form.</value>
6
public
DataTable DataTableForm
7
{
8
get { return m_dataTable; }
9
}
10

2

3

4

5

6

7

8

9

10

因为前面有提到了,一个对象对应一个
DataRow,当前一个对象单独存在时,它的数据存放在数据类的数据容器里,而当中被加到一个集合里时,数据行就应该存放在数据集合对应的DataTable里了。那要怎么转换呢?很简单,只需要把数据行从原来(数据类的数据容器)的表里面复制到ObjectList对应的DataTable里,然后将原来的行删除就行了。这里复制一行数据也是有学问的,因为一个DataRow一旦创建了,它就只能被加入到创建它的那个DataTable里,当它被加入到另外一个表里就会抛出它同时只能属于一个表的异常,而如果是DataRow里的数据一列一列地复制到另一个DataRow,就会对性能造成非常大的影响。最理想的办法就是数据不动,只改变DataRow记录的一个状态信息就行了。这里的改变行状态的办法就是NewRow,而数据不动的办法就是将源Row的ItemArray属性赋值给目的的ItemArray。经过测试,这样做可以减少1倍以上的时间成本(内存成本也一样)。
具体的添加方法如下:
同时增加一个反序列化的构造函数
1
protected
ObjectList(SerializationInfo info, StreamingContext context) :
base
()
2
{
3
m_dataTable = info.GetValue("DataTable", typeof(DataTable)) as DataTable;
4
Type m_dataObjectType = info.GetValue("DataObjectType", typeof(Type)) as Type;
5
if (m_dataObjectType != null)
6
{
7
foreach (DataRow m_dr in m_dataTable.Rows)
8
{
9
m_list.Add(Activator.CreateInstance(m_dataObjectType, m_dr));
10
}
11
}
12
}
13

2

3

4

5

6

7

8

9

10

11

12

13

这样就可以支持序列化与反序列化了。
整个数据类的定义如下:
1
[Serializable]
2
public
class
ObjectList1 : IList, ISerializable
3
{
4
private ArrayList m_list = null;
5
private DataTable m_dataTable = null;
6
/// <summary>
7
/// Initializes a new instance of the <see cref="T:ObjectList"/> class.
8
/// </summary>
9
public ObjectList1()
10
{
11
m_list = new ArrayList();
12
}
13
/// <summary>
14
/// Gets the data table form.
15
/// </summary>
16
/// <value>The data table form.</value>
17
public DataTable DataTableForm
18
{
19
get { return m_dataTable; }
20
}
21
private void AddObjectRowToListTable(IDataObject p_dataObject)
22
{
23
if (m_dataTable == null)
24
m_dataTable = p_dataObject.DataContainer.Clone();
25
DataRow m_newRow = m_dataTable.NewRow();
26
27
28
m_newRow.ItemArray = p_dataObject.ObjectRow.ItemArray;
29
/***********************************************
30
* 使用上面代码时间性能会比下面的高一倍
31
* for (int i = 0; i < p_dataObject.ObjectRow.ItemArray.Length; i++)
32
{
33
m_newRow[i] = p_dataObject.ObjectRow.ItemArray[i];
34
}
35
* ********************************************/
36
37
m_dataTable.Rows.Add(m_newRow);
38
p_dataObject.ObjectRow.Delete();
39
p_dataObject.ObjectRow.Table.AcceptChanges();
40
p_dataObject.ObjectRow = m_newRow;
41
}
42
IList Members
163
164
#region ICollection Members
165
166
/// <summary>
167
/// Copies the elements of the <see cref="T:System.Collections.ICollection"></see> to an <see cref="T:System.Array"></see>, starting at a particular <see cref="T:System.Array"></see> index.
168
/// </summary>
169
/// <param name="array">The one-dimensional <see cref="T:System.Array"></see> that is the destination of the elements copied from <see cref="T:System.Collections.ICollection"></see>. The <see cref="T:System.Array"></see> must have zero-based indexing.</param>
170
/// <param name="index">The zero-based index in array at which copying begins.</param>
171
/// <exception cref="T:System.ArgumentNullException">array is null. </exception>
172
/// <exception cref="T:System.ArgumentOutOfRangeException">index is less than zero. </exception>
173
/// <exception cref="T:System.ArgumentException">array is multidimensional.-or- index is equal to or greater than the length of array.-or- The number of elements in the source <see cref="T:System.Collections.ICollection"></see> is greater than the available space from index to the end of the destination array. </exception>
174
/// <exception cref="T:System.InvalidCastException">The type of the source <see cref="T:System.Collections.ICollection"></see> cannot be cast automatically to the type of the destination array. </exception>
175
public void CopyTo(Array array, int index)
176
{
177
this.m_list.CopyTo(array, index);
178
}
179
180
/// <summary>
181
/// Gets the number of elements contained in the <see cref="T:System.Collections.ICollection"></see>.
182
/// </summary>
183
/// <value></value>
184
/// <returns>The number of elements contained in the <see cref="T:System.Collections.ICollection"></see>.</returns>
185
public int Count
186
{
187
get { return this.m_list.Count; }
188
}
189
190
/// <summary>
191
/// Gets a value indicating whether access to the <see cref="T:System.Collections.ICollection"></see> is synchronized (thread safe).
192
/// </summary>
193
/// <value></value>
194
/// <returns>true if access to the <see cref="T:System.Collections.ICollection"></see> is synchronized (thread safe); otherwise, false.</returns>
195
public bool IsSynchronized
196
{
197
get { return false; }
198
}
199
200
/// <summary>
201
/// Gets an object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"></see>.
202
/// </summary>
203
/// <value></value>
204
/// <returns>An object that can be used to synchronize access to the <see cref="T:System.Collections.ICollection"></see>.</returns>
205
public object SyncRoot
206
{
207
get { return this.m_list.SyncRoot; }
208
}
209
210
#endregion
211
212
IEnumerable Members
226
227
protected ObjectList1(SerializationInfo info, StreamingContext context)
228
{
229
m_dataTable = info.GetValue("DataTable", typeof(DataTable)) as DataTable;
230
Type m_dataObjectType = info.GetValue("DataObjectType", typeof(Type)) as Type;
231
if (m_dataObjectType != null)
232
{
233
m_list = new ArrayList();
234
foreach (DataRow m_dr in m_dataTable.Rows)
235
{
236
m_list.Add(Activator.CreateInstance(m_dataObjectType,m_dr));
237
}
238
}
239
}
240
ISerializable Members
253
}

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

209

210

211

212

226

227

228

229

230

231

232

233

234

235

236

237

238

239

240

253

上面的数据类肯定还存在一些问题,目前没有更深入地去研究,有些地方可能还不是很合理,随着应用的深入,仍需要做一些改进。最后,如果认为这样使用很不爽,不能做到强类型的引用。所有的对象都只能认识到
DataObjectBase这一级类型(因为所有的数据类都是这个抽象里继承下来)。我们可以定义一个泛型的集合对它进行包装,这里就不多做介绍了。
1
private
void
AddObjectRowToListTable(IDataObject p_dataObject)
2
{
3
if (m_dataTable == null)
4
m_dataTable = p_dataObject.DataContainer.Clone();
5
DataRow m_newRow = m_dataTable.NewRow();
6
m_newRow.ItemArray = p_dataObject.ObjectRow.ItemArray;
7
/***********************************************
8
* 使用上面代码时间性能会比下面的高一倍
9
* for (int i = 0; i < p_dataObject.ObjectRow.ItemArray.Length; i++)
10
{
11
m_newRow[i] = p_dataObject.ObjectRow.ItemArray[i];
12
}
13
* ********************************************/
14
15
m_dataTable.Rows.Add(m_newRow);
16
p_dataObject.ObjectRow.Delete();
17
p_dataObject.ObjectRow.Table.AcceptChanges();
18
p_dataObject.ObjectRow = m_newRow;
19
}
20

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

下一个就是序列化的问题了。序列化集合类好像比较麻烦,因为它本身并没有为我提供支持序列化的方法。而在这个数据集合里,实现了
ISerializable接口,由我们自己来定义要序列的方式。我们的关键是序列化数据,所以就这里的序列化就只需要序列
ObjectList对应的DataTable就行了。实现ISerializable接口如下:
1
public
void
GetObjectData(SerializationInfo info, StreamingContext context)
2
{
3
if (this.Count >= 0)
4
{
5
info.AddValue("DataObjectType", m_list[0].GetType()); //当前数据类的类型
6
}
7
8
info.AddValue("DataTable", this.m_dataTable, typeof(DataTable));
9
}

2

3

4

5

6

7

8

9
