レベルエンター山本大のブログ

面白いプログラミング教育を若い人たちに

BLOCKVROCKリファレンス目次はこちら

アンチ型付DataSet派の自作ORマッピング

VisualStudio2005を使ってるが、

「型付DataSet」にはいろいろ不満がある。


自動的に作られるのはいいけど、

接続文字列まで内部に持ってたり、

時々壊れて、XSDビューで開けなかったり、

TableAdapterに書き込んだクエリの

メンテナンスをしようと思うと、

いちいち、重たいXSDビューを開かなくてはならずイライラする。


また、TableAdapterを必死につくったのに、DBスキーマの変更があったときには結局コンバートできず

ウィザードやら、プロパティビューやらをこつこつ叩いて作り直した。


メモリ上のデータの保存場所としても普通のDataTableのほうが使いやすい。

例えば、一意制約が邪魔になることも多かった。

正直、もう2度とごめんだとおもった。

(たとえ、僕の作り方が悪かったんだとしても。。。)



ただ、データを型付きのオブジェクトで扱えるのは嬉しい。

結局、1行分のDataRowをオブジェクトにマッピングできれば、

十分、型付であることメリットは得られる。

リフレクション使えば、以下の程度の仕様の型付Rowはできそうだ。

データテーブルおよび、カラム名に一致するフィールドを持つPojoを渡せば、Pojoにデータを詰め込んで返してくれる。

これによって、データの型変換を省力化でき、コード補完によってカラムを思い出せる。


以下の「DataTableUtil.cs」がORMと呼べる部分のすべてだ。

あとは、DBカラムと同じフィールドを持つPOJO(ここでは「User.cs」)を作ればおしまい。

■DataTableUtil.cs

using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Reflection;
namespace DataSet
{
    class DataTableUtil<T> where T : new()
    {
        DataTable dt;
        private int cursor = 0;

        public DataTableUtil(DataTable dt)
        {

            this.dt = dt;
        }

        public T GetRowData(int rowIndex)
        {
            T rowObject = new T();
            Type objType = rowObject.GetType();
            FieldInfo[] fis = objType.GetFields();
            foreach (FieldInfo fi in fis)
            {
                fi.SetValue(rowObject, dt.Rows[rowIndex][fi.Name]);
            }
            return rowObject;
        }

        public T NextRow()
        {
            T ret = GetRowData(cursor);
            cursor++;
            return ret;
        }

        public bool hasRow()
        {
            return (this.dt.Rows.Count != cursor);
        }

    }
}

DBのカラムと同じフィールドを持つPojoを作る。

Propertyを使えばカプセル化もできるだろう。

(上記のDataTableUtilはプロパティには対応してないけど)

■User.cs

using System;
using System.Collections.Generic;
using System.Text;

namespace DataSet
{
    class User
    {
        public int ID;
        public String Name;
        public DateTime BirthDay;
        public Decimal Salary;
    }
}


実験用アプリはWindows Formアプリでつくった。
button1_Clickが、上記の仕組みを使ったコード
button2_Clickは、ノーマルなDataTableを使う方法。

コード量はそんなに変わらないけど、
補完が効くので、実装上は効率アップが実感できた。


private DataTable createDatatable()
{
    ////////////////////////////
    // データテーブルを作る
    ////////////////////////////
    DataTable dt = new DataTable();
    dt.Columns.AddRange(
        new DataColumn[]{
            new DataColumn("ID",typeof(Int32)),
            new DataColumn("Name",typeof(String)),
            new DataColumn("BirthDay",typeof(DateTime)),
            new DataColumn("Salary",typeof(Decimal))
        });

    // データ1件目
    DataRow row1 = dt.NewRow();
    row1["ID"] = "1";
    row1["Name"] = "山本";
    row1["BirthDay"] = new DateTime(1977, 5, 24);
    row1["Salary"] = 10;
    dt.Rows.Add(row1);

    // データ2件目
    DataRow row2 = dt.NewRow();
    row2["ID"] = "2";
    row2["Name"] = "亀野";
    row2["BirthDay"] = new DateTime(1978, 3, 23);
    row2["Salary"] = 20;
    dt.Rows.Add(row2);
    return dt;
}


private void button1_Click(object sender, EventArgs e)
{
    DataTable dt = createDatatable();
    DataTableUtil<User> util = new DataTableUtil<User>(dt);
    StringBuilder sb = new StringBuilder();
    while (util.hasRow())
    {
        User user = util.NextRow();
        sb.AppendLine(user.ID + ":" + user.Name + ":" + user.BirthDay + ":" + user.Salary);
    }
    Console.WriteLine(sb.ToString());
}

private void button2_Click(object sender, EventArgs e)
{
    DataTable dt = createDatatable();
    StringBuilder sb = new StringBuilder();
    foreach (DataRow row in dt.Rows)
    {
        int id = (int)row["ID"];
        string name = (String)row["Name"];
        DateTime birthDay = (DateTime)row["BirthDay"];
        Decimal salary = (Decimal)row["Salary"];
        sb.AppendLine(id + ":" + name + ":" + birthDay + ":" + salary);
    }
    Console.WriteLine(sb.ToString());
}

ありがた迷惑で横柄なFWよりも、

車輪を再発明してでも、ちゃんと自分でコントロールできるほうがいい。