[.NET]透過 T4 產生對應 DB table 的 entity
前言
在前面的 RowMapper 文章裡,提到透過自訂 Attribute 來封裝 entity 與 DB 資料欄位的對應,以企圖讓 data access layer 回來的資料,都是以 entity 物件的形式,而非 DataTable 或 DataSet 這類弱型別的結構。
但,難道每次設計 mapping table 的 entity 都只能手動去刻嗎?感覺好蠢、好浪費時間。以 LINQ to SQL 或 LINQ to EF,都只需要拉一拉 data model,就可以自動產生對應的 entity 集合。刻 entity 實在太浪費時間了。
沒錯,所以有很多人會自己寫對應的程式碼產生器,甚至許多套件也都有提供類似的功能。這一篇文要介紹的,是透過內建的 T4 轉換,用最簡單的方式來自動產生我們所需要的 entity class 檔案。
註:有些人可能會說,為什麼不乾脆使用 LINQ to SQL 或 LINQ to EF,但實際上很多現實狀況,是不允許使用的。不管是 .net framework migration 的問題,或是 performance 問題,或是需要在DA層自訂一些客製化的動作時,就不一定可以直接使用。
簡介
T4 的全名是「Text Template Transformation Toolkit」,簡單的說,就是一個可以轉換的文字範本,在範本裡面可以透過程式碼與邏輯,來決定要嵌入哪些文字。幾乎現在的 Visual Studio 上 的 code generator 底子都是透過 T4 來產生對應的程式碼。
範例
需求說明:
- 針對某一個DB中,某一張 table
- 產生對應的 class 內容
- Class 名稱預設與 table 名稱相同
- Property 名稱預設與 table column 名稱相同
- Property type 預設為 column type 所對應的 data type
- 每一個 property 上,要有對應的自訂 attribute,供 RowMapper 使用
- 可自行加入 todo 註解,或是相關 using namespace
在任何一個專案底下,新增一個項目,類型為「文字範本」,請見下圖:
接著會出現下面的視窗,其實就是 T4 要幫忙轉換輸出結果的動作,當按下確定,就會按照 tt 上的範本邏輯產生對應的程式碼。第一次所產生的程式碼其實是空的,所以按確定跟取消都沒差。
接下來按照我們的需求,來設計 template 的內容:
<#@ template language="C#" debug="True" hostspecific="True" #>
<#@ output extension=".cs" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.xml" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Data" #>
using System;
//todo: 請修改對應的namespace
namespace MyProject.Entities
{
<#
//修改connection string
string connectionString = "你的ConnectionString";
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
//如果需要database中全部table,則使用conn.GetSchema("Tables")即可
string[] restrictions = new string[4];
restrictions[1] = "dbo";
//修改table名稱
restrictions[2] = "你的Table名稱";
DataTable schema = conn.GetSchema("Tables", restrictions);
string selectQuery = "select * from @tableName";
SqlCommand command = new SqlCommand(selectQuery,conn);
SqlDataAdapter ad = new SqlDataAdapter(command);
System.Data.DataSet ds = new DataSet();
foreach(System.Data.DataRow row in schema.Rows)
{
#>
//mapping table name: <#= row["TABLE_NAME"].ToString().Trim('s') #>
public class <#= row["TABLE_NAME"].ToString().Trim('s') #>
{
<#
command.CommandText = selectQuery.Replace("@tableName",row["TABLE_NAME"].ToString());
ad.FillSchema(ds, SchemaType.Mapped, row["TABLE_NAME"].ToString());
foreach (DataColumn dc in ds.Tables[0].Columns)
{
#>
[ColumnMappingAttribute("<#= dc.ColumnName #>")]
public <#= dc.DataType.Name #> <#= dc.ColumnName #> { get; set; }
<# } #>
}
<#
} #>
}
簡單的說,有點像 XSLT 轉換,或是 .aspx 的資料繫結,又或是 .asp 的寫法:
- 在<# #>區塊裡面的就是實際執行的程式碼。
- 不在<# #>區塊的部分,則是文字範本。
- 透過兩者的組合,就可以達到 code generator 的效果。
上面的範本主要做的事情如下:
- 透過 connection string,建立與 DB 的連線
- 透過指定的 table name,來針對我們需要的 table 產生對應的 entity
- 透過 GetSchema() 取得所有 Tables 的結構
- 針對所有 table (若有給定 table name,則只針對該 table),產生所有對應的 class , class的名稱為 TABLE_NAME 欄位的值。
- 針對 table 上每一個欄位,產生對應的 public property,這邊是用 C# 3.0 的自動實作屬性,所以只產生 {get; set;}
- 每一個 property 上,都要加上自訂的 attribute: ColumnMappingAttribute,對應的 column name,則以該 ColumnName 值為準。
範本建立完成後,只要存檔,跳出上面的視窗後,按下確定,就會產生對應的 cs 檔出來 (副檔名是由範本第二行的 output extension=".cs" 決定的)
結果範例如下:
只需要加入對應的 namespace ,並格式化一下,就是一個 mapping Log table 的 entity 了。
另外值得一提的是,T4 的 template 建立完以後,在任何一個 project 上想使用時,只需要直接把該檔案拉進 Visual Studio 中就可以了。
結論
T4 其實用處還有很多,這篇只是小小的應用一下,用來解決自己日常作業上常見的需求。
透過這一篇,希望大家可以延伸出一些屬於自己的小應用,可以提昇自己的生產力。
Reference
Sample Code
download: T4 sample.zip
blog 與課程更新內容,請前往新站位置:http://tdd.best/