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

如何手动创建类型化DataTable

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (47投票s)

2008年10月28日

CPOL

6分钟阅读

viewsIcon

268482

downloadIcon

3356

当您重写 GetRowType 时,还需要重写 NewRowFromBuilder()。

引言

我写这篇文章是因为网上关于使用 DataTable.GetRowType() 方法的信息不多,而且我找到的代码示例要么是完全错误的,要么是不完整的。此外,似乎没有任何自动化工具可以仅创建类型化的 DataTable——相反,有用于创建类型化 DataSet 的工具。最后,我不得不创建一个类型化 DataSet 来弄清楚我在手动创建类型化 DataTable 时哪里做错了。因此,这是一篇关于我所学内容的初学者文章,目的是提供一个示例和正确的信息作为他人的资源。我没有提供创建类型化 DataTable 的工具,这也许是未来文章的内容。

什么是类型化 DataTable?

类型化 DataTable 允许您创建一个特定的 DataTable,其中已初始化了所需的列、约束等。类型化 DataTable 通常还使用类型化 DataRow,这允许您通过属性名访问字段。因此,而不是

DataTable personTable=new DataTable();
personTable.Columns.Add(new DataColumn("LastName"));
personTable.Columns.Add(new DataColumn("FirstName"));

DataRow row=personTable.NewRow();
row["LastName"]="Clifton";
row["FirstName"]="Marc";

使用类型化 DataTable 会看起来像这样

PersonTable personTable=new PersonTable();
PersonRow row=personTable.GetNewRow();
row.LastName="Clifton";
row.FirstName="Marc";

类型化 DataTable 的优势与类型化 DataSet 的优势相同:您拥有类型化的 DataTableDataRow,并且您使用属性而不是字符串来设置/获取行中的值。此外,通过使用类型化的 DataRow,字段值(在 DataRow 中是 object 类型)可以在属性 getter 中被自动强制转换为正确的类型。这提高了代码的可读性,并消除了不当构造和字段名拼写错误的可能性。

创建类型化 DataTable

要创建类型化 DataTable,请创建自己的派生自 DataTable 的类。例如

public class PersonTable : DataTable
{
}

有两个方法需要重写:GetRowType() 和 NewRowFromBuilder()。这篇文章的重点在于,我花了大约四个小时才弄清楚需要重写第二个方法。

protected override Type GetRowType()
{
  return typeof(PersonRow);
}

protected override DataRow NewRowFromBuilder(DataRowBuilder builder)
{
  return new PersonRow(builder);
}

第二个方法至关重要。如果不提供它,在尝试创建新行时会遇到关于“数组类型不匹配”的异常。我花了几个小时才弄明白这一点!

创建类型化 DataRow

接下来,您需要一个类型化 DataRow 来定义上面引用的 PersonRow 类型。

public class PersonRow : DataRow
{
}

构造函数

考虑到上面调用的 NewRowFromBuilder,构造函数参数是显而易见的,但不太明显的是,构造函数必须标记为 protectedinternal,因为 DataRow 构造函数被标记为 internal

public class PersonRow : DataRow
{
  internal PersonRow(DataRowBuilder builder) : base(builder)
  {
  }
}

填写详细信息

接下来,我将展示类型化 DataTableDataRow 的基本知识。这些方法和属性的目的是利用类型化 DataRow 来避免在需要 DataTable 的代码中进行类型转换。

PersonTable 方法

构造函数

在构造函数中,我们可以添加定义表的列和约束。

public class PersonTable : DataTable
{
  public PersonTable()
  {
    Columns.Add(new DataColumn("LastName", typeof(string)));
    Columns.Add(new DataColumn("FirstName", typeof(string)));
  }
}

以上是一个简单的示例,并没有说明如何创建主键、设置字段约束等。

索引器

您可以实现一个返回类型化 DataRow 的索引器

public PersonRow this[int idx]
{
  get { return (PersonRow)Rows[idx]; }
}

索引器是在类型化 DataTable 上实现的,因为我们无法重写 Rows 属性上的索引器。边界检查可以留给 .NET 框架的 Rows 属性。非类型化 DataRow 的典型用法看起来像这样

DataRow row=someTable.Rows[n];

而类型化 DataRow 的索引器看起来像这样

PersonRow row=personTable[n];

不理想,因为它看起来像我在索引一个表数组。另一种方法是实现一个属性,也许命名为 PersonRows;然而,这将需要实现一个 PersonRowsCollection 并将 Rows 集合复制到类型化集合中,这在每次索引 Rows 集合时很可能会对性能产生重大影响。这甚至更不理想!

Add

Add 方法应该接受类型化的 DataRow。这可以防止我们将行添加到另一个表中。如果您尝试使用非类型化 DataTable 执行此操作,会在运行时出现错误。类型化 Add 方法的优点是您会收到编译器错误,而不是运行时错误。

public void Add(PersonRow row)
{
  Rows.Add(row);
}

移除

类型化的 Remove 方法具有与上面类型化 Add 方法相同的优点

public void Remove(PersonRow row)
{
  Rows.Remove(row);
}

GetNewRow

这里,如果我们尝试使用 DataTable.NewRow() 方法,就会出现冲突,因为唯一不同的是返回类型,而不是方法签名(参数)。因此,我们可以写

public new PersonRow NewRow()
{
  PersonRow row = (PersonRow)NewRow();

  return row;
}

然而,我个人反对使用“new”关键字来覆盖基类的行为。因此,我更喜欢完全不同的方法名

public PersonRow GetNewRow()
{
  PersonRow row = (PersonRow)NewRow();

  return row;
}

PersonRow 属性

类型化的 DataRow 应包含 PersonTable 构造函数中定义的列的属性

public string LastName
{
  get {return (string)base["LastName"];}
  set {base["LastName"]=value;}
}

public string FirstName
{
  get {return (string)base["FirstName"];}
  set {base["FirstName"]=value;}
}

这里的优点是我们可以使用属性名(任何拼写错误都会导致编译器错误),可以使用 Intellisense,并且可以在应用程序中而不是在这里进行类型转换。此外,如果需要,我们还可以添加验证和属性更改事件。这里也是处理 DBNull 到 null 的转换以及从 null 到 DBNull 的转换的好地方,如果我们使用可空类型,我们可以为属性 getter/setter 添加更智能的功能。

PersonRow 构造函数

您可能希望在构造函数中初始化字段

public class PersonRow : DataRow
{
  internal PersonRow(DataRowBuilder builder) : base(builder)
  {
    LastName=String.Empty;
    FirstName=String.Empty;
  }
}

行事件

如有必要,您可能希望实现类型化的行事件。常见的行事件是

  • ColumnChanged
  • ColumnChanging
  • RowChanged
  • RowChanging
  • RowDeleted
  • RowDeleting

我们将查看其中一个事件 RowChanged 来说明类型化事件。

定义委托

首先,我们需要一个合适类型的委托

public delegate void PersonRowChangedDlgt(PersonTable sender, 
                     PersonRowChangedEventArgs args);

请注意,此委托定义了类型化的参数。

事件

现在我们可以将事件添加到 PersonTable 类中

public event PersonRowChangedDlgt PersonRowChanged;

定义事件参数类

我们还需要一个类型化的事件参数类,因为我们要使用我们的类型化 PersonRow

public class PersonRowChangedEventArgs
{
  protected DataRowAction action;
  protected PersonRow row;

  public DataRowAction Action
  {
    get { return action; }
  }

  public PersonRow Row
  {
    get { return row; }
  }

  public PersonRowChangedEventArgs(DataRowAction action, PersonRow row)
  {
    this.action = action;
    this.row = row;
  }
}

重写 OnRowChanged 方法

我们不需要添加 RowChanged 事件处理程序,而是可以重写 OnRowChanged 方法,并为新方法 OnPersonRowChanged 创建类似的模式。请注意,我们仍然调用基类 DataTableRowChanged 实现。这些方法被添加到 PersonDataTable 类中。

protected override void OnRowChanged(DataRowChangeEventArgs e)
{
  base.OnRowChanged(e);
  PersonRowChangedEventArgs args = 
    new PersonRowChangedEventArgs(e.Action, (PersonRow)e.Row);
  OnPersonRowChanged(args); 
}

protected virtual void OnPersonRowChanged(PersonRowChangedEventArgs args)
{
  if (PersonRowChanged != null)
  {
    PersonRowChanged(this, args);
  }
}

请注意,上面的方法是 virtual 的,因为这是 .NET 框架中引发事件的模式,并且遵循此模式是个好习惯。

添加一个类型化事件就需要这么多工作,所以您可以看出,有一个代码生成器会非常有帮助。

使用事件

这是一个简单的示例,说明如何使用类型化的 DataTable 和事件

class Program
{
  static void Main(string[] args)
  {
    PersonTable table = new PersonTable();
    table.PersonRowChanged += new PersonRowChangedDlgt(OnPersonRowChanged);
    PersonRow row = table.GetNewRow();
    table.Add(row);
  }

  static void OnPersonRowChanged(PersonTable sender, PersonRowChangedEventArgs args)
  {
    // This is silly example only for the purposes of illustrating using typed events.
    // Do not do this in real applications, because you would never use this Changed event
    // to validate row fields!
    if (args.Row.LastName != String.Empty)
    {
      throw new ApplicationException("The row did not initialize " + 
                "to an empty string for the LastName field.");
    }
  }
}

然而,这说明了类型化 DataTable 和类型化 DataRow 的优点:可读性和编译器对正确用法的检查。

结论

希望这篇文章清楚地说明了如何手动创建类型化 DataTable。“我发现”(在互联网上其他地方找不到)的“发现”是,当您重写 GetRowType() 时,您还需要重写 NewRowFromBuilder()

© . All rights reserved.