T4を使ったテーブルからクラスの生成

#はじめに
Dapperを使いデータの入出力をするとき、いちいちクラスを定義するのが面倒なので、T4を使って簡単にクラスを生成できないかと調べサンプルを作成した。

#テーブルからクラスを生成するサンプル
##データベース接続文字列の定義
Definitin.xmlにデータベース接続文字列の定義を行う

```xml:Definition.xml
<?xml version="1.0" encoding="utf-8" ?>
<Definisions>
  <ConnectionString name="CStr1" string="Data Source=LOCALHOST;Initial Catalog=Catalog1;Integrated Security=True" />
  <ConnectionString name="CStr2" string="Data Source=LOCALHOST;Initial Catalog=Catalog2;Integrated Security=True" />
</Definisions>
```
##各テーブル毎に作成するttファイル
変更する箇所は「TargetDB」、「Table」のみで、できるだけ簡略化を行った。

```text:EFile.tt
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output encoding="UTF-8" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ assembly name="System.Data" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ output extension=".cs" #>

<#
    var TargetDB = "CStr1";
    var Table = "Ｅファイル";

#>
<#@ include file="TableToClass.ttinclude" #>
```
##テーブルからクラスを作成する共通ttファイル

```text:TableToClass.ttinclude
<#
    var file = this.Host.ResolvePath("Definition.xml");

    var xdoc = XDocument.Load(file);
    var defs = from def in xdoc.Descendants("ConnectionString")
                select new
                {
                    Name = def.Attribute("name").Value,
                    ConnStr = def.Attribute("string").Value,
                };

    var connstr = defs.Where(r => r.Name == TargetDB)
        .Select(r => r.ConnStr).Single();

	var list = new List<Colums>();
    using (var conn = new SqlConnection(connstr))
    {
        conn.Open();
        var cmd = new SqlCommand(string.Format(Colums.SQL, Table), conn);
        using (var rd = cmd.ExecuteReader())
        {
            while(rd.Read())
				list.Add(new Colums(rd));
		}
	}

#>

namespace DapperClassLibrary
{
    /// <summary>
    /// <#= Table #>より生成されたクラス
    /// </summary>
    public partial class <#= Table #>Table
    {
<#
    foreach(var col in list)
    {
#>
		<#= col.GetProperty() #>
<#
    }
#>
		public static string SQL =
@"
SELECT <#= string.Join(",", list.Select(r => r.COLUMN_NAME).ToArray()) #>
	FROM <#= Table #>
";

		public void SetDataSample(string[] fields)
		{
<#
	var i = 0;
    foreach(var col in list)
    {
#>
			<#= col.GetSetData(i++) #>
<#
    }
#>
		}
    }

}

<#+
    class Colums {
        public string TABLE_NAME { get; set; }
        public string COLUMN_NAME { get; set; }
        public int ORDINAL_POSITION { get; set; }
        public string IS_NULLABLE { get; set; }
        public string DATA_TYPE { get; set; }
        public int? CHARACTER_MAXIMUM_LENGTH { get; set; }
        public byte? NUMERIC_PRECISION { get; set; }
        public int? NUMERIC_SCALE { get; set; }

        public static string SQL =
@"
SELECT TABLE_NAME, COLUMN_NAME, ORDINAL_POSITION, IS_NULLABLE, DATA_TYPE, 
    CHARACTER_MAXIMUM_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = '{0}'
ORDER BY ORDINAL_POSITION
";

        public Colums()
        {

        }

        public Colums(SqlDataReader rd)
        {
            TABLE_NAME = rd.GetString(0);
            COLUMN_NAME = rd.GetString(1);
            ORDINAL_POSITION = rd.GetInt32(2);
            IS_NULLABLE = rd.GetString(3);
            DATA_TYPE = rd.GetString(4);
            CHARACTER_MAXIMUM_LENGTH = rd.IsDBNull(5) ? 
                (int?)null : rd.GetInt32(5);
            NUMERIC_PRECISION = rd.IsDBNull(6) ?
                (byte?)null : rd.GetByte(6);
            NUMERIC_SCALE = rd.IsDBNull(7) ?
                (int?)null : rd.GetInt32(7);
        }

        public bool IsNullable()
        {
            var type = dicType[DATA_TYPE];

            return (dicNullabel[type] && IS_NULLABLE == "YES");
        }

        public string GetSetData(int i)
        {
            var type = dicType[DATA_TYPE];

            var fld = string.Format("fields[{0}]", i);

            if (!IsNullable())
            {
                if (type == "string")
                    return string.Format("{0} = {1};", COLUMN_NAME, fld);

                return string.Format("{0} = {1}.Parse({2});", 
                    COLUMN_NAME, type, fld);
            }

            return string.Format(
                "{0} = string.IsNullOrEmpty({2}) ? ({1}?)null : {1}.Parse({2});",
                COLUMN_NAME, type, fld);
        }

        public string GetProperty()
        {
            var type = dicType[DATA_TYPE];

            return string.Format("public {0}{1} {2} ",
                type, GetNullable(), COLUMN_NAME) + "{ get; set; }";
        }

        private string GetNullable()
        {
            return IsNullable() ? "?" : "";
        }

        private static Dictionary<string, bool> dicNullabel =
                new Dictionary<string, bool>()
        {
            {"string", false},
            {"int", true},
            {"byte", true},
            {"short", true},
            {"decimal", true},
            {"bool", true},
        };

        private static Dictionary<string, string> dicType =
                new Dictionary<string, string>()
        {
            {"char", "string"},
            {"varchar", "string"},
            {"nvarchar", "string"},
            {"nchar", "string"},
            {"int", "int"},
            {"tinyint", "byte"},
            {"smallint", "short"},
            {"decimal", "decimal"},
            {"bit", "bool"},
        };
    }
#>
```
##生成されたファイル

```csharp:EFile.cs
namespace DapperClassLibrary
{
	/// <summary>
    /// EFileより生成されたクラス
    /// </summary>
    public partial class EFileTable
    {
		public string 施設コード { get; set; }
		public string データ識別番号 { get; set; }
		public string 退院年月日 { get; set; }
		public string 入院年月日 { get; set; }
		public string データ区分 { get; set; }
		public decimal? 順序番号 { get; set; }
		public string 病院点数マスタコード { get; set; }
		public string レセプト電算コード { get; set; }
		public string 解釈番号 { get; set; }
		public string 診療行為名称 { get; set; }
		public int? 行為点数 { get; set; }
		public int? 行為薬剤料 { get; set; }
		public int? 行為材料料 { get; set; }
		public string 円点区分 { get; set; }
		public short? 行為回数 { get; set; }
		public string 保険者番号 { get; set; }
		public string レセプト種別コード { get; set; }
		public string 実施年月日 { get; set; }
		public string レセプト科区分 { get; set; }
		public string 診療科区分 { get; set; }
		public string 医師コード { get; set; }
		public string 病棟コード { get; set; }
		public string 病棟区分 { get; set; }
		public string 入外区分 { get; set; }
		public string 施設タイプ { get; set; }
		public string 診療年月 { get; set; }
		public int? 行 { get; set; }
        public static string SQL =
@"
SELECT 施設コード,データ識別番号,退院年月日,入院年月日,データ区分,
        順序番号,病院点数マスタコード,レセプト電算コード,解釈番号,
        診療行為名称,行為点数,行為薬剤料,行為材料料,円点区分,行為回数,
        保険者番号,レセプト種別コード,実施年月日,レセプト科区分,
        診療科区分,医師コード,病棟コード,病棟区分,入外区分,施設タイプ,
        診療年月,行
	FROM Ｅファイル
";

		public void SetDataSample(string[] fields)
		{
			施設コード = fields[0];
			データ識別番号 = fields[1];
			退院年月日 = fields[2];
			入院年月日 = fields[3];
			データ区分 = fields[4];
			順序番号 = string.IsNullOrEmpty(fields[5]) ? (decimal?)null : decimal.Parse(fields[5]);
			病院点数マスタコード = fields[6];
			レセプト電算コード = fields[7];
			解釈番号 = fields[8];
			診療行為名称 = fields[9];
			行為点数 = string.IsNullOrEmpty(fields[10]) ? (int?)null : int.Parse(fields[10]);
			行為薬剤料 = string.IsNullOrEmpty(fields[11]) ? (int?)null : int.Parse(fields[11]);
			行為材料料 = string.IsNullOrEmpty(fields[12]) ? (int?)null : int.Parse(fields[12]);
			円点区分 = fields[13];
			行為回数 = string.IsNullOrEmpty(fields[14]) ? (short?)null : short.Parse(fields[14]);
			保険者番号 = fields[15];
			レセプト種別コード = fields[16];
			実施年月日 = fields[17];
			レセプト科区分 = fields[18];
			診療科区分 = fields[19];
			医師コード = fields[20];
			病棟コード = fields[21];
			病棟区分 = fields[22];
			入外区分 = fields[23];
			施設タイプ = fields[24];
			診療年月 = fields[25];
			行 = string.IsNullOrEmpty(fields[26]) ? (int?)null : int.Parse(fields[26]);
		}
    }
}
```

#ストアドプロシージャからクラスを生成するサンプル
ストアドプロシージャは複数の結果を返したり、元テーブルと異なることから、直接ストアドプロシージャからクラスを作るサンプルを作成した。
##各ストアドプロシージャ毎に作成するttファイル

```text:SpResultFromProc必要度.tt
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ output encoding="UTF-8" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="System.Xml.Linq" #>
<#@ assembly name="System.Data" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Xml.Linq" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ output extension=".cs" #>

<#
    var TargetDB = "CStr1";
    var SpName = "Proc必要度";
    // ストアドプロシジャーに必要なパラメタを指定
    var AddParams = new Action<SqlCommand>(p =>
    {
        p.Parameters.Add(new SqlParameter("ymd", "20160710"));
    });
    var Folder = "";
#>
<#@ include file="SpToClass.ttinclude" #>
```
##ストアドプロシージャからクラスを作成する共通ttファイル

```text:SpToClass.ttinclude
<#
    var file = this.Host.ResolvePath("Definition.xml");

    var xdoc = XDocument.Load(file);
    var defs = from def in xdoc.Descendants("ConnectionString")
                select new
                {
                    Name = def.Attribute("name").Value,
                    ConnStr = def.Attribute("string").Value,
                };

    var connstr = defs.Where(r => r.Name == TargetDB)
        .Select(r => r.ConnStr).Single();

	var list = new List<Colums[]>();
    using (var conn = new SqlConnection(connstr))
    {
        conn.Open();

        var cmd = new SqlCommand(SpName, conn);
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.CommandTimeout = 50000;
        AddParams(cmd);

        using (var rd = cmd.ExecuteReader())
        {
            Console.WriteLine(rd.FieldCount);
            do
            {
                var st = rd.GetSchemaTable();
                list.Add(Enumerable.Range(0, rd.FieldCount)
					.Select(i => new Colums(rd, i, st.Rows[i])).ToArray());
            } 
            while (rd.NextResult());
        }
	}

	var resultCount = 0;
#>
using System;

namespace DapperClassLibrary<#= Folder #>
{
<#
	foreach(var arr in list)
	{
#>
	/// <summary>
    /// <#= SpName #>より生成されたクラス
    /// </summary>
    public partial class SpResultFrom<#= SpName #><#= resultCount++ #>
    {
<#
		foreach(var col in arr)
		{
#>
		<#= col.GetProperty() #>
<#
	    }
#>

		public static string SQL = @"<#= SpName #>";

		/*
		declare @temp table (
<#
		var arrFld = string.Join(",\n", arr.Select(r => r.GetFieldDef()).ToArray()).Split('\n');

		foreach(var col in arrFld)
		{
#>
			<#= col #>
<#
	    }
#>
		);
		*/
    }
<#
    }
#>

}

<#+
    class Colums
    {
        public string ColumnName { get; set; }
        public Type Type { get; set; }
        public string DbType { get; set; }
        public DataRow Row { get; set; }

        public Colums()
        {

        }

        public Colums(SqlDataReader rd, int i, DataRow row)
        {
            ColumnName = rd.GetName(i);
            Type = rd.GetFieldType(i);
            Row = row;
            DbType = Row["DataTypeName"].ToString();
        }

        public bool IsNullable()
        {
            var type = dicType[DbType];
            var IsNull = (bool)Row["AllowDBNull"];
            return (dicNullabel[type] && IsNull);
        }

        public string GetProperty()
        {
            var type = dicType[DbType];

            return string.Format("public {0}{1} {2} ",
                type, GetNullable(), ColumnName) + "{ get; set; }";
        }

        public string GetFieldDef()
        {
            return string.Format("{0}{1}", 
                    dicDdlFld[Row["DataTypeName"].ToString()](this),
                    IsNullable() ? "" : " NOT NULL");
        }

        private string GetNullable()
        {
            return IsNullable() ? "?" : "";
        }

        private static Dictionary<string, Func<Colums, string>> dicDdlFld =
                new Dictionary<string, Func<Colums, string>>()
        {
            {"char", m => string.Format("[{0}] {1}({2})", m.ColumnName, m.Row["DataTypeName"], m.Row["ColumnSize"]) },
            {"varchar", m => string.Format("[{0}] {1}({2})", m.ColumnName, m.Row["DataTypeName"], m.Row["ColumnSize"]) },
            {"nvarchar", m => string.Format("[{0}] {1}({2})", m.ColumnName, m.Row["DataTypeName"], m.Row["ColumnSize"]) },
            {"nchar", m => string.Format("[{0}] {1}({2})", m.ColumnName, m.Row["DataTypeName"], m.Row["ColumnSize"])},
            {"int", m => string.Format("[{0}] {1}", m.ColumnName, m.Row["DataTypeName"])},
            {"tinyint", m => string.Format("[{0}] {1}", m.ColumnName, m.Row["DataTypeName"])},
            {"smallint", m => string.Format("[{0}] {1}", m.ColumnName, m.Row["DataTypeName"])},
            {"bigint", m => string.Format("[{0}] {1}", m.ColumnName, m.Row["DataTypeName"])},
            {"decimal", m => string.Format("[{0}] {1}({2},{3})", m.ColumnName, m.Row["DataTypeName"], m.Row["NumericPrecision"], m.Row["NumericScale"])},
            {"bit", m => string.Format("[{0}] {1}", m.ColumnName, m.Row["DataTypeName"])},
            {"date", m => string.Format("[{0}] {1}", m.ColumnName, m.Row["DataTypeName"])},
            {"datetime", m => string.Format("[{0}] {1}", m.ColumnName, m.Row["DataTypeName"])},
        };

        private static Dictionary<string, bool> dicNullabel =
                new Dictionary<string, bool>()
        {
            {"string", false},
            {"int", true},
            {"long", true},
            {"byte", true},
            {"short", true},
            {"decimal", true},
            {"bool", true},
            {"DateTime", true},
        };

        private static Dictionary<string, string> dicType =
                new Dictionary<string, string>()
        {
            {"char", "string"},
            {"varchar", "string"},
            {"nvarchar", "string"},
            {"nchar", "string"},
            {"int", "int"},
            {"tinyint", "byte"},
            {"smallint", "short"},
            {"bigint", "long"},
            {"decimal", "decimal"},
            {"bit", "bool"},
            {"date", "DateTime"},
            {"datetime", "DateTime"},
        };
    }
#>
```
##ストアドプロシジャーからできたクラス

```csharp:SpResultFromProc必要度.cs
using System;

namespace DapperClassLibrary
{
    /// <summary>
    /// Proc必要度28Hより生成されたクラス
    /// </summary>
    public partial class SpResultFromProc必要度0
    {
		public DateTime? 対象日 { get; set; }
		public string 患者名 { get; set; }
		public int? 年齢 { get; set; }
		public string WARDCODE { get; set; }
		public string ROOMCODE { get; set; }
		public decimal? 創傷処置 { get; set; }
		public decimal? 呼吸ケア { get; set; }
		public decimal? 点滴ライン { get; set; }
		public decimal? 心電図 { get; set; }
		public decimal? シリンジポンプ { get; set; }
		public decimal? 輸血 { get; set; }
		public decimal? 専門的治療 { get; set; }
		public decimal? 救急入院 { get; set; }
		public decimal? 寝返り { get; set; }
		public decimal? 移譲 { get; set; }
		public decimal? 口腔清潔 { get; set; }
		public decimal? 食事摂取 { get; set; }
		public decimal? 衣服の着脱 { get; set; }
		public decimal? 診療指示 { get; set; }
		public decimal? 危険行動 { get; set; }
		public decimal? 開頭手術 { get; set; }
		public decimal? 開胸手術 { get; set; }
		public decimal? 開腹手術 { get; set; }
		public decimal? 骨手術 { get; set; }
		public decimal? 腹腔鏡手術 { get; set; }
		public decimal? 全身麻酔手術 { get; set; }
		public decimal? 救命内科治療 { get; set; }
		public decimal? A { get; set; }
		public decimal? B { get; set; }
		public decimal? C { get; set; }
		public int? 入院暦日 { get; set; }
		public string 基準 { get; set; }
		public byte? T創傷処置 { get; set; }
		public byte? T局所陰圧閉鎖処置 { get; set; }
		public byte? T重度褥瘡処置 { get; set; }
		public byte? T人工呼吸 { get; set; }
		public byte? T酸素吸入 { get; set; }
		public byte? T喀痰吸引 { get; set; }
		public byte? T呼吸心拍監視 { get; set; }
		public byte? T輸血 { get; set; }
		public byte? Tシリンジポンプ { get; set; }
		public byte? T救急入院当日 { get; set; }
		public byte? T救急入院翌日 { get; set; }

		public static string SQL = @"Proc必要度";

		/*
		declare @temp table (
			[対象日] date,
			[患者名] nvarchar(42) NOT NULL,
			[年齢] int,
			[WARDCODE] varchar(4) NOT NULL,
			[ROOMCODE] char(4) NOT NULL,
			[創傷処置] decimal(38,4),
			[呼吸ケア] decimal(38,4),
			[点滴ライン] decimal(38,4),
			[心電図] decimal(38,4),
			[シリンジポンプ] decimal(38,4),
			[輸血] decimal(38,4),
			[専門的治療] decimal(38,4),
			[救急入院] decimal(38,4),
			[寝返り] decimal(38,4),
			[移譲] decimal(38,4),
			[口腔清潔] decimal(38,4),
			[食事摂取] decimal(38,4),
			[衣服の着脱] decimal(38,4),
			[診療指示] decimal(38,4),
			[危険行動] decimal(38,4),
			[開頭手術] decimal(38,4),
			[開胸手術] decimal(38,4),
			[開腹手術] decimal(38,4),
			[骨手術] decimal(38,4),
			[腹腔鏡手術] decimal(38,4),
			[全身麻酔手術] decimal(38,4),
			[救命内科治療] decimal(38,4),
			[A] decimal(38,4),
			[B] decimal(38,4),
			[C] decimal(38,4),
			[入院暦日] int,
			[基準] varchar(2) NOT NULL,
			[T創傷処置] tinyint,
			[T局所陰圧閉鎖処置] tinyint,
			[T重度褥瘡処置] tinyint,
			[T人工呼吸] tinyint,
			[T酸素吸入] tinyint,
			[T喀痰吸引] tinyint,
			[T呼吸心拍監視] tinyint,
			[T輸血] tinyint,
			[Tシリンジポンプ] tinyint,
			[T救急入院当日] tinyint,
			[T救急入院翌日] tinyint
		);
		*/
    }

}
```