如何在DataTable中更改DataColumn的数据类型?

135

我有:

DataTable Table = new DataTable;
SqlConnection = new System.Data.SqlClient.SqlConnection("Data Source=" + ServerName + ";Initial Catalog=" + DatabaseName + ";Integrated Security=SSPI; Connect Timeout=120");

SqlDataAdapter adapter = new SqlDataAdapter("Select * from " + TableName, Connection);
adapter.FillSchema(Table, SchemaType.Source);
adapter.Fill(Table);

DataColumn column = DataTable.Columns[0];

我想做的是:

假设当前column.DataType.Name"Double",我希望变成"Int32"

我该怎么做?

13个回答

309

在Datatable填充数据后,您无法更改DataType。但是,您可以克隆Data表,更改列类型并从先前的数据表加载数据到克隆表中,如下所示。

DataTable dtCloned = dt.Clone();
dtCloned.Columns[0].DataType = typeof(Int32);
foreach (DataRow row in dt.Rows) 
{
    dtCloned.ImportRow(row);
}

喜欢这个解决方案-优美!但是ImportRow()对我来说似乎不能将字符串值转换为浮点数值。我错过了什么吗? - yangli.liy
3
DataTable.ImportRow() 方法(或其底层存储)使用 IConvertible 接口进行值的转换。请确保相应地设置 DataTable.Locale 属性!(默认为 CultureInfo.CurrentCulture) - Sir Kill A Lot
2
这个不起作用。我正在尝试创建一个MemoryStream列,而不是数据库中的字节数组列。它会报System.ArgumentException错误:值的类型与列的类型不匹配,无法将<System.Byte[]>存储在DATA列中。期望的类型是MemoryStream。 - Mert Serimer
这个不起作用。我正在检索具有“时间戳”列的记录,这些记录最终成为byte[]类型。反过来,在DataGridView组件中,尝试将byte[]类型的列呈现为图像时,它无法正确显示。我有一种将byte[]转换为字符串的方法,但是使用ImportRow时它肯定不会自动发生。它最终会将值转换为字符串文字“System.Byte []”。 - Triynko

36

虽然在填充了DataTable之后无法更改列的类型,但是您可以在调用Fill之前调用FillSchema来更改它。例如,假设第3列是您想要从double转换为Int32的列,您可以使用以下代码:

adapter.FillSchema(table, SchemaType.Source);
table.Columns[2].DataType = typeof (Int32);
adapter.Fill(table);

1
请注意,如果您的适配器命令是存储过程,则似乎无法正常工作。 - Mark Sowul
1
我已经在使用存储过程的 Oracle.MangedDataAccess.Client.OracleDataAdapter 上进行了测试,它可以正常工作。谢谢。 - 3per

32

虽然这是一篇旧帖子,但我想发表我的观点。这里有一个 DataTable 扩展程序,可以逐列将单个列按指定的类型进行转换:

public static class DataTableExt
{
    public static void ConvertColumnType(this DataTable dt, string columnName, Type newType)
    {
        using (DataColumn dc = new DataColumn(columnName + "_new", newType))
        {
            // Add the new column which has the new type, and move it to the ordinal of the old column
            int ordinal = dt.Columns[columnName].Ordinal;
            dt.Columns.Add(dc);
            dc.SetOrdinal(ordinal);

            // Get and convert the values of the old column, and insert them into the new
            foreach (DataRow dr in dt.Rows)
                dr[dc.ColumnName] = Convert.ChangeType(dr[columnName], newType);

            // Remove the old column
            dt.Columns.Remove(columnName);

            // Give the new column the old column's name
            dc.ColumnName = columnName;
        }
    }
}

然后可以这样调用:

MyTable.ConvertColumnType("MyColumnName", typeof(int));
当然可以使用您想要的任何类型,只要列中的每个值实际上都可以转换为新类型。

在将Byte[]转换为字符串类型列时,出现“对象必须实现IConvertible”的错误。 - Harshad Vekariya
1
只需将一些泛型添加到其中public static void ConvertColumnType<T>(this DataTable dt, string columnName, TnewType) where T : Type, IConvertible - T.M.
6
我认为这是最优雅的解决方案,只需避免转换 DBNull,例如:dr[dc.ColumnName] = dr[columnName] == DBNull.Value ? DBNull.Value : Convert.ChangeType(dr[columnName], newType); - miboper
先生,我为这份出色的工作鼓掌! - Nik

12

我采取了不同的方法。我需要解析一个在Excel导入中以OA日期格式呈现的日期时间。这种方法很容易建立......实质上,

  1. 添加所需类型的列
  2. 遍历行并转换值
  3. 删除原始列并将新列重命名为与旧列相匹配

private void ChangeColumnType(System.Data.DataTable dt, string p, Type type){
        dt.Columns.Add(p + "_new", type);
        foreach (System.Data.DataRow dr in dt.Rows)
        {   // Will need switch Case for others if Date is not the only one.
            dr[p + "_new"] =DateTime.FromOADate(double.Parse(dr[p].ToString())); // dr[p].ToString();
        }
        dt.Columns.Remove(p);
        dt.Columns[p + "_new"].ColumnName = p;
    }

谢谢!正是我在寻找的。 - Rahul Singh

10

还要考虑更改返回类型:

select cast(columnName as int) columnName from table

8
Dim tblReady1 As DataTable = tblReady.Clone()

'' convert all the columns type to String 
For Each col As DataColumn In tblReady1.Columns
  col.DataType = GetType(String)
Next

tblReady1.Load(tblReady.CreateDataReader)

5
一旦填充了 DataTable,就无法更改列的类型。
在这种情况下,您最好的选择是在填充 DataTable 之前添加一个 Int32 列:
dataTable = new DataTable("Contact");
dataColumn = new DataColumn("Id");
dataColumn.DataType = typeof(Int32);
dataTable.Columns.Add(dataColumn);

然后,您可以将原始表中的数据克隆到新表中:
DataTable dataTableClone = dataTable.Clone();

这里有一个更详细的帖子,请点击链接

5

如果你想仅更改一个列,例如从字符串到Int32,你可以使用Expression属性:

DataColumn col = new DataColumn("col_int" , typeof(int));
table.Columns.Add(col);
col.Expression = "table_exist_col_string"; // digit string convert to int  

好的答案!不需要使用foreach(...)和检查null值。 - Behzad Ebrahimi

5
我结合了Mark的解决方案效率 - 这样我就不需要克隆整个数据表.Clone - 与泛型和可扩展性,使得我可以定义自己的转换函数。这就是我最终得到的:
/// <summary>
///     Converts a column in a DataTable to another type using a user-defined converter function.
/// </summary>
/// <param name="dt">The source table.</param>
/// <param name="columnName">The name of the column to convert.</param>
/// <param name="valueConverter">Converter function that converts existing values to the new type.</param>
/// <typeparam name="TTargetType">The target column type.</typeparam>
public static void ConvertColumnTypeTo<TTargetType>(this DataTable dt, string columnName, Func<object, TTargetType> valueConverter)
{
    var newType = typeof(TTargetType);

    DataColumn dc = new DataColumn(columnName + "_new", newType);

    // Add the new column which has the new type, and move it to the ordinal of the old column
    int ordinal = dt.Columns[columnName].Ordinal;
    dt.Columns.Add(dc);
    dc.SetOrdinal(ordinal);

    // Get and convert the values of the old column, and insert them into the new
    foreach (DataRow dr in dt.Rows)
    {
        dr[dc.ColumnName] = valueConverter(dr[columnName]);
    }

    // Remove the old column
    dt.Columns.Remove(columnName);

    // Give the new column the old column's name
    dc.ColumnName = columnName;
}

这样,使用起来会更加简单明了,同时也可以进行自定义:
DataTable someDt = CreateSomeDataTable();
// Assume ColumnName is an int column which we want to convert to a string one.
someDt.ConvertColumnTypeTo<string>('ColumnName', raw => raw.ToString());

2

我创建了一个扩展函数,可以更改DataTable的列类型。与克隆整个表并导入所有数据不同,它只克隆列,解析值,然后删除原始列。

    /// <summary>
    /// Changes the datatype of a column. More specifically it creates a new one and transfers the data to it
    /// </summary>
    /// <param name="column">The source column</param>
    /// <param name="type">The target type</param>
    /// <param name="parser">A lambda function for converting the value</param>
    public static void ChangeType(this DataColumn column, Type type, Func<object, object> parser)
    {
        //no table? just switch the type
        if (column.Table == null)
        {
            column.DataType = type;
            return;
        }

        //clone our table
        DataTable clonedtable = column.Table.Clone();

        //get our cloned column
        DataColumn clonedcolumn = clonedtable.Columns[column.ColumnName];

        //remove from our cloned table
        clonedtable.Columns.Remove(clonedcolumn);

        //change the data type
        clonedcolumn.DataType = type;

        //change our name
        clonedcolumn.ColumnName = Guid.NewGuid().ToString();

        //add our cloned column
        column.Table.Columns.Add(clonedcolumn);

        //interpret our rows
        foreach (DataRow drRow in column.Table.Rows)
        {
            drRow[clonedcolumn] = parser(drRow[column]);
        }

        //remove our original column
        column.Table.Columns.Remove(column);

        //change our name
        clonedcolumn.ColumnName = column.ColumnName;
    }
}

你可以这样使用它:
List<DataColumn> lsColumns = dtData.Columns
    .Cast<DataColumn>()
    .Where(i => i.DataType == typeof(decimal))
    .ToList()

//loop through each of our decimal columns
foreach(DataColumn column in lsColumns)
{
    //change to double
    column.ChangeType(typeof(double),(value) =>
    {
        double output = 0;
        double.TryParse(value.ToString(), out output);
        return output;  
    });
}

上述代码将所有十进制列更改为双精度。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接