312 votes

Convertir une liste générique ou une table énumérative en une table de données ?

J'ai quelques méthodes qui renvoient différentes listes génériques.

Existe-t-il en .net une classe, une méthode statique ou autre pour convertir une liste en table de données ? La seule chose que je peux imaginer est d'utiliser Reflection pour le faire.

Si j'ai ça :

List<Whatever> whatever = new List<Whatever>();

(Ce code suivant ne fonctionne pas bien sûr, mais j'aimerais avoir la possibilité de :

DataTable dt = (DataTable) whatever;

2 votes

Bien sûr, une bonne question serait "pourquoi ?" - alors que List<T> est dans de nombreux cas un meilleur outil que DataTable ;-p Chacun son truc, je suppose...

1 votes

Je pense que celle-ci est peut-être un double de cette question : stackoverflow.com/questions/523153/ Il a même une réponse presque identique :-)

2 votes

@MarcGravell : Mon "pourquoi ?" est la manipulation de List<T> (Traverser les colonnes et les lignes). J'essaie de faire un pivot à partir d'une List<T> et accéder aux propriétés via la réflexion c'est une douleur. Est-ce que je m'y prends mal ?

378voto

Marc Gravell Points 482669

Voici une belle mise à jour de 2013 utilisant FastMember de NuGet :

IEnumerable<SomeType> data = ...
DataTable table = new DataTable();
using(var reader = ObjectReader.Create(data)) {
    table.Load(reader);
}

Ceci utilise l'API de métaprogrammation de FastMember pour une performance maximale. Si vous voulez le restreindre à des membres particuliers (ou faire respecter l'ordre), vous pouvez également le faire :

IEnumerable<SomeType> data = ...
DataTable table = new DataTable();
using(var reader = ObjectReader.Create(data, "Id", "Name", "Description")) {
    table.Load(reader);
}

L'éditeur Dis / revendicateur : FastMember est un projet de Marc Gravell. C'est de l'or et des mouches pleines !


Oui, c'est à peu près l'exact opposé de ce un ; la réflexion suffirait - ou si vous avez besoin de plus rapide, HyperDescriptor dans la version 2.0, ou peut-être Expression en 3.5. En fait, HyperDescriptor devrait être plus que suffisant.

Par exemple :

// remove "this" if not on C# 3.0 / .NET 3.5
public static DataTable ToDataTable<T>(this IList<T> data)
{
    PropertyDescriptorCollection props =
        TypeDescriptor.GetProperties(typeof(T));
    DataTable table = new DataTable();
    for(int i = 0 ; i < props.Count ; i++)
    {
        PropertyDescriptor prop = props[i];
        table.Columns.Add(prop.Name, prop.PropertyType);
    }
    object[] values = new object[props.Count];
    foreach (T item in data)
    {
        for (int i = 0; i < values.Length; i++)
        {
            values[i] = props[i].GetValue(item);
        }
        table.Rows.Add(values);
    }
    return table;        
}

Maintenant, avec une seule ligne, vous pouvez rendre ce processus beaucoup plus rapide que la réflexion (en activant l'option HyperDescriptor pour le type d'objet T ).


Modification de la requête de performance ; voici un banc d'essai avec les résultats :

Vanilla 27179
Hyper   6997

Je soupçonne que le goulot d'étranglement s'est déplacé de l'accès aux membres vers DataTable performance... Je doute que vous puissiez améliorer beaucoup cela...

Code :

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
public class MyData
{
    public int A { get; set; }
    public string B { get; set; }
    public DateTime C { get; set; }
    public decimal D { get; set; }
    public string E { get; set; }
    public int F { get; set; }
}

static class Program
{
    static void RunTest(List<MyData> data, string caption)
    {
        GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
        GC.WaitForPendingFinalizers();
        GC.WaitForFullGCComplete();
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = 0; i < 500; i++)
        {
            data.ToDataTable();
        }
        watch.Stop();
        Console.WriteLine(caption + "\t" + watch.ElapsedMilliseconds);
    }
    static void Main()
    {
        List<MyData> foos = new List<MyData>();
        for (int i = 0 ; i < 5000 ; i++ ){
            foos.Add(new MyData
            { // just gibberish...
                A = i,
                B = i.ToString(),
                C = DateTime.Now.AddSeconds(i),
                D = i,
                E = "hello",
                F = i * 2
            });
        }
        RunTest(foos, "Vanilla");
        Hyper.ComponentModel.HyperTypeDescriptionProvider.Add(
            typeof(MyData));
        RunTest(foos, "Hyper");
        Console.ReadLine(); // return to exit        
    }
}

4 votes

Eh bien, "tel quel", il sera aussi rapide que la réflexion. Si vous activez l'HyperDescriptor, il battra la réflexion à plate couture... Je vais faire un test rapide... (2 minutes)

0 votes

L'expression a été mentionnée pour la version 3.5. Si elle est utilisée, comment affectera-t-elle le code, y a-t-il un exemple ?

0 votes

@MicMit - cela le rendrait plus complexe ;-p En toute sincérité, je pourrait mettre un exemple, mais cela demanderait beaucoup d'efforts - serait-il toujours intéressant ?

292voto

Mary Hamlin Points 1902

J'ai dû modifier l'exemple de code de Marc Gravell pour gérer les types nullables et les valeurs nulles. J'ai inclus une version fonctionnelle ci-dessous. Merci Marc.

public static DataTable ToDataTable<T>(this IList<T> data)
{
    PropertyDescriptorCollection properties = 
        TypeDescriptor.GetProperties(typeof(T));
    DataTable table = new DataTable();
    foreach (PropertyDescriptor prop in properties)
        table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
    foreach (T item in data)
    {
        DataRow row = table.NewRow();
        foreach (PropertyDescriptor prop in properties)
             row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
        table.Rows.Add(row);
    }
    return table;
}

0 votes

C'est une excellente réponse. J'aimerais que cet exemple soit étendu pour gérer une liste de groupes par qui contiendrait une propriété d'élément et dont les colonnes seraient créées de la même manière que ci-dessus.

3 votes

Pour cela, @Jim Beam, modifie la signature de la méthode pour accepter le retour de GroupBy : public static DataTable ToDataTable<TKey, T>(this IEnumerable<IGrouping<TKey, T>> data) Ensuite, ajoutez une colonne supplémentaire avant la boucle foreach : table.Columns.Add("Key", Nullable.GetUnderlyingType(typeof(TKey)) ?? typeof(TKey)); Ajoutez ensuite une boucle autour de la boucle de données où vous itérez les groupes : foreach (IGrouping<TKey, T> group in data) { foreach (T item in group.Items) { Voir ce GIST pour plus de détails : gist.github.com/rickdailey/8679306

0 votes

Hé, y a-t-il un moyen de gérer un objet avec des objets internes ? Je veux juste que les propriétés internes apparaissent en colonnes après celles de l'objet parent.

16voto

Onur Omer Points 77

Une petite modification de La réponse de Marc pour qu'il fonctionne avec des types de valeurs comme List<string> à la table de données :

public static DataTable ListToDataTable<T>(IList<T> data)
{
    DataTable table = new DataTable();

    //special handling for value types and string
    if (typeof(T).IsValueType || typeof(T).Equals(typeof(string)))
    {

        DataColumn dc = new DataColumn("Value", typeof(T));
        table.Columns.Add(dc);
        foreach (T item in data)
        {
            DataRow dr = table.NewRow();
            dr[0] = item;
            table.Rows.Add(dr);
        }
    }
    else
    {
        PropertyDescriptorCollection properties = TypeDescriptor.GetProperties(typeof(T));
        foreach (PropertyDescriptor prop in properties)
        {
            table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
        }
        foreach (T item in data)
        {
            DataRow row = table.NewRow();
            foreach (PropertyDescriptor prop in properties)
            {
                try
                {
                    row[prop.Name] = prop.GetValue(item) ?? DBNull.Value;
                }
                catch (Exception ex)
                {
                    row[prop.Name] = DBNull.Value;
                }
            }
            table.Rows.Add(row);
        }
    }
    return table;
}

0 votes

Comment le faire pour List<int> ?

1 votes

La méthode ci-dessus fonctionnera aussi pour int (et d'autres types de valeurs)... int est un type de valeur. voir : msdn.microsoft.com/fr/us/library/s1ax56ch.aspx

0 votes

J'aime cette méthode car elle ne dépend pas de l'utilisation d'une méthode d'extension. Elle fonctionne bien pour les bases de code plus anciennes qui n'ont peut-être pas accès aux méthodes d'extension.

15voto

Anatole BAUDOUIN Points 1049

Il s'agit d'un simple mélange des solutions. Elle fonctionne avec les types Nullables.

public static DataTable ToDataTable<T>(this IList<T> list)
{
  PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(T));
  DataTable table = new DataTable();
  for (int i = 0; i < props.Count; i++)
  {
    PropertyDescriptor prop = props[i];
    table.Columns.Add(prop.Name, Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType);
  }
  object[] values = new object[props.Count];
  foreach (T item in list)
  {
    for (int i = 0; i < values.Length; i++)
      values[i] = props[i].GetValue(item) ?? DBNull.Value;
    table.Rows.Add(values);
  }
  return table;
}

1 votes

Cette solution est source d'erreurs car elle dépend de l'ordre de déclaration des propriétés dans la classe T.

9voto

Binoj Antony Points 7519

Il n'existe pas de classe de conversion générique intégrée dans la bibliothèque de classes de base du framework .net qui puisse faire cela pour vous.

Mais Voici un site web avec le code pour faire cela en utilisant la réflexion.

Y en voici un autre

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X