65.9K
CodeProject 正在变化。 阅读更多。
Home

使用属性的 DataTable 生成器

starIconstarIconstarIcon
emptyStarIcon
starIcon
emptyStarIcon

3.67/5 (3投票s)

2007年1月29日

7分钟阅读

viewsIcon

43343

downloadIcon

483

本文演示了DataTable生成器的第一个版本。这个生成器使用任何类上的自定义属性中的信息,并将该类或其实例转换为System.Data.DataTable。

前言

本文演示了DataTable生成器的第一个版本。这个生成器使用任何类上的自定义属性中的信息,并将该类或其实例转换为System.Data.DataTable

问题

在任何现代应用程序中,您都会处理许多自定义类,例如EmployeeArticleManager,每个类都有自己的属性、成员变量和方法。

通常,您需要一种快速的方法来一次性捕获类的当前值,查看其中包含的当前数据,或者简单地将所有值转换为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()检索方法。

它们每个都返回一个FieldInfoPropertyInfoMethodInfo的数组,我们将其传递给EnumerateMembers()EnumerateMembers()使用辅助函数GetAttribute()来检索DataTableInclude属性。如果属性存在,它会提取该属性,并将MemberInfo(我们正在检查的字段、属性或方法)和DataTableInclude属性传递给DefineColumn()

最后,DefineColumn()

DefineColumn()首先检查给定的DataTableInclude属性是否包含名称。如果没有,它将使用MemberInfo的名称(字段、属性或方法的名称)作为列名。

接下来,它需要知道该成员封装的数据类型。根据它正在处理的成员,这些信息可以在以下位置找到:

  • 对于属性:PropertyType
  • 对于字段:FieldType
  • 对于方法:ReturnType

此类型(例如,stringint64long等)将随后分配给数据列的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
  • 枚举分配给数据表的所有列

对于检索到的每一列,函数执行以下操作:

  1. 检查此列是否在DefineColumns()创建的列表中
  2. 如果是,则检索MemberInfo并使用它来检索当前值
  3. 对于属性或字段,使用GetValue();对于方法,使用Invoke()调用它
  4. 将此返回的值分配给当前列的单元格

就是这样。

为了方便您使用,还有一个第二个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 的关系相关的任何间接、附带、后果性或惩罚性损害赔偿负责。这包括但不限于适销性和特定用途的适用性。

© . All rights reserved.