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

使用泛型和属性将集合转换为数据表

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.96/5 (13投票s)

Aug 22, 2006

2分钟阅读

viewsIcon

137694

downloadIcon

889

本文展示了如何使用特性来解决涉及横切关注点的问题。

引言

下面的逐步示例将演示如何使用面向切面编程(AOP) 将对象集合转换为 DataTable。此特定示例将利用特性、泛型和反射的强大功能,将容器类的集合显式转换为 DataTable。

我应该声明,这篇文章的部分灵感来自于一本关于该主题的简洁明了的书籍:Jason Bock 和 Tom Barnaby 的Applied .NET Attributes

步骤 1:构建您自己的自定义特性类

自定义特性总是从System.Attribute继承,事实上,任何直接或间接从 System.Attribute 继承的类都是特性类。特性类也遵循约定,在类名后附加“Attribute”一词作为后缀。

特性允许您向对象添加元数据,您可以在运行时通过反射读取这些元数据。因此,它们为横切关注点的面向对象问题提供了一个优雅(和细粒度)的解决方案。

我们的第一步是构建一个自定义特性类,它允许我们在运行时获取有关容器类(我们尚未构建)属性的元数据。这种解决方案的优点在于,我们可以使用我们的自定义特性类(在本例中为 ConversionAttribute)来修饰我们决定在以后添加到项目中的任何类。

using System; 
namespace Coreweb.Example
{
   
[AttributeUsage(AttributeTargets.Property)]
   public class ConversionAttribute : Attribute
{
      private bool m_dataTableConversion;
      private bool m_allowDbNull;
      private bool m_keyField; 
            public ConversionAttribute() { } 
            public bool DataTableConversion
      {
         get { return m_dataTableConversion; }
         set { m_dataTableConversion = value; }
      } 
            public bool AllowDbNull
      {
         get { return m_allowDbNull; }
         set { m_allowDbNull = value; }
      } 
      public bool KeyField
      {
         get { return m_keyField; }
         set { m_keyField = value; }
      }
   }
}

步骤 2:构建一个容器类

现在我们创建一个容器类!请注意我是如何使用我们在步骤 1 中创建的特性来修饰这个类的属性的。在步骤 4 中,我们将使用这些信息通过反射来构建一个DataTable

using System;using System.Collections; namespace Coreweb.Example
{
   public class Employee
   {
      private string m_firstName;
      private string m_lastName;
      private DateTime m_birthDate;
      private ArrayList m_aList; 

      public Employee(string firstName, string lastName, DateTime birthDate)
      {
         m_firstName = firstName;
         m_lastName = lastName;
         m_birthDate = birthDate;
      }

      [Conversion(DataTableConversion = true, KeyField = true, 
       AllowDbNull = false)]
      public string FirstName
      {
         get { return m_firstName; }
         set { m_firstName = value; }
      }

      [Conversion(DataTableConversion = true, KeyField = true, 
       AllowDbNull = false)]
      public string LastName
      {
         get { return m_lastName; }
         set { m_lastName = value; }
      } 

      [Conversion(DataTableConversion = true, AllowDbNull = false)]
      public int Age
      {
         get { return this.GetAge(); }
      } 

      [Conversion(DataTableConversion = true, AllowDbNull = true)]
      public DateTime BirthDate
      {
         get { return m_birthDate; }
         set { m_birthDate = value; }
      } 
      // I've included this property to demonstrate how properties that aren't
      // decorated are excluded from our explicit conversion. (Properties 
      // that hold reference types aren't good candidates for inclusion
      // in our DataTable.  If you try to bind this field to the DataGrid 
      // in the ConversionTestHarness you will get a 
// System.Web.HttpException)
public ArrayList AList { get { return m_aList; } set { m_aList = value; } } // This method derives the age of the Employee from his / her birth date. private int GetAge() { int years = DateTime.Now.Year - m_birthDate.Year; if (DateTime.Now.Month < m_birthDate.Month || (DateTime.Now.Month == m_birthDate.Month && DateTime.Now.Day < m_birthDate.Day)) { years--; } return years; } } }

步骤 3:创建一个接口 - IDataTableConverter

出于本示例的目的,我们将把泛型列表(System.Collections.Generic.List)转换为 DataTable。字典的实现可能不同,因此我们希望利用接口的强大功能来抽象出任何特定的实现。

using System;
using System.Collections.Generic;
using System.Data; 

namespace Coreweb.Example
{
   public interface IDataTableConverter<T>
   {
      DataTable GetDataTable(List<T> items);
   }
}

步骤 4:构建一个 DataTableConverter 类

这是将完成所有工作的类。本质上,这个类使用System.Reflection命名空间在运行时查询特性,构建 DataTable 模式并填充它。

using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection; 
 namespace Coreweb.Example
{
   public class DataTableConverter<T> : IDataTableConverter<T>
   {
      private bool m_enforceKeys; 
      public DataTableConverter() { } 
            public DataTableConverter(bool enforceKeys)
      {
         m_enforceKeys = enforceKeys;
      } 
            public DataTable GetDataTable(List<T> items)
      {
         DataTable dt; 
               try
         {
            // Build a table schema from the first element in the collection
            dt = this.ConstructDataTableSchema(items[0]);
         }
         catch (IndexOutOfRangeException ex)
         {
            throw (new ApplicationException(
                      "Cannot convert List of zero length to a " +
                      "DataTable", ex));
         } 
         // If the container is not convertable than throw an 
         // ApplicationException.
         if (dt != null)
         {
            // Create a new row for every item in the collection and fill it.
for (int i = 0; i < items.Count; i++)
{ DataRow dr = dt.NewRow(); Type type = items[i].GetType(); MemberInfo[] members = type.GetProperties(); foreach (MemberInfo member in members) { object[] attributes = member.GetCustomAttributes(true); if (attributes.Length != 0) { foreach (object attribute in attributes) { ConversionAttribute ca = attribute as ConversionAttribute; if (ca != null) { if (ca.DataTableConversion) { string[] nameArray
= member.Name.ToString().Split( Convert.ToChar(" ")); PropertyInfo prop = type.GetProperty(
nameArray[0]); Type valueType = prop.GetValue(items[i],
null).GetType(); dr[nameArray[0]] = prop.GetValue(items[i],null); } } } } } dt.Rows.Add(dr); } return dt; } else { throw new ApplicationException("List items are not convertable."); } } // This method reads the attributes of your container class via // reflection in order to build a schema for the DataTable that you
// will explicitly convert to.
private DataTable ConstructDataTableSchema(T item) { string tableName = string.Empty; List<DTCONVERTERCONTAINER> schemaContainers = new List<DTCONVERTERCONTAINER>(); Type type = item.GetType(); MemberInfo[] members = type.GetProperties(); foreach (MemberInfo member in members) { object[] attributes = member.GetCustomAttributes(true); if (attributes.Length != 0) { foreach (object attribute in attributes) { ConversionAttribute ca = attribute as ConversionAttribute; if (ca != null) { if (ca.DataTableConversion) { // The name of the container class is used to name
// your DataTable
string[] classNameArray =
member.ReflectedType.ToString().Split( Convert.ToChar(".")); tableName = classNameArray[classNameArray.Length- 1]; string name = member.Name.ToString(); PropertyInfo prop = type.GetProperty(name); Type valueType = prop.GetValue(item, null).GetType(); // Each property that is will be a column in our
// DataTable.
schemaContainers.Add(new DTConverterContainer(name, valueType, ca.AllowDbNull, ca.KeyField)); } } } } } if (schemaContainers.Count > 0) { DataTable dataTable = new DataTable(tableName); DataColumn[] dataColumn = new DataColumn[schemaContainers.Count]; // Counts the number of keys that will need to be created int totalNumberofKeys = 0; foreach (DTConverterContainer container in schemaContainers) { if (container.IsKey == true && m_enforceKeys == true) { totalNumberofKeys = totalNumberofKeys + 1; } } // Builds the DataColumns for our DataTable DataColumn[] keyColumnArray = new DataColumn[totalNumberofKeys]; int keyColumnIndex = 0; for (int i = 0; i < schemaContainers.Count; i++) { dataColumn[i] = new DataColumn(); dataColumn[i].DataType = schemaContainers[i].PropertyType; dataColumn[i].ColumnName = schemaContainers[i].PropertyName; dataColumn[i].AllowDBNull = schemaContainers[i].AllowDbNull; dataTable.Columns.Add(dataColumn[i]); if (schemaContainers[i].IsKey == true &&
m_enforceKeys == true) { keyColumnArray[keyColumnIndex] = dataColumn[i]; keyColumnIndex = keyColumnIndex + 1; } } if (m_enforceKeys) { dataTable.PrimaryKey = keyColumnArray; } return dataTable; } return null; } private class DTConverterContainer { private string m_propertyName; private Type m_propertyType; private bool m_allowDbNull; private bool m_isKey; internal DTConverterContainer(string propertyName, Type propertyType, bool allowDbNull, bool isKey) { m_propertyName = propertyName; m_propertyType = propertyType; m_allowDbNull = allowDbNull; m_isKey = isKey; } public string PropertyName { get { return m_propertyName; } set { m_propertyName = value; } } public Type PropertyType { get { return m_propertyType; } set { m_propertyType = value; } } public bool AllowDbNull { get { return m_allowDbNull; } set { m_allowDbNull = value; } } public bool IsKey { get { return m_isKey; } set { m_isKey = value; } } } } }

步骤 5:构建您自己的泛型列表

在这里,我们添加了显式转换运算符,它允许我们的列表转换为 DataTable。如果您想了解更多关于 C# 中的类型转换,我建议您阅读以下(优秀)文章,作者 Rajesh V.S.:Type Conversions

using System;
using System.Collections.Generic;
using System.Data;

namespace Coreweb.Example
{
   public class CoreWebList<T> : List<T> 
   {
      private static bool m_enforceKeysInDataTableConversion;

      public CoreWebList() 
      {
         m_enforceKeysInDataTableConversion = false;
      }    
      
      public bool EnforceKeysInDataTableConversion
      {
         get { return m_enforceKeysInDataTableConversion; }
         set { m_enforceKeysInDataTableConversion = value; }
      }
 
      public static explicit operator DataTable(CoreWebList<T> list)
      {
         IDataTableConverter<T> converter = new DataTableConverter<T>(
            m_enforceKeysInDataTableConversion);

         return converter.GetDataTable(list);
      }
   }
}

步骤 6:构建您的测试工具

在这一步中,我们将用 Employees 填充我们的集合,显式转换为 DataTable,然后绑定到 DataGrid。

<%@ Page Language="C#" AutoEventWireup="true" 
         CodeFile="ConversionTestHarness.aspx.cs"
         Inherits="ConversionTestHarness" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
     "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
   <title>Conversion Test Harness</title>
</head>
<body>
    <form id="form1" runat="server">
      <div>
         <asp:DataGrid ID="dgTest" runat="server"></asp:DataGrid>
      </div>
    </form>
</body>
</html>
using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Text;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls; 
using Coreweb.Example; 

public partial class ConversionTestHarness : System.Web.UI.Page
{
   protected void Page_Load(object sender, EventArgs e)
   {
      this.DemonstrationTest();
   } 
   private void DemonstrationTest()
   {
      CoreWebList coreWebTestList = new CoreWebList(); 
      coreWebTestList.Add(new Employee("Albert", "Einstein", 
         Convert.ToDateTime("3/14/1879")));
      coreWebTestList.Add(new Employee("John", "von Neumann",
Convert.ToDateTime("12/28/1903"))); coreWebTestList.Add(new Employee("Joe", "Finsterwald",
Convert.ToDateTime("1/18/1969"))); coreWebTestList.Add(new Employee("Erwin", "Schrödinger",
Convert.ToDateTime("8/12/1887"))); DataTable dt = (DataTable)coreWebTestList; DataView dv = new DataView(dt); dv.Sort = "BirthDate"; dgTest.DataSource = dv; dgTest.DataBind(); } }

结论

现在您有能力将任何带装饰的容器类从 List 显式转换为 DataTable!希望我也激发了您对面向切面编程的兴趣!

© . All rights reserved.