.NET - Generische Sammlung in DataTable konvertieren

76

Ich versuche, eine generische Sammlung (Liste) in eine DataTable zu konvertieren. Ich habe den folgenden Code gefunden, der mir dabei hilft:

// Sorry about indentation
public class CollectionHelper
{
private CollectionHelper()
{
}

// this is the method I have been using
public static DataTable ConvertTo<T>(IList<T> list)
{
    DataTable table = CreateTable<T>();
    Type entityType = typeof(T);
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType);

    foreach (T item in list)
    {
        DataRow row = table.NewRow();

        foreach (PropertyDescriptor prop in properties)
        {
            row[prop.Name] = prop.GetValue(item);
        }

        table.Rows.Add(row);
    }

    return table;
}    

public static DataTable CreateTable<T>()
{
    Type entityType = typeof(T);
    DataTable table = new DataTable(entityType.Name);
    PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(entityType);

    foreach (PropertyDescriptor prop in properties)
    {
        // HERE IS WHERE THE ERROR IS THROWN FOR NULLABLE TYPES
        table.Columns.Add(prop.Name, prop.PropertyType);
    }

    return table;
}
}

Mein Problem ist, dass beim Ändern einer der Eigenschaften von MySimpleClass in einen nullbaren Typ der folgende Fehler angezeigt wird:

DataSet does not support System.Nullable<>.

Wie kann ich dies mit nullbaren Eigenschaften / Feldern in meiner Klasse tun?

Ronnie Overby
quelle

Antworten:

139

Dann müssen Sie sie vermutlich in die nicht nullbare Form heben, verwenden Nullable.GetUnderlyingTypeund möglicherweise einige nullWerte in DbNull.Value...

Ändern Sie die Zuordnung in:

row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;

und beim Hinzufügen der folgenden Spalten:

table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(
            prop.PropertyType) ?? prop.PropertyType);

Und es funktioniert. ( ??ist der Null-Koaleszenz-Operator; er verwendet den ersten Operanden, wenn er nicht Null ist, andernfalls wird der zweite Operand ausgewertet und verwendet)

Marc Gravell
quelle
62
Wenn ich groß bin, möchte ich Marc Gravell sein.
Ronnie Overby
2
Mmmm. Null-Koaleszenz-Operator. So schön.
J. Steen
4
Verwenden Sie in VB IF (Nullable.GetUnderlyingType (prop.PropertyType), prop.PropertyType)
GilShalit
Tolle. Danke Marc!
Carlos Landeras
5

Gut. Da DataSet keine nullbaren Typen unterstützt, müssten Sie überprüfen, ob es sich bei der Eigenschaft um einen generischen Typ handelt, die generische Definition dieses Typs abrufen und dann das Argument (das der tatsächliche Typ ist) mithilfe von verwenden Nullable.GetUnderlyingType. Wenn der Wert null ist, verwenden Sie ihn einfach DBNull.Valueim DataSet.

J. Steen
quelle
5

Wenn Nullable.GetUnderlyingType()Ihre prop.PropertyTypeRückgabe einen Wert ungleich Null hat, verwenden Sie diesen als Typ einer Spalte. Andernfalls verwenden Sie prop.PropertyTypesich.

Anton Gogolev
quelle
4

Ich weiß, dass diese Frage alt ist, aber ich hatte das gleiche Problem mit einer Erweiterungsmethode, die ich erstellt habe. Mit der Antwort von Marc Gravell konnte ich meinen Code ändern. Diese Erweiterungsmethode verarbeitet Listen primitiver Typen, Zeichenfolgen, Aufzählungen und Objekte mit primitiven Eigenschaften.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Linq;
using System.Text;

/// <summary>
/// Converts a List&lt;T&gt; to a DataTable.
/// </summary>
/// <typeparam name="T">The type of the list collection.</typeparam>
/// <param name="list">List instance reference.</param>
/// <returns>A DataTable of the converted list collection.</returns>
public static DataTable ToDataTable<T>(this List<T> list)
{
    var entityType = typeof (T);

    // Lists of type System.String and System.Enum (which includes enumerations and structs) must be handled differently 
    // than primitives and custom objects (e.g. an object that is not type System.Object).
    if (entityType == typeof (String))
    {
        var dataTable = new DataTable(entityType.Name);
        dataTable.Columns.Add(entityType.Name);

        // Iterate through each item in the list. There is only one cell, so use index 0 to set the value.
        foreach (T item in list)
        {
            var row = dataTable.NewRow();
            row[0] = item;
            dataTable.Rows.Add(row);
        }

        return dataTable;
    }
    else if (entityType.BaseType == typeof (Enum))
    {
        var dataTable = new DataTable(entityType.Name);
        dataTable.Columns.Add(entityType.Name);

        // Iterate through each item in the list. There is only one cell, so use index 0 to set the value.
        foreach (string namedConstant in Enum.GetNames(entityType))
        {
            var row = dataTable.NewRow();
            row[0] = namedConstant;
            dataTable.Rows.Add(row);
        }

        return dataTable;
    }

    // Check if the type of the list is a primitive type or not. Note that if the type of the list is a custom 
    // object (e.g. an object that is not type System.Object), the underlying type will be null.
    var underlyingType = Nullable.GetUnderlyingType(entityType);
    var primitiveTypes = new List<Type>
    {
        typeof (Byte),
        typeof (Char),
        typeof (Decimal),
        typeof (Double),
        typeof (Int16),
        typeof (Int32),
        typeof (Int64),
        typeof (SByte),
        typeof (Single),
        typeof (UInt16),
        typeof (UInt32),
        typeof (UInt64),
    };

    var typeIsPrimitive = primitiveTypes.Contains(underlyingType);

    // If the type of the list is a primitive, perform a simple conversion.
    // Otherwise, map the object's properties to columns and fill the cells with the properties' values.
    if (typeIsPrimitive)
    {
        var dataTable = new DataTable(underlyingType.Name);
        dataTable.Columns.Add(underlyingType.Name);

        // Iterate through each item in the list. There is only one cell, so use index 0 to set the value.
        foreach (T item in list)
        {
            var row = dataTable.NewRow();
            row[0] = item;
            dataTable.Rows.Add(row);
        }

        return dataTable;
    }
    else
    {
        // TODO:
        // 1. Convert lists of type System.Object to a data table.
        // 2. Handle objects with nested objects (make the column name the name of the object and print "system.object" as the value).

        var dataTable = new DataTable(entityType.Name);
        var propertyDescriptorCollection = TypeDescriptor.GetProperties(entityType);

        // Iterate through each property in the object and add that property name as a new column in the data table.
        foreach (PropertyDescriptor propertyDescriptor in propertyDescriptorCollection)
        {
            // Data tables cannot have nullable columns. The cells can have null values, but the actual columns themselves cannot be nullable.
            // Therefore, if the current property type is nullable, use the underlying type (e.g. if the type is a nullable int, use int).
            var propertyType = Nullable.GetUnderlyingType(propertyDescriptor.PropertyType) ?? propertyDescriptor.PropertyType;
            dataTable.Columns.Add(propertyDescriptor.Name, propertyType);
        }

        // Iterate through each object in the list adn add a new row in the data table.
        // Then iterate through each property in the object and add the property's value to the current cell.
        // Once all properties in the current object have been used, add the row to the data table.
        foreach (T item in list)
        {
            var row = dataTable.NewRow();

            foreach (PropertyDescriptor propertyDescriptor in propertyDescriptorCollection)
            {
                var value = propertyDescriptor.GetValue(item);
                row[propertyDescriptor.Name] = value ?? DBNull.Value;
            }

            dataTable.Rows.Add(row);
        }

        return dataTable;
    }
}
Halcyon
quelle
Das funktioniert! Aber nur mit nullbaren Typen. Sie müssen die Anweisung "if (zugrunde liegender Typ == null) zugrunde liegender Typ = Entitätstyp;" hinzufügen. vor Ihrem "bool typeIsPrimitive = primitiveTypes.Contains (zugrunde liegender Typ);" Daher funktioniert es auch für List <int>. Ich danke Ihnen für das Teilen!
Krisztián Balla
4

Hier ist eine Version mit einigen Änderungen, um Nullen und '\ 0'-Zeichen zuzulassen, ohne die DataTable in die Luft zu jagen.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Data;

namespace SomeNamespace
{
    public static class Extenders
    {
        public static DataTable ToDataTable<T>(this IEnumerable<T> collection, string tableName)
        {
            DataTable tbl = ToDataTable(collection);
            tbl.TableName = tableName;
            return tbl;
        }

        public static DataTable ToDataTable<T>(this IEnumerable<T> collection)
        {
            DataTable dt = new DataTable();
            Type t = typeof(T);
            PropertyInfo[] pia = t.GetProperties();
            object temp;
            DataRow dr;

            for (int i = 0; i < pia.Length; i++ )
            {
                dt.Columns.Add(pia[i].Name, Nullable.GetUnderlyingType(pia[i].PropertyType) ?? pia[i].PropertyType);
                dt.Columns[i].AllowDBNull = true;
            }

            //Populate the table
            foreach (T item in collection)
            {
                dr = dt.NewRow();
                dr.BeginEdit();

                for (int i = 0; i < pia.Length; i++)
                {
                    temp = pia[i].GetValue(item, null);
                    if (temp == null || (temp.GetType().Name == "Char" && ((char)temp).Equals('\0')))
                    {
                        dr[pia[i].Name] = (object)DBNull.Value;
                    }
                    else
                    {
                        dr[pia[i].Name] = temp;
                    }
                }

                dr.EndEdit();
                dt.Rows.Add(dr);
            }
            return dt;
        }

    }
}
Lander
quelle