使用属性的 DataTable 生成器






3.67/5 (3投票s)
2007年1月29日
7分钟阅读

43343

483
本文演示了DataTable生成器的第一个版本。这个生成器使用任何类上的自定义属性中的信息,并将该类或其实例转换为System.Data.DataTable。
前言
本文演示了DataTable生成器的第一个版本。这个生成器使用任何类上的自定义属性中的信息,并将该类或其实例转换为System.Data.DataTable
。
问题
在任何现代应用程序中,您都会处理许多自定义类,例如Employee
、Article
或Manager
,每个类都有自己的属性、成员变量和方法。
通常,您需要一种快速的方法来一次性捕获类的当前值,查看其中包含的当前数据,或者简单地将所有值转换为XML文件。
幸运的是,有一个类已经能够做到这一切:DataTable
。一个DataTable
可以包含数千行、数十列,可以使用网格轻松查看,最后,使用XmlSerializer
,可以轻松地将其转换为XML文件。
唯一的问题是如何将自定义类转换为DataTable
?嗯,这正是Xteq.Data.DataTableGenerator
的用武之地。
Xteq.Data.DataTableGenerator
首先想到的解决方案是定义一个自定义接口,并且每个要转换为DataTable
的类都必须实现它。在该方法中,它应该定义要添加哪些列,它们的数据类型是什么,最后,用行填充数据表。
然而,对于我需要这个生成器的项目来说,这会过于臃肿,而且我也不想为每个类添加另一段代码。
我想要的是这样的
public class CustomClass1
{
//Include this private member
[DataTableInclude]
private string _name = string.Empty;
}
这样,我只需要将自定义属性[DataTableInclude]
应用于应该包含在DataTable
中的成员,而DataTableGenerator
(简称DTG)应该完成其余的工作。
创建这个自定义属性是比较容易的部分。只需创建一个新类,从System.Attribute
派生它,并将AttributeUsage
应用于该属性可以应用的成员上。
由于我们希望能够转换属性、成员变量和函数,因此我们在自定义类DataTableIncludeAttribute
上添加以下内容
[AttributeUsage(AttributeTargets.Property |
AttributeTargets.Field | AttributeTargets.Method)]
public sealed class DataTableIncludeAttribute : System.Attribute
....
反射并创建数据列
有了这个Attribute
类,我们就可以开始创建DTG了。我们需要做的第一件事是定义DataTable
上的列。只有在列到位的情况下,我们才能添加显示数据的行。
要创建DataColumn
,我们基本上只需要知道两件事:列的名称和数据类型。
为了检索这些信息,我们将使用反射。反射是.NET Framework中用于检索有关对象(实际上,仅是对象类型)的信息并使用它的方法。对于我们的目的,我们需要知道在给定类的哪些部分(属性、成员或方法)上应用了我们的自定义属性。
由于DTG将使用检索到的信息来定义列,因此该函数称为DefineColumns
并接受一个Type
public void DefineColumns(Type type)
为了让所有懒惰的程序员的生活更轻松,我们还定义了第二个方法,可以在现有实例上使用
public void DefineColumns(object obj)
{
//get the underlying type of the object
Type type = obj.GetType();
DefineColumns(type);
}
在DefineColumns()
中,我们需要从给定类型中检索所有方法、字段和属性,并检查我们的属性是否已应用于其中任何一个。为此,我们使用type.GetFields()
检索字段,使用type.GetProperties()
检索属性,并使用type.GetMethods()
检索方法。
它们每个都返回一个FieldInfo
、PropertyInfo
或MethodInfo
的数组,我们将其传递给EnumerateMembers()
。EnumerateMembers()
使用辅助函数GetAttribute()
来检索DataTableInclude
属性。如果属性存在,它会提取该属性,并将MemberInfo
(我们正在检查的字段、属性或方法)和DataTableInclude
属性传递给DefineColumn()
。
最后,DefineColumn()
DefineColumn()
首先检查给定的DataTableInclude
属性是否包含名称。如果没有,它将使用MemberInfo
的名称(字段、属性或方法的名称)作为列名。
接下来,它需要知道该成员封装的数据类型。根据它正在处理的成员,这些信息可以在以下位置找到:
- 对于属性:
PropertyType
- 对于字段:
FieldType
- 对于方法:
ReturnType
此类型(例如,string
、int64
、long
等)将随后分配给数据列的DataType
属性。
然后,它将这个新创建的列以及当前的MemberInfo
添加到内部Dictionary
集合(_list
)中,以便以后可以轻松访问。
排序
DTG开始被其他人使用后10分钟内收到的一个要求是:“我如何控制列的顺序?”我最初的反应是说这种排序应该在前端完成。但是,由于DTG应该使生活更轻松,所以它也应该能够控制列的出现顺序。
这种排序是通过一个临时列表(_sort_list
)完成的,该列表由DefineColumns()
和DefineColumn()
使用。该列表存储创建的数据列以及DataTableInclude
属性。一旦DefineColumns()
完成了所有成员的枚举,它就会对_sort_list
进行排序,然后将包含的列添加到数据表中。
此排序获取每个DataTableInclude
属性的SortID
属性,并按升序排序。例如,如果您希望某个成员出现在第一个位置,只需按如下方式应用DataTableInclude
属性
[DataTableInclude(SortID=1)]
private string _name = string.Empty;
由于_sort_list
同时包含创建的数据列和DataTableInclude
属性类,因此您可以通过更改DataColumnAndDataTableIncludeAttributeComparer
轻松实现自己的排序方法。
添加数据
添加完所有列后,我们应该从自定义对象添加包含数据的行。这是通过使用以下方法实现的:
public void Add(object o)
很简单,不是吗?事实上,该函数本身非常简单,因为它只需要执行以下操作:
- 创建一个新的
DataRow
- 枚举分配给数据表的所有列
对于检索到的每一列,函数执行以下操作:
- 检查此列是否在
DefineColumns()
创建的列表中 - 如果是,则检索
MemberInfo
并使用它来检索当前值 - 对于属性或字段,使用
GetValue()
;对于方法,使用Invoke()
调用它 - 将此返回的值分配给当前列的单元格
就是这样。
为了方便您使用,还有一个第二个Add()
方法,它接受一个IEnumerable
并添加该枚举器返回的所有对象。一个快速的例子
ArrayList list = new ArrayList();
//Add some objects
for (int i = 0; i < 30; i++)
{
CustomClass1 cc = new CustomClass1();
cc.Age = i;
cc.Name = "John Doe " + i;
list.Add(cc);
}
generator.Add(list); //adds 30 rows
示例
在此之后,结果到底是什么?嗯,这里有一些代码演示了DTG的使用有多么简单。这是一个我们想在数据网格中显示的自定义类
using System;
using Xteq.Data;
namespace TestAppDataTableGen
{
class CustomClassSimple
{
[DataTableInclude]
private long _myField=13;
[DataTableInclude(SortID=10)]
public string Prop
{
get
{
return (_myField + 10).ToString();
}
}
[DataTableInclude("Ticks")]
protected long GetCurrentTickCount()
{
return DateTime.Now.Ticks;
}
}
}
使用我们定义的DataTableInclude
属性来包含私有字段_myField
,包含属性“Prop
”(应显示在位置10),并且我们还想获取GetCurrentTickCount()
的值,但列名应为“Ticks”。
要显示此类,我们只需要编写
//create our custom classs
CustomClassSimple ccs = new CustomClassSimple();
//create the generator
DataTableGenerator generator = new DataTableGenerator();
//use the current instance to define the columns
generator.DefineColumns(ccs);
//add the current object to the data table
generator.Add(ccs);
//use the grid to display the generated data table
grid1.DataSource = generator.DataTable;
然后数据网格将显示
私有成员注意事项
尽管DTG能够检索私有成员(请参阅上面的示例中的_myField
),但这仅在声明该私有成员的级别上起作用。
例如,如果您创建一个派生自上述示例类的CustomClassSimple2
,则_myField
将不会显示,因为它对CustomClassSimple2
是私有的,因此无法访问。
搜索方法
在创建DTG的新实例时,您还可以传入一个或多个DataTableGeneratorSearch
枚举值。由于反射传递的对象需要一些时间,因此您可能会决定只检查和包含字段。可以通过使用此构造函数来完成
DataTableGenerator generator =
new DataTableGenerator(DataTableGeneratorSearch.Fields);
您也可以像这样将多个选项OR在一起
DataTableGenerator generator =
new DataTableGenerator(DataTableGeneratorSearch.Fields|
DataTableGeneratorSearch.Properties);
DataTable技巧
由于DTG通过DataTable
属性提供了对其底层DataTable
的完全访问权限,因此您可以添加新列,删除现有列,或对其进行任何您想做的操作。
结论
如本文开头所述,这只是DataTableGenerator
的第一个版本。它目前无法处理索引属性、带参数的方法,无法解析任何内部集合(例如,类A
有一个包含类B
的集合的字段_collection
)或基类的私有成员。
但是,使用此代码作为基础,实现这些或其他任何功能都应该很容易。
尽情享用!
免责声明
软件及所有配套文件、数据和材料均按“原样”分发和提供,不附带任何形式的明示或暗示的保证。用户承认,良好的数据处理程序规定,任何程序,包括软件,都必须在使用前与非关键数据进行彻底测试,用户在此承担使用该程序的全部风险。
此外,在任何情况下,XTEQ Systems 或其主要人员、股东、管理人员、员工、关联公司、承包商、子公司或母公司,均不对与使用软件或用户与 XTEQ Systems 的关系相关的任何间接、附带、后果性或惩罚性损害赔偿负责。这包括但不限于适销性和特定用途的适用性。