【问题标题】:Changing populated DataTable column data types更改填充的 DataTable 列数据类型
【发布时间】:2010-03-29 14:09:50
【问题描述】:

我有一个 System.Data.DataTable,它是通过读取一个 CSV 文件来填充的,该文件将每列的数据类型设置为字符串。

我想将 DataTable 的内容附加到现有的数据库表中 - 目前这是使用 SqlBulkCopy 和 DataTable 作为源来完成的。

但是,需要更改DataTable的列数据类型以匹配目标数据库表的架构,处理空值。

我对 ADO.NET 不是很熟悉,所以一直在寻找一种干净的方式来做这件事?

谢谢。

【问题讨论】:

    标签: c# .net ado.net


    【解决方案1】:

    我编写了这个通用函数来完成这项工作,它非常适合我:

    public static bool ChangeColumnDataType(DataTable table, string columnname, Type newtype)
    {
        if (table.Columns.Contains(columnname) == false)
            return false;
    
        DataColumn column= table.Columns[columnname];
        if (column.DataType == newtype)
            return true;
    
        try
        {
            DataColumn newcolumn = new DataColumn("temporary", newtype);
            table.Columns.Add(newcolumn);
            foreach (DataRow row in table.Rows)
            {
                try
                {
                    row["temporary"] = Convert.ChangeType(row[columnname], newtype);
                }
                catch
                {
                }
            }
            table.Columns.Remove(columnname);
            newcolumn.ColumnName = columnname;
        }
        catch (Exception)
        {
            return false;
        }
    
        return true;
    }
    

    你可以把代码复制到一个类中(这里是MyClass),并以此为例:

    MyClass.ChangeColumnDataType(table, "GEOST", typeof (int));
    

    【讨论】:

    • 唯一的问题 - 新列添加到集合的末尾,例如,如果您将其绑定到具有“autogeneratecolumns=true”的网格,它的序号位置将不同
    • 更正了 :) 如果您不介意,请在我的解决方案中使用它:codecorner.galanter.net/2013/08/02/…
    【解决方案2】:

    在用数据填充 DataColumn 后,您无法更改 DataType。它不是只读属性,但如果您在它已有数据后尝试更改它,则会在运行时收到异常。

    来自documentation

    在列开始存储数据后更改此属性时会生成异常。

    因此,您必须在开始时确保正确的列类型(如果可能),或者创建一个新的DataTable,专门用于从原始DataTable 导入和复制数据。

    您还可以编写一个自定义的 IDataReader 类,该类从您的 DataTable 读取并执行即时转换并将其传递给 SqlBulkCopy - 它会更有效,但它显然不是快速修复。

    【讨论】:

    • 谢谢 - 我意识到一旦填充我就无法更改它。我正在寻找的是一种创建具有正确数据类型的第二个 DataTable,然后从第一个 DataTable 导入和转换数据的方法。不幸的是,第一个 DataTable 不可能使用 String 以外的数据类型。
    • @TonE:我能想到的唯一方法是实现 IDataReader 并使用 DataTable.Load 方法 - 但如果你打算这样做,你也可以只需将阅读器本身用于SqlBulkCopy,而不是创建第二个DataTable
    • 这是一个有趣的建议。是否可以从 DataTableReader 派生,如果可以,您知道我需要重写哪些方法来提供类型更改功能吗?只是想在时间限制的情况下判断它是否合理。
    • @TonE: DataTableReader 是密封的,所以不,你必须从头开始写一个。即使你可以子类化它,你也必须重写几乎所有的方法,所以只做你自己的会更容易。您的IDataReader 实现必须定义自己的列/字段列表(可能只是List<Type>)并在原始字符串数据上使用Convert 类。我不确定,但我认为您还必须提供GetSchemaTable 的有效实现才能使其与SqlBulkCopy 一起使用......这是困难的部分。
    • 感谢您的帮助。我目前没有时间实现这一点,因此修改了 CSV 读取代码以采用类型化的 DataTable 并使用 FillErrorEventHandler 将错误值替换为 null。真的可以在第一个实例中做到这一点!自定义 IDataReader 是个好主意,以后会尝试一下。
    【解决方案3】:

    请务必设置要填充的数据表的数据类型。

    例如:

        DataTable table = new DataTable("countries");
        table.Columns.Add("country_code", typeof (string));
        table.Columns.Add("country_name", typeof (string));
        //...
        //Fill table
    

    或者,如果它们兼容,您可以更改列类型:

    table.Columns["country_code"].DataType = typeof(string);
    

    【讨论】:

    • 是的,这就是我目前正在做的事情,但实际上用正确设置的类型填充第二个 DataTable 是很困难的。有没有简单的方法来做到这一点?
    【解决方案4】:

    如果您从 csv 文件填充,则首先在数据表中读取模式,然后更改列的数据类型,然后填充表。 示例:我正在使用 XML 文件导入数据。

           DataSet dstemp = new DataSet();
           dstemp.ReadXmlSchema(@"D:\path of file\filename.xml");
           dstemp.Tables[0].Columns["Student_id"].DataType = typeof(Guid);
           dstemp.ReadXml(@"D:\path of file\filename.xml");
    

    我认为它应该适合你。

    【讨论】:

      【解决方案5】:

      就像“Eddie Monge Jr”或“Gisway”无法获得它。

      但列顺序正确。

      public static bool ChangeColumnDataType(DataTable table, string columnname, Type newtype){
          if (table.Columns.Contains(columnname) == false)
              return false;
      
          DataColumn column = table.Columns[columnname];
          if (column.DataType == newtype)
              return true;
      
          try{
              DataColumn newcolumn = new DataColumn("temporary", newtype);
              table.Columns.Add(newcolumn);
      
              foreach (DataRow row in table.Rows){
                  try{
                      row["temporary"] = Convert.ChangeType(row[columnname], newtype);
                  }
                  catch{}
              }
              newcolumn.SetOrdinal(column.Ordinal);
              table.Columns.Remove(columnname);
              newcolumn.ColumnName = columnname;
          }
          catch (Exception){
              return false;
          }
      
          return true;
      }
      

      【讨论】:

        【解决方案6】:

        我创建了Gisway's/Yuri Galanter's 解决方案的改进版本,它解决了以下几点:

        • Don't eat exceptions,早点失败
        • 保留原列的可空性/AllowDBNull
        • 直接使用列对象,不需要表对象作为参数
        • 将命名更改为典型的 .Net 约定
        • 改进文档
        • 在临时列名中包含一个 guid 以真正避免冲突
        • 重构为扩展方法

        由于我需要的项目是在 VB.Net 中,所以我的解决方案也是在其中编写(和测试)的,抱歉 - 不过转换应该不难。

        ' following methods will be defined in a module, which is why they aren't Shared
        ' based on https://codecorner.galanter.net/2013/08/02/ado-net-datatable-change-column-datatype-after-table-is-populated-with-data/ 
        ' and https://stackoverflow.com/a/15692087/1200847 
        
        ''' <summary> 
        ''' Converts DataType of a DataTable's column to a new type by creating a copy of the column with the new type and removing the old one. 
        ''' </summary> 
        ''' <param name="table">DataTable containing the column</param> 
        ''' <param name="columnName">Name of the column</param> 
        ''' <param name="newType">New type of the column</param> 
        <Extension()> 
        Public Sub ChangeColumnDataType(table As DataTable, columnName As String, newType As Type) 
            If Not table.Columns.Contains(columnName) Then Throw New ArgumentException($"No column of the given table is named ""{columnName}"".") 
            Dim oldCol As DataColumn = table.Columns(columnName) 
            oldCol.ChangeDataType(newType) 
        End Sub 
        
        ''' <summary> 
        ''' Converts DataType of a DataTable's column to a new type by creating a copy of the column with the new type and removing the old one. 
        ''' </summary> 
        ''' <param name="column">The column whichs type should be changed</param> 
        ''' <param name="newType">New type of the column</param> 
        <Extension()> 
        Public Sub ChangeDataType(column As DataColumn, newType As Type) 
            Dim table = column.Table 
            If column.DataType Is newType Then Return 
        
            Dim tempColName = "temporary-327b8efdb7984e4d82d514230b92a137" 
            Dim newCol As New DataColumn(tempColName, newType) 
            newCol.AllowDBNull = column.AllowDBNull 
        
            table.Columns.Add(newCol) 
            newCol.SetOrdinal(table.Columns.IndexOf(column)) 
        
            For Each row As DataRow In table.Rows 
                row(tempColName) = Convert.ChangeType(row(column), newType) 
            Next 
            table.Columns.Remove(column) 
            newCol.ColumnName = column.ColumnName 
        End Sub
        

        如果您有一个 int 列,它确实应该是一个 bool 列,请像这样使用它:

        table.Columns("TrueOrFalse").ChangeDataType(GetType(Boolean)) 
        

        重要提示:由于这会更改 DataTable,您可能希望在加载数据后立即执行此操作并在之后接受更改。这种方式更改跟踪、数据绑定等之后可以正常工作:

        table.AcceptChanges()
        

        如果在加载数据时未正确配置列的不可为空性,就像我的 Oracle NUMBER(1,0) NOT NULL 列的情况一样,您可能需要插入如下代码:

        table.Columns("TrueOrFalse").AllowDBNull = False 
        table.Columns("TrueOrFalse").DefaultValue = 0
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-05-03
          • 1970-01-01
          • 1970-01-01
          • 2018-12-01
          • 1970-01-01
          • 2010-10-28
          • 1970-01-01
          相关资源
          最近更新 更多