¿Cómo cambiar el tipo de datos de una columna de datos en una tabla de datos?

120

Yo tengo:

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];

Lo que quiero hacer es:

Suponga que actualmente column.DataType.Name es "Double" . Quiero que se convierta en "Int32" .

¿Cómo logro esto?

rofans91
fuente

Respuestas:

269

No puede cambiar el tipo de datos después de que la tabla de datos esté llena de datos. Sin embargo, puede clonar la tabla de datos, cambiar el tipo de columna y cargar datos de la tabla de datos anterior a la tabla clonada como se muestra a continuación.

DataTable dtCloned = dt.Clone();
dtCloned.Columns[0].DataType = typeof(Int32);
foreach (DataRow row in dt.Rows) 
{
    dtCloned.ImportRow(row);
}
Akhil
fuente
Me encanta la solución: ¡elegante! Sin embargo, ImportRow () no parece convertir valores de cadena en valores flotantes para mí. ¿Me estoy perdiendo de algo?
yangli.liy
1
DataTable.ImportRow () (o más bien su almacenamiento subyacente) convierte valores mediante la interfaz IConvertible. ¡Asegúrese de establecer la propiedad DataTable.Locale en consecuencia! (el valor predeterminado es CultureInfo.CurrentCulture)
Sir Kill A Lot
Esto no funciona. Estoy tratando de crear una columna de flujo de memoria en lugar de la columna de matriz de bytes de db. Da System.ArgumentException: el tipo de valor no coincide con el tipo de columna. No se pudo almacenar <System.Byte []> en la columna de datos. El tipo esperado es MemoryStream
Mert Serimer
28

Si bien es cierto que no puede cambiar el tipo de columna después de que DataTablese llena, puede cambiarlo después de llamar FillSchema, pero antes de llamar Fill. Por ejemplo, digamos que la tercera columna es la que desea convertir doublea Int32, podría usar:

adapter.FillSchema(table, SchemaType.Source);
table.Columns[2].DataType = typeof (Int32);
adapter.Fill(table);
rsbarro
fuente
1
Tenga en cuenta que esto no parece funcionar si el comando de su adaptador es un procedimiento almacenado
Mark Sowul
1
Probé esto Oracle.MangedDataAccess.Client.OracleDataAdaptercon un procedimiento almacenado. Esta funcionando. Gracias.
3 del
21

Publicación anterior, pero pensé que lo haría, con una extensión DataTable que puede convertir una sola columna a la vez, a un tipo determinado:

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;
        }
    }
}

Entonces se puede llamar así:

MyTable.ConvertColumnType("MyColumnName", typeof(int));

Por supuesto, use el tipo que desee, siempre que cada valor de la columna pueda convertirse al nuevo tipo.

Marc Ferrold
fuente
Da el error "El objeto debe implementar IConvertible". al convertir Byte [] en una columna de tipo cadena.
Harshad Vekariya
Solo agregue algunos genéricos public static void ConvertColumnType<T>(this DataTable dt, string columnName, TnewType) where T : Type, IConvertible
TM
3
Creo que esta es la solución más elegante, solo evite convertir DBNulls, por ejemplo dr[dc.ColumnName] = dr[columnName] == DBNull.Value ? DBNull.Value : Convert.ChangeType(dr[columnName], newType);
miboper
8

Considere también modificar el tipo de retorno:

select cast(columnName as int) columnName from table
tsilb
fuente
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)
Superna Parajuli
fuente
8

He adoptado un enfoque un poco diferente. Necesitaba analizar una fecha y hora de una importación de Excel que estaba en el formato de fecha OA. Esta metodología es lo suficientemente simple para construir a partir de ... en esencia,

  1. Agregue la columna del tipo que desee
  2. Rasga las filas convirtiendo el valor
  3. Elimine la columna original y cambie el nombre a la nueva para que coincida con la antigua

    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;
        }
John Salewski
fuente
¡Gracias! Exactamente lo que estaba buscando.
Rahul Singh
4

Una vez que DataTablese ha llenado, no puede cambiar el tipo de columna.

Su mejor opción en este escenario es agregar una Int32columna DataTableantes de llenarla:

dataTable = new DataTable("Contact");
dataColumn = new DataColumn("Id");
dataColumn.DataType = typeof(Int32);
dataTable.Columns.Add(dataColumn);

Luego, puede clonar los datos de su tabla original a la nueva tabla:

DataTable dataTableClone = dataTable.Clone();

Aquí hay una publicación con más detalles .

Josh Earl
fuente
4

si desea cambiar solo una columna. por ejemplo, de cadena a int32, puede usar la propiedad Expression:

DataColumn col = new DataColumn("col_int" , typeof(int));
table.Columns.Add(col);
col.Expression = "table_exist_col_string"; // digit string convert to int  
mehdi
fuente
¡buena respuesta! no es necesario foreach (...) y verificar valores nulos.
Behzad Ebrahimi
2

Creé una función de extensión que permite cambiar el tipo de columna de un DataTable. En lugar de clonar toda la tabla e importar todos los datos, simplemente clona la columna, analiza el valor y luego elimina el original.

    /// <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;
    }
}

Puedes usarlo así:

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;  
    });
}

El código anterior cambia todas las columnas decimales a dobles.

Wes Hanney
fuente
1
DataTable DT = ...
// Rename column to OLD:
DT.Columns["ID"].ColumnName = "ID_OLD";
// Add column with new type:
DT.Columns.Add( "ID", typeof(int) );
// copy data from old column to new column with new type:
foreach( DataRow DR in DT.Rows )
{ DR["ID"] = Convert.ToInt32( DR["ID_OLD"] ); }
// remove "OLD" column
DT.Columns.Remove( "ID_OLD" );
Altivo
fuente
1

Combiné la eficiencia de la solución de Mark, por lo que no tengo que .Cloneusar toda la DataTable , con genéricos y extensibilidad, para poder definir mi propia función de conversión . Esto es con lo que terminé:

/// <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;
}

De esta manera, el uso es mucho más sencillo, aunque también personalizable:

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());
Marcell Toth
fuente
0

Puedes agregar una columna con tipo de dato distinto, luego copiar los datos y eliminar la columna anterior

TB.Columns.Add("columna1", GetType(Integer))    
TB.Select("id=id").ToList().ForEach(Sub(row) row("columna1") = row("columna2"))    
TB.Columns.Remove("columna2")
Jhonny Espinoza
fuente
1
Debe agregar algo de contexto a su respuesta, la respuesta de solo código no tiene valor ya que se consideran de baja calidad.
Nawed Nabi Zada