使用 T4 範本產生器產生對應資料庫的 Entity
T4 是「Text Template Transformation Toolkit」的簡稱,這工具是用來動態產生文字檔或是程式碼用的,
Visual Studio 2008 之後的板板都有內建,像是 EntityFramework就有用到他來產生對應 table 的 Entity,
他有「執行階段」和「設計階段」兩種執行方式,可以從檔案的「自訂工具」屬性設定,
分別是TextTemplatingFilePreprocessor 和 TextTemplatingFileGenerator。
這篇就來說如何用設計階段的 T4 產生自己想要的 Entity。
我們可以在任意專案新增項目,選擇文字範本
如果專案沒有文字範本可以選,也可以建立文字檔,然後把附檔名改成 .tt 。
產生之預設內容如下
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".txt" #>
此檔案的「自動工具」屬性會是TextTemplatingFileGenerator,
T4 的結構由「指示詞」、「文字區塊」、「控制區塊」三者組成
指示詞 有很多種,這邊只說明有用到的
在上面的程式碼就都是指示詞,
template (範本指示詞)設定一些範本的處理方式
assembly (組件指示詞)是設定要載入的 dll,
import (匯入指示詞)用來匯入命名空間
output (輸出指示詞)用來定義副檔名和編碼
文字區塊會將文字直接做輸出,以下的 Hello 就是文字區塊
<#@ output extension=".txt" #>
Hello
控制區塊就是用來寫程式碼的地方,預設的程式語言是C#
我們可以將範本指示詞的 language 屬性設定成 vb,表示要用 vb 來開發
而控制區塊又分三種
由 <# #> 包起來的區塊稱作「標準控制區塊」,在這裡開發相關邏輯
<#= #> 包起來的是「運算式控制區塊」,可以將運算式計算完成的內容轉成文字輸出。
第三個式「類別功能控制區塊」,由 <#+ #> 包起來,用來定義屬性,方法或是類別的地方。
接著我們就來直接看完整的程式碼吧,我也直接把說明寫在註解裡
<#@ template language="C#" debug="True" hostspecific="True" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Diagnostics" #>
using System;
//改成自己的 namespace
namespace MyProject.Entities {
<#
//修改connection string
string connectionString = @"我的連線字串";
SqlConnection conn = new SqlConnection(connectionString);
conn.Open();
//取得所有 table 的 schema
DataTable schema = conn.GetSchema("Tables");
foreach(System.Data.DataRow row in schema.Rows) {
var tableName = row["TABLE_NAME"].ToString().Trim('s');
//取得必要資訊列表
var schemaList = CodeStringGenerator.GetSchemaInfos(conn, tableName);
#>
/// <summary>
/// <#= tableName #>
/// </summary>
public class <#= tableName #>{
<#
foreach (var s in schemaList) {
#>
/// <summary>
<#
//將DB裡對於欄位的描述排版顯示
foreach(var d in s.Description.Split('\n')){
#>
/// <#= d.Trim() #>
<#
}
#> /// </summary>
public <#= s.TypeName #><#= s.AllowDBNull ? "?" : ""#> <#= s.ColumnName #> { get; set; }
<#
}
#>
}
<#
}
#>
}
<#+
//以下是取得產生類別所需內容的程式碼,參考了 Necroskillz 的文章
//http://www.necronet.org/archive/2012/10/09/generate-c-pocos-from-sql-statement-in-linqpad.aspx
public class CodeStringGenerator{
public static Dictionary<Type, string> TypeAliases = new Dictionary<Type, string> {
{ typeof(int), "int" },
{ typeof(short), "short" },
{ typeof(byte), "byte" },
{ typeof(byte[]), "byte[]" },
{ typeof(long), "long" },
{ typeof(double), "double" },
{ typeof(decimal), "decimal" },
{ typeof(float), "float" },
{ typeof(bool), "bool" },
{ typeof(string), "string" }
};
public static HashSet<Type> NullableTypes = new HashSet<Type> {
typeof(int),
typeof(short),
typeof(long),
typeof(double),
typeof(decimal),
typeof(float),
typeof(bool),
typeof(DateTime)
};
//取得欄位的描述
//https://docs.microsoft.com/zh-tw/sql/relational-databases/system-functions/sys-fn-listextendedproperty-transact-sql
public static string descriptionSQL = @"SELECT * FROM ::fn_listextendedproperty(NULL, 'schema', 'dbo', 'table', @tableName, 'column', NULL)";
//取得欄位相關資訊以及描述文字
public static List<SchemaInfo> GetSchemaInfos(SqlConnection connection, string table) {
List<SchemaInfo> list = new List<SchemaInfo>();
Dictionary<string, string> descriptionDir = new Dictionary<string, string>();
if (connection.State != ConnectionState.Open)
connection.Open();
var desciptionCmd = connection.CreateCommand();
desciptionCmd.CommandText = descriptionSQL;
desciptionCmd.Parameters.Add(new SqlParameter("@tableName", table));
var descReader = desciptionCmd.ExecuteReader();
while (descReader.Read()) {
descriptionDir.Add(descReader["objname"].ToString(), descReader["value"].ToString());
}
descReader.Close();
var cmd = connection.CreateCommand();
cmd.CommandText = $"select top(1) * from [{table}]";
var reader = cmd.ExecuteReader();
do {
if (reader.FieldCount <= 1) continue;
var schema = reader.GetSchemaTable();
foreach (DataRow row in schema.Rows) {
var info = new SchemaInfo();
info.DataType = (Type)row["DataType"];
info.TypeName = TypeAliases.ContainsKey(info.DataType) ? TypeAliases[info.DataType] : info.DataType.Name;
info.AllowDBNull = (bool)row["AllowDBNull"] && NullableTypes.Contains(info.DataType);
info.ColumnName = (string)row["ColumnName"];
if (descriptionDir.ContainsKey(info.ColumnName)) info.Description = descriptionDir[info.ColumnName];
else info.Description = "";
list.Add(info);
}
} while (reader.NextResult());
reader.Close();
return list;
}
}
public struct SchemaInfo {
public Type DataType { get; set; }
public bool AllowDBNull { get; set; }
public string TypeName { get; set; }
public string ColumnName { get; set; }
public string Description { get; set; }
}
#>
存檔之後,就會自動產生 TextTemplate1.cs 檔案,內容大致如下
根據每個人的資料庫內容而定。
如此一來就可以自動產生一系列程式碼,不用自己 Key 囉。
參考資訊