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






4.96/5 (13投票s)
Aug 22, 2006
2分钟阅读

137694

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!希望我也激发了您对面向切面编程的兴趣!