web-dev-qa-db-fra.com

Convertir DataTable en liste générique en C #

Déni de responsabilité: Je sais que c'est demandé à tellement d'endroits à SO.
Ma requête est un peu différente. 

Langage de codage: C # 3.5

J'ai un DataTable nommé cardsTable qui extrait des données de la base de données et j'ai une classe Cards qui n'ont que quelques propriétés (pas de constructeur)

public class Cards
{
    public Int64 CardID { get; set; }
    public string CardName { get; set; }
    public Int64 ProjectID { get; set; }
    public Double CardWidth { get; set; }
    public Double CardHeight { get; set; }
    public string Orientation { get; set; }
    public string BackgroundImage { get; set; }
    public string Background { get; set; }
}

Je veux insérer les données de cardsTable dans un objet de type List.
Mes données auront des champs nuls et la méthode ne devrait pas se tromper lorsque je convertis les données. La méthode ci-dessous est-elle la meilleure solution?

DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().ToList().ConvertAll(x => new Cards { CardID = (Int64)x.ItemArray[0] });
21
naveen

Vous pourriez en fait raccourcir considérablement. Vous pouvez considérer la méthode d'extension Select() comme un convertisseur de type. La conversion pourrait alors être écrite comme ceci:

List<Cards> target = dt.AsEnumerable()
    .Select(row => new Cards
    {
        // assuming column 0's type is Nullable<long>
        CardID = row.Field<long?>(0).GetValueOrDefault(),
        CardName = String.IsNullOrEmpty(row.Field<string>(1))
            ? "not found"
            : row.Field<string>(1),
    }).ToList();
30
Jeff Mercado

Je pense que toutes les solutions peuvent être améliorées et rendre la méthode plus générale si vous utilisez certaines conventions et réflexions. Supposons que vous nommez vos colonnes dans le datatable comme les propriétés de votre objet. Vous pouvez alors écrire quelque chose qui examine toutes les propriétés de votre objet, puis regardez cette colonne dans le datatable pour mapper la valeur. 

J'ai fait le contraire, c'est-à-dire ... d'IList à datatable, et le code que j'ai écrit peut être consulté à l'adresse: http://blog.tomasjansson.com/convert-datatable-to-generic-list-extension/

Cela ne devrait pas être si difficile de faire l'inverse et il devrait être aussi difficile de surcharger les fonctions afin que vous puissiez fournir des informations sur les propriétés que vous souhaitez inclure ou exclure.

EDIT: Le code pour le faire fonctionner est donc:

public static class DataTableExtensions
{
    private static Dictionary<Type,IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();
    public static IList<PropertyInfo> GetPropertiesForType<T>()
    {
        var type = typeof(T);
        if(!typeDictionary.ContainsKey(typeof(T)))
        {
            typeDictionary.Add(type, type.GetProperties().ToList());
        }
        return typeDictionary[type];
    }

    public static IList<T> ToList<T>(this DataTable table) where T : new()
    {
        IList<PropertyInfo> properties = GetPropertiesForType<T>();
        IList<T> result = new List<T>();

        foreach (var row in table.Rows)
        {
            var item = CreateItemFromRow<T>((DataRow)row, properties);
            result.Add(item);
        }

        return result;
    }

    private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
    {
        T item = new T();
        foreach (var property in properties)
        {
            property.SetValue(item, row[property.Name], null);
        }
        return item;
    }

}

Si vous avez un DataTable, vous pouvez simplement écrire yourTable.ToList<YourType>() et il créera la liste pour vous. Si vous avez un type plus complexe avec des objets imbriqués, vous devez mettre à jour le code. Une suggestion est de simplement surcharger la méthode ToList pour accepter un params string[] excludeProperties qui contient toutes vos propriétés qui ne devraient pas être mappées. Bien sûr, vous pouvez ajouter des vérifications nulles dans la boucle foreach de la méthode CreateItemForRow.

UPDATE: Ajout d'un dictionnaire statique pour stocker le résultat de l'opération de réflexion afin de l'accélérer un peu. Je n'ai pas compilé le code, mais ça devrait marcher :). 

9
Tomas Jansson

Le .ToList () est au mauvais endroit, et si certains champs peuvent être nuls, vous devrez les traiter car ils ne convertiront pas en Int64 s'ils sont nuls

DataTable dt = GetDataFromDB();
List<Cards> target = dt.AsEnumerable().Select(
  x => new Cards { CardID = (Int64)(x.ItemArray[0] ?? 0) }).ToList();
6
Jamiec

Juste une petite simplification. Je n'utilise pas ItemArray:

List<Person> list = tbl.AsEnumerable().Select(x => new Person
                    {
                        Id = (Int32) (x["Id"]),
                        Name = (string) (x["Name"] ?? ""),
                        LastName = (string) (x["LastName"] ?? "")
                    }).ToList();
6
FrenkyB

bien sa solution en une ligne

cela dépend si ou non vous savez les données de la base de données sont toutes valides et ne contiendront rien qui puisse briser ce qui précède

par exemple, un champ nullable lorsque vous ne vous y attendez pas - peut-être en raison d'une jointure gauche dans le fichier SQL qui génère les données.

Donc, si vous avez validé les données auparavant, alors oui, j’allais suggérer un peu de linq, mais vous l’avez trouvée.

Si vous avez besoin de validation, vous devrez probablement parcourir les champs de données, générer votre objet comme ci-dessus et l'ajouter à la collection ... Cela vous permettra également de gérer les erreurs dans une ligne et de traiter le reste.

C'est comme ça que je le vois quand même

(putain je suis venu sur quelque chose afin que mon représentant était 1024)

2
John Nicholas

Arriver tard, mais cela peut être utile. Peut être appelé en utilisant: 

table.Map (); ou appelez avec un Func pour filtrer les valeurs.

Vous pouvez même modifier le nom du mappage entre la propriété type et l'en-tête DataColumn en définissant les attributs de la propriété.

[AttributeUsage(AttributeTargets.Property)]
    public class SimppleMapperAttribute: Attribute
    {
        public string HeaderName { get; set; }
    }


     public static class SimpleMapper
{
    #region properties
    public static bool UseDeferredExecution { get; set; } = true;
    #endregion

#region public_interface  


    public static IEnumerable<T> MapWhere<T>(this DataTable table, Func<T, bool> sortExpression) where T:new()
    {
        var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties())).Where((t)=>sortExpression(t));
        return UseDeferredExecution ? result : result.ToArray();
    }
    public static IEnumerable<T> Map<T>(this DataTable table) where T : new()
    {
        var result = table.Select().Select(row => ConvertRow<T>(row, table.Columns, typeof(T).GetProperties()));
        return UseDeferredExecution ? result : result.ToArray();
    }
    #endregion

#region implementation_details
    private static T ConvertRow<T>(DataRow row, DataColumnCollection columns, System.Reflection.PropertyInfo[] p_info) where T : new()
    {
        var instance = new T();
        foreach (var info in p_info)
        {
            if (columns.Contains(GetMappingName(info))) SetProperty(row, instance, info);             
        }
        return instance;
    }

    private static void SetProperty<T>(DataRow row, T instance, System.Reflection.PropertyInfo info) where T : new()
    {
        string mp_name = GetMappingName(info);
        object value = row[mp_name];
        info.SetValue(instance, value);
    }

    private static string GetMappingName(System.Reflection.PropertyInfo info)
    {
        SimppleMapperAttribute attribute = info.GetCustomAttributes(typeof(SimppleMapperAttribute),true).Select((o) => o as SimppleMapperAttribute).FirstOrDefault();
        return attribute == null ? info.Name : attribute.HeaderName;
    }
#endregion
}
0
falopsy

Voici un moyen simple de convertir une liste générique en c # avec la condition Where

List<Filter> filter = ds.Tables[0].AsEnumerable()
                        .Where(x => x.Field<int>("FilterID") == 5)
                        .Select(row => new Filter
                        {
                            FilterID = row.Field<int>("FilterID"),
                            FilterName = row.Field<string>("FilterName")
                        }).ToList();

Définissez d'abord les propriétés et utilisez-les conformément à 

public class Filter
{
    public int FilterID { get; set; }
    public string FilterName { get; set; }
}

Paquet mis:

using System.Linq;
using System.Collections.Generic;
0
Manjunath Bilwar

Vous pouvez mapper la table de données sur la classe de modèle à l'aide d'une classe générique comme ci-dessous.

Classe générique

 public static class DataTableMappingtoModel
    {
        /// <summary>
        /// Maps Data Table values to coresponded model propertise
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="dt"></param>
        /// <returns></returns>
        public static List<T> MappingToEntity<T>(this DataTable dt) 
        {
            try
            {
                var lst = new List<T>();
                var tClass = typeof (T);
                PropertyInfo[] proInModel = tClass.GetProperties();
                List<DataColumn> proInDataColumns = dt.Columns.Cast<DataColumn>().ToList();
                T cn;
                foreach (DataRow item in dt.Rows)
                {
                    cn = (T) Activator.CreateInstance(tClass);
                    foreach (var pc in proInModel)
                    {


                            var d = proInDataColumns.Find(c => string.Equals(c.ColumnName.ToLower().Trim(), pc.Name.ToLower().Trim(), StringComparison.CurrentCultureIgnoreCase));
                            if (d != null)
                                pc.SetValue(cn, item[pc.Name], null);


                    }
                    lst.Add(cn);
                }
                return lst;
            }
            catch (Exception e)
            {
                throw e;
            }
        }
    }

Classe de modèle

public class Item
{
    public string ItemCode { get; set; }
    public string Cost { get; set; }
    public override string ToString()
    {
        return "ItemCode : " + ItemCode + ", Cost : " + Cost;
    }
}

Créer une table de données  

public DataTable getTable()
{
    DataTable dt = new DataTable();
    dt.Columns.Add(new DataColumn("ItemCode", typeof(string)));
    dt.Columns.Add(new DataColumn("Cost", typeof(string)));
    DataRow dr;
    for (int i = 0; i < 10; i++)
    {
        dr = dt.NewRow();
        dr[0] = "ItemCode" + (i + 1);
        dr[1] = "Cost" + (i + 1);
        dt.Rows.Add(dr);
    }
    return dt;
}

Nous pouvons maintenant convertir ce DataTable en liste comme ci-dessous:

DataTable dt = getTable();
List<Item> lst = dt.ToCollection<Item>();
foreach (Item cn in lst)
{
    Response.Write(cn.ToString() + "<BR/>");
}

L'espoir vous aidera

0
dush88c

Je me suis inspiré de la logique de Tomas Jansson pour inclure un attribut "Ignorer". Cela me permet d'ajouter d'autres attributs à la classe en cours de chargement sans interrompre le chargement du DataTable-To-Class.

Alternativement, j'ai aussi envisagé d'ajouter un paramètre séparé qui contient le nom de colonne réel à lire dans le DataTable. Dans ce cas, au lieu d'utiliser "row [property.Name]", vous utiliseriez alors row [attribut.Name] "ou quelque chose comme ça pour cette propriété particulière.

public static class DataTableExtensions
{
    [AttributeUsage(AttributeTargets.Property, Inherited = false, AllowMultiple = true)]
    public sealed class IgnoreAttribute : Attribute { public IgnoreAttribute() { } }

    private static Dictionary<Type, IList<PropertyInfo>> typeDictionary = new Dictionary<Type, IList<PropertyInfo>>();

    public static IList<PropertyInfo> GetPropertiesForType<T>()
    {
        var type = typeof(T);

        if (!typeDictionary.ContainsKey(typeof(T)))
            typeDictionary.Add(type, type.GetProperties().ToList());

        return typeDictionary[type];
    }

    public static IList<T> ToList<T>(this DataTable table) where T : new()
    {
        IList<PropertyInfo> properties = GetPropertiesForType<T>();
        IList<T> result = new List<T>();

        foreach (var row in table.Rows)
            result.Add(CreateItemFromRow<T>((DataRow)row, properties));

        return result;
    }

    private static T CreateItemFromRow<T>(DataRow row, IList<PropertyInfo> properties) where T : new()
    {
        T item = new T();

        foreach (var property in properties)
        {
            // Only load those attributes NOT tagged with the Ignore Attribute
            var atr = property.GetCustomAttribute(typeof(IgnoreAttribute));
            if (atr == null)
                property.SetValue(item, row[property.Name], null);
        }

        return item;
    }
}
0
Javier Rapoport