【C#】DataTableを自作ClassのListに変換する汎用関数【現場の備忘録】

プログラム

DataTabale型で取得したデータはList型に変換した方が使いやすい。

だけど複数あるDataTabaleとListをそれぞれ1個ずつ…

/ データテーブルを用意
DataTable dt = CreateTable();
// クラスリストを用意
List<Entity> entityList = new List<Entity>();
Entity entity = new Entity();
// 一個ずつテーブルから自作クラスに移していく
entity.HINMEI = dt.Rows[0]["HINMEI"].ToString();
・
・
・

…なんてやってらんないので、任意なClassで使えるDataTableをList化する汎用関数を用意していく。

List<自作Class>への変換処理

    /// <summary>
    /// DataTableをEntityListに変換
    /// </summary>
    /// <typeparam name="T">任意のclass名</typeparam>
    /// <param name="dt">任意のDataTable</param>
    /// <returns></returns>
    public static List<T> ToEntityList<T>(DataTable dt)
    {
        var result = new List<T>();
        if (dt == null) return result;

        foreach (DataRow dr in dt.Rows)
        {
            try
            {
                // テーブルの行毎にマッピング処理を施していく
                result.Add(ToEntity<T>(dr));
            }
            catch (Exception ex)
            {
                break;
            }
        }
        return result;
    }
    /// <summary>
    /// DataRowをEntityに変換(マッピング処理)
    /// </summary>
    /// <typeparam name="T">任意のclass名</typeparam>
    /// <param name="dr">任意のDataRow</param>
    /// <returns></returns>
    public static T ToEntity<T>(DataRow dr)
    {
        // 任意のクラス型のオブジェクトを生成
        var result = (T?)Activator.CreateInstance(typeof(T));
        // 生成されたオブジェクトがnullの場合はデフォルト値をreturn
        if (result == null) return default(T)!;
        // 渡された引数がnullの場合もreturn
        if (dr == null) return result;

        foreach (DataColumn col in dr.Table.Columns)
        {
            // カラムとプロパティとマッピングしていく
            var colName = col.ColumnName;
            PropertyInfo? propertyInfo = result.GetType().GetProperty(colName);
            if (propertyInfo != null && propertyInfo.CanWrite)
            {
                propertyInfo.SetValue(result, dr[colName]);
            }
        }
        return result;
    }

やっていることはコメントで書いてある通り。

コピペですぐ使えます。

注意点はDataTableのcolumn名とclassの中の変数名、あとはそれぞれの型を合わせるということ。

DataTableの方の「TOSHI_NO」はint型だけど、classの方の「TOSHI_NO」はstring型、ってなるとエラーになるので注意。

実装例

下記の例はVisualStudio2022.Net8.0、作成プロジェクトはコンソールアプリ、OSはWindows11で実装。

Program.csとEntity.csのクラスファイルを作成してもらえればあとはコピペで確認できるはず。

処理としては 「CreateTable()」でテーブルデータを作成して自作クラスのEntity」のリストである「entityList」に今回紹介した変換処理「ToEntityList<Entity>(dt)」で変換しています。

Program.cs
using System.Data;
using System.Reflection;

class Program
{
    static void Main()
    {
        // データテーブルを用意
        DataTable dt = CreateTable();
        // クラスリストを用意
        List<Entity> entityList = new List<Entity>();

        // DataTableを指定クラスのList化
        entityList = ToEntityList<Entity>(dt);

        // コンソールへの表示
        foreach (Entity en in entityList)
        {
            string row = string.Empty;

            Console.WriteLine($"{en.TOSHI_NO} {en.HINMEI}");
        }
    }
    /// <summary>
    /// データテーブル作成
    /// </summary>
    /// <returns></returns>
    static public DataTable CreateTable() 
    {
        DataTable dt = new DataTable();

        dt.Columns.Add("TOSHI_NO", typeof(int));
        dt.Columns.Add("HINMEI", typeof(string));
        dt.Columns.Add("JYURYO", typeof(decimal));
        dt.Columns.Add("IRISU", typeof(int));
        dt.Columns.Add("NYUKO_YMD", typeof(string));
        dt.Columns.Add("SHUKKO_YMD", typeof(string));

        dt.Rows.Add(1, "りんご", 1.1, 3, "20240101", "20240110");
        dt.Rows.Add(2, "マスカット", 1.7, 6, "20240201", "");
        dt.Rows.Add(3, "いちご", 0.1, 2, "20240303", "20240303");
        dt.Rows.Add(4, "なし", 2.1, 2, "20240325", "");
        dt.Rows.Add(5, "社員", 54.5, 1, "20240401", "20240425");

        return dt;
    }
    /// <summary>
    /// DataTableをEntityListに変換
    /// </summary>
    /// <typeparam name="T">任意のclass名</typeparam>
    /// <param name="dt">任意のDataTable</param>
    /// <returns></returns>
    public static List<T> ToEntityList<T>(DataTable dt)
    {
        var result = new List<T>();
        if (dt == null) return result;

        foreach (DataRow dr in dt.Rows)
        {
            try
            {
                // テーブルの行毎にマッピング処理を施していく
                result.Add(ToEntity<T>(dr));
            }
            catch (Exception ex)
            {
                break;
            }
        }
        return result;
    }
    /// <summary>
    /// DataRowをEntityに変換(マッピング処理)
    /// </summary>
    /// <typeparam name="T">任意のclass名</typeparam>
    /// <param name="dr">任意のDataRow</param>
    /// <returns></returns>
    public static T ToEntity<T>(DataRow dr)
    {
        // 任意のクラス型のオブジェクトを生成
        var result = (T?)Activator.CreateInstance(typeof(T));
        // 生成されたオブジェクトがnullの場合はデフォルト値をreturn
        if (result == null) return default(T)!;
        // 渡された引数がnullの場合もreturn
        if (dr == null) return result;

        foreach (DataColumn col in dr.Table.Columns)
        {
            // カラムとプロパティとマッピングしていく
            var colName = col.ColumnName;
            PropertyInfo? propertyInfo = result.GetType().GetProperty(colName);
            if (propertyInfo != null && propertyInfo.CanWrite)
            {
                propertyInfo.SetValue(result, dr[colName]);
            }
        }
        return result;
    }
}
Entity.cs
/// <summary>
/// 自作クラス作成
/// </summary>
public class Entity
{
    public Entity()
    {
        Initialize();
    }

    public int TOSHI_NO { get; set; } = 0;
    public string HINMEI { get; set; } = string.Empty;
    public decimal JYURYO { get; set; } = 0;
    public int IRISU { get; set; } = 0;
    public string NYUKO_YMD { get; set; } = string.Empty;
    public string SHUKKO_YMD { get; set; } = string.Empty;

    public void Initialize() 
    {
        TOSHI_NO = 0;
        HINMEI = string.Empty;
        JYURYO = 0;
        IRISU = 0;
        NYUKO_YMD = string.Empty;
        SHUKKO_YMD = string.Empty;
    }
}

さいごに

例えば「TOSHI_NO」という名前の値を取得したい場合、DataTable型で作成したデータから取得するなら文字列でcolumn名を指定しないといけないので、ビルド時点では誤字脱字に気付きづらくて実行エラーの原因になってしまいがち。

だけど自作クラスのListにしてしまえばそんなことは起きないし扱いやすい。

この変換処理は用意しておいても損はないと思います。

コメント

タイトルとURLをコピーしました