Comment modifier le type de données d'un DataColumn dans un DataTable?

120

J'ai:

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

Ce que je veux faire, c'est:

Supposons qu'actuellement, column.DataType.Name est "Double" . Je veux qu'il devienne "Int32" .

Comment y parvenir?

rofans91
la source

Réponses:

269

Vous ne pouvez pas modifier le DataType une fois que le Datatable est rempli de données. Cependant, vous pouvez cloner la table de données, modifier le type de colonne et charger les données de la table de données précédente vers la table clonée comme indiqué ci-dessous.

DataTable dtCloned = dt.Clone();
dtCloned.Columns[0].DataType = typeof(Int32);
foreach (DataRow row in dt.Rows) 
{
    dtCloned.ImportRow(row);
}
Akhil
la source
J'adore la solution - Élégante! Cependant, ImportRow () ne semble pas convertir les valeurs de chaîne en valeurs flottantes pour moi. Est-ce que j'ai râté quelque chose?
yangli.liy
1
DataTable.ImportRow () (ou plutôt son stockage sous-jacent) convertit les valeurs en utilisant l'interface IConvertible. Assurez-vous de définir la propriété DataTable.Locale en conséquence! (La valeur par défaut est CultureInfo.CurrentCulture)
Sir Kill A Lot
Cela ne fonctionne pas. J'essaie de créer une colonne memorystream au lieu de la colonne de tableau d'octets à venir db. Cela donne à System.ArgumentException: le type de valeur a une incompatibilité avec le type de colonne Impossible de stocker <System.Byte []> dans la colonne de données. Le type attendu est MemoryStream
Mert Serimer
28

S'il est vrai que vous ne pouvez pas changer le type de colonne une fois que le DataTableest rempli, vous pouvez le changer après l'appel FillSchema, mais avant l'appel Fill. Par exemple, supposons que la 3ème colonne est celui que vous voulez convertir doubleà Int32, vous pouvez utiliser:

adapter.FillSchema(table, SchemaType.Source);
table.Columns[2].DataType = typeof (Int32);
adapter.Fill(table);
rsbarro
la source
1
Notez que cela ne semble pas fonctionner si la commande de votre adaptateur est une procédure stockée
Mark Sowul
1
J'ai testé cela Oracle.MangedDataAccess.Client.OracleDataAdapteravec une procédure stockée. Ça marche. Merci.
3 au
21

Ancien article, mais je pensais que je peserais, avec une extension DataTable qui peut convertir une seule colonne à la fois, en un type donné:

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

Il peut alors être appelé comme ceci:

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

Bien sûr, en utilisant le type de votre choix, à condition que chaque valeur de la colonne puisse être convertie dans le nouveau type.

Marc Ferrold
la source
Donne l'erreur "L'objet doit implémenter IConvertible." lors de la conversion de Byte [] en colonne de type chaîne.
Harshad Vekariya
Ajoutez simplement quelques génériques public static void ConvertColumnType<T>(this DataTable dt, string columnName, TnewType) where T : Type, IConvertible
TM
3
Je pense que c'est la solution la plus élégante, évitez simplement de convertir les DBNulls, par exemple dr[dc.ColumnName] = dr[columnName] == DBNull.Value ? DBNull.Value : Convert.ChangeType(dr[columnName], newType);
miboper
8

Pensez également à modifier le type de retour:

select cast(columnName as int) columnName from table
Tsilb
la source
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
la source
8

J'ai adopté une approche un peu différente. J'avais besoin d'analyser une date / heure à partir d'une importation Excel qui était au format de date OA. Cette méthodologie est assez simple pour construire à partir de ...

  1. Ajouter la colonne du type souhaité
  2. Extrayez les lignes en convertissant la valeur
  3. Supprimer la colonne d'origine et renommer la nouvelle pour correspondre à l'ancienne

    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
la source
Merci! Exactement ce que je cherchais.
Rahul Singh
4

Une fois DataTablea rempli, vous ne pouvez pas changer le type d'une colonne.

Votre meilleure option dans ce scénario est d'ajouter une Int32colonne à la DataTableavant de la remplir:

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

Ensuite, vous pouvez cloner les données de votre table d'origine vers la nouvelle table:

DataTable dataTableClone = dataTable.Clone();

Voici un article avec plus de détails .

Josh Earl
la source
4

si vous souhaitez modifier uniquement une colonne, par exemple de string en int32, vous pouvez utiliser la propriété 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
la source
bonne réponse! pas besoin de foreach (...) et de vérifier les valeurs nulles.
Behzad Ebrahimi
2

J'ai créé une fonction d'extension qui permet de changer le type de colonne d'un DataTable. Au lieu de cloner la table entière et d'importer toutes les données, il clone simplement la colonne, analyse la valeur puis supprime l'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;
    }
}

Vous pouvez l'utiliser comme ceci:

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

Le code ci-dessus change toutes les colonnes décimales en doubles.

Wes Hanney
la source
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
la source
1

J'ai combiné l'efficacité de la solution de Mark - donc je n'ai pas besoin de .Clonela totalité du DataTable - avec les génériques et l'extensibilité, pour que je puisse définir ma propre fonction de conversion . C'est ce avec quoi j'ai fini:

/// <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 cette façon, l'utilisation est beaucoup plus simple, tout en étant également personnalisable:

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
la source
0

Puedes agregar una columna con tipo de dato distinto, luego copiar los datos y elimar 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
la source
1
Vous devez ajouter un peu de contexte à votre réponse, la réponse en code seul n'a aucune valeur car elle est considérée comme de faible qualité.
Nawed Nabi Zada ​​le