web-dev-qa-db-ja.com

C#でDataTableを汎用リストに変換

免責事項:私は、SOの非常に多くの場所で質問されたことを知っています。
私のクエリは少し異なります。

コーディング言語:C#3.5

DBからデータを取得するcardsTableという名前のDataTableがあり、いくつかのプロパティのみを持つクラスCardsがあります(コンストラクターはありません)

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

リストのタイプのオブジェクトにcardsTableデータを挿入したい。
データにはnullフィールドが含まれるため、データを変換するときにメソッドがエラーになりません。以下の方法が最善の方法ですか?

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

実際にかなり短くすることができます。 Select()拡張メソッドを型コンバーターと考えることができます。変換は次のように記述できます。

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();
34
Jeff Mercado

何らかの慣習とリフレクションを使用すれば、すべてのソリューションが改善され、方法がより一般的になると思います。データテーブルの列にオブジェクトのプロパティと同じ名前を付けた場合、オブジェクトのすべてのプロパティを調べてデータテーブルでその列を調べて値をマップする何かを書くことができます。

私は反対のことをしました、つまり... IListからデータテーブルまで、そして私が書いたコードは以下で見ることができます: http://blog.tomasjansson.com/convert-datatable-to-generic-list-extension /

逆方向に行くのはそれほど難しいことではなく、関数をオーバーロードして、含めるプロパティまたは除外するプロパティの情報を提供できるようにする必要があります。

EDIT:それを機能させるコードは次のとおりです。

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

}
_

DataTableがある場合は、yourTable.ToList<YourType>()と書くだけでリストが作成されます。ネストされたオブジェクトを持つより複雑なタイプがある場合は、コードを更新する必要があります。 1つの提案は、ToListメソッドをオーバーロードして、マップすべきではないすべてのプロパティを含む_params string[] excludeProperties_を受け入れることです。もちろん、foreachメソッドのCreateItemForRowループにnullチェックを追加できます。

UPDATE:リフレクション操作の結果を保存する静的辞書を追加して、少し速くしました。私はコードをコンパイルしていませんが、動作するはずです:)。

11
Tomas Jansson

ほんの少し単純化。私はItemArrayを使用しません:

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

.ToList()は間違った場所にあり、一部のフィールドがnullになる可能性がある場合、それらがnullの場合はInt64に変換されないため、これらを処理する必要があります。

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

よくその一行ソリューション

knowデータベース内のデータはすべて有効であり、上記を壊すものは何も含まれていないかどうかによって異なります

たとえば、予期しない場合はnull値を許可するフィールド-データを生成する左結合int eh sqlが原因である可能性があります。

その前にデータを検証したことがあるなら、私はlinqを提案するつもりでした-しかし、あなたは落ちました。

ただし、何らかの検証が必要な場合は、おそらくデータ行をループして、上記のようにオブジェクトを生成し、コレクションに追加する必要があります...これにより、1行でエラーを処理し、残りを処理することもできます。

とにかく私はそれを見る方法

(いまいましいことに私は何かを下票することにしたので、私の担当者は1024人でした)

2
John Nicholas

以下のような汎用クラスを使用して、データテーブルをモデルクラスにマップできます。

汎用クラス

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

モデルクラス

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

Create DataTable

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

これで、このDataTableを以下のようにリストに変換できます

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

希望があなたを助ける

1
dush88c

遅くなりますが、これは便利です。以下を使用して呼び出すことができます。

table.Map();または、Funcで呼び出して値をフィルタリングします。

プロパティの属性を設定することで、typeプロパティとDataColumnヘッダー間のマッピング名を変更することもできます。

[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

Where条件を使用してc#で汎用リストに変換する簡単な方法を次に示します。

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

最初にプロパティを定義し、使用する

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

パッケージを置く:

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

Tomas Janssonのロジックの上に「Ignore」属性を含めるように構築しました。これにより、DataTable-To-Classのロード自体を壊すことなく、ロードされるクラスに他の属性を追加できます。

または、DataTableから読み取られる実際の列名を保持する別のパラメーターを追加することも検討しました。その場合、「row [property.Name]」を使用する代わりに、row [attribute.Name]」またはそのようなプロパティをその特定のプロパティに使用します。

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