使用T4模板映射數據庫表
(零) 前言
在公司接觸的第一個項目里面有一個輕型的ORM,覺得其使用的數據庫映射的方法比較有趣。之前也使用過EF框架來映射數據庫,這個就真是傻瓜式的不過據說比較“重”,這個沒有研究就沒有發言權,不過就使用起來還是很方便的。這個ORM中的數據庫映射用到了T4模板,我在查資料時就一直查不到如何截取T4模板的信息和添加VS項目中的關聯。查出來的資料只有引用了別人已寫好的類庫,遂看源碼之,終於功夫不負有心人讓我了解了T4模板的正確打開方式。
(一) 什么是T4文本模板?
T4,即4個T開頭的英文字母組合:Text Template Transformation Toolkit。
T4文本模板,即一種自定義規則的代碼生成器。根據業務模型可生成任何形式的文本文件或供程序調用的字符串。(模型以適合於應用程序域的形式包含信息,並且可以在應用程序的生存期更改)
VS本身只提供一套基於T4引擎的代碼生成的執行環境,由下面程序集構成:
Microsoft.VisualStudio.TextTemplating.10.0.dll
Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll
Microsoft.VisualStudio.TextTemplating.Modeling.10.0.dll
Microsoft.VisualStudio.TextTemplating.VSHost.10.0.dll
在VS中直接打開T4模板是沒有高亮和縮進以及提示的,需要自行在VS的“擴展和更新”或“NuGet”中查找模板編輯器並安裝。
(二) T4文本模板的主要結構
模板包含將要生成的文件的文本,使用不同的控制結構與指令將有助於文件的生成。
T4模板有兩種類型:
·運行時T4文本模板
可以通過運行時此文本模板在應用程序的運行時生成字符串。
使用此文本模板的計算機不必具有Visual Studio。
有關此項的更多信息,請參考T4 文本模板的運行時文本生成。
·設計時T4文本模板
可以通過設計時此文本模板在Viusal Studio項目中生成程序代碼和其他文
件。
此文本模板的計算機必須在Visual Stduio項目或MS Build內執行。
有關此項的更多信息,請參考T4 文本模板的設計時文本生成。
文本模板由以下部件構成:
· 指令:控制模板處理方式的元素。
· 文本塊:直接復制到輸出的內容。
· 控制塊:向文本插入可變值並控制文本的條件或重復部件的程序代碼。
(二點一) 指令
1) 模板指令
1 <#@ template [language="C#"] [hostspecific="true|TrueFromBase"] [debug="true"] [inherits="templateBaseClass"] [culture="code"] [compilerOptions="options"] [visibility="internal"] [linePragmas="false"] #>
T4模板通常以此命令開頭,該指令指定應如何處理模板。
此指令在同一模板中應只有一個。
常用特性(特性一般都是可選的):
· debug特性:開啟使得VS調試器可以識別中斷和異常的位置。
· hostspecific特性:開啟獲得名為Host的對轉換引擎宿主的引用,並可以
將其轉換為 IServiceProvider以訪問Visual Studio功能。若要獲取宿主
提取到的文本,則此特性是必要的。
· language特性: 可選參數VB和C#,指定要用於語句和表達式塊中的源代碼
的語言。
2) 參數指令
1 <#@ parameter type="Full.TypeName" name="ParameterName" #>
此指令聲明模板代碼中從自外部傳入的值初始的屬性。
3) 輸出指令
1 <#@ output extension=".fileNameExtension" [encoding="encoding"] #>
此指令用於定義生成的文件擴展名和編碼。
運行時文本模板中不需要輸出指令,設計時文本模板中也應只有一條輸出指令。
常用特性:
· extension特性:指定生成的文本輸出文件的文件擴展名。
4) 程序集指令
1 <#@ assembly name="[assembly strong name|assembly file name]" #>
此指令可加載程序集,讓模板代碼可使用其類型。
運行時文本模板不需要此指令,只需要在Visual Studio的引用中添加即可。
可以使用 $(variableName) 語法引用 Visual Studio 變量
(如 $(SolutionDir)),以及使用 %VariableName% 來引用環境變量。
5) 導入指令
1 <#@ import namespace="namespace" #>
此指令可以讓文本模板在不提供完全限定名稱的情況下引用另一個命名空間中的元
素,類似於C#中的 using。
6) 包含指令
1 <#@ include file="filePath" [once="true"] #>
此指令可包括來自另一個文件的文本,在處理時被包含內容就像是包含文本模板的
組成部分一樣。
包含的地址可以是絕對的也可以是相對的,也可以使用 %name%來使用環境變量。
(二點二) 文本塊
文本塊直接向輸出文件插入文本。 文本塊沒有特殊格式。
例如,下面的文本模板將生成一個包含單詞“Hello”的文本文件:
1 <#@ output extension=".txt" #> 2 Hello
所有的文本都可以經由宿主(Host)來獲取。
(二點三) 控制塊
控制塊是用於轉換模板的程序代碼節點,在二點一的第一項中可以通過修改 language特性來改變控制塊所使用的腳本語言。
1) 標准控制塊
在模板文件中,可以混合使用任意數量的文本塊和標准控制塊。 但是,不能在控
制塊中嵌套控制塊。
每個標准控制塊都以 <# ... #> 符號分隔。
在標准控制塊中可以使用 Write(string)來輸出,不過更好的方式是交錯使用
文本塊和控制塊來輸出文本。
1 <# 2 for(int i = 0; i < 4; i++) 3 { 4 Write(i + ", "); 5 } 6 Write("4"); 7 #> Hello!
//輸出結果: 0, 1, 2, 3, 4 Hello!
1 <# 2 for(int i = 0; i < 4; i++) 3 { 4 #> <#= i #> , <# } #> Hello!
//輸出結果: 0,1,2,3,Hello!
可以看到在輸出循環運算時,始終是用{...}大括號來包含文本塊。
2) 表達式控制塊
表達式控制塊計算其中的表達式並將其轉換為字符串,該字符串將插入到輸出文件
中。
表達式控制塊以 <#= ... #> 符號分隔。
例如,如果使用下面的控制塊,則輸出文件包含“5”:
1 <#= 2 + 3 #>
3) 類功能控制塊
類功能控制塊定義屬性、方法和不應包含在主轉換(文本塊和其他控制塊)中的所
有其他代碼。
類功能控制塊通常用於編寫幫助類函數,其也可以被包含在多個文本模板中。
類功能控制塊以 <#+ ... #> 符號分隔。
例如,下面的模板文件聲明並使用一個方法:
1 <#@ output extension=".txt" #> 2 Squares: 3 <# 4 for(int i = 0; i < 4; i++) 5 { 6 #> 7 The square of <#= i #> is <#= Square(i+1) #>. 8 <# 9 } 10 #> 11 That is the end of the list. 12 <#+ // 類功能控制塊 13 private int Square(int i) 14 { 15 return i*i; 16 } 17 #>
類功能必須編寫在文件末尾,不過被包含(include指令)的文本例外。
(三) 備注
設計時文本模板在單獨的 AppDomain 中運行。
·讀取文件最簡單的方法
<# string fileContent = File.ReadAllText(@"C:\myData.txt"); ...
·寫入文件最簡單的方法 <# File.WriteAllText(@"C:\myData.txt",str); #>
·可以使用關系圖和 UML圖來生成模型文件。
·使用 this.Host 屬性可以訪問由執行模板的宿主公開的屬性。 若要使
用 this.Host,必須在 <@template#> 指令中設置 hostspecific 特性:
<#@template ... hostspecific="true" #>
·獲得T4模板宿主當前的文本 this.GenerationEnvironment;
·添加 Visual Studio的項目關聯:
1 // 獲取Visual Studio實例 2 EnvDTE.DTE dte = (EnvDTE.DTE)
((IServiceProvider)Host).GetService(typeof(EnvDTE.DTE)); 3 EnvDTE.ProjectItem templateProjectItem =
dte.Solution.FindProjectItem(Host.TemplateFile); 4 ........................................... 5 // 添加 VS的項目關聯 6 templateProjectItem.ProjectItems.AddFromFile(path);
·獲得MySql數據庫字段對應C#的內置類型:
Type type =reader.GetFieldType(0); //獲取reader中第0列的數據
(四) 從數據庫映射數據模型的示例
1 <#@ template debug="true" hostspecific="true" language="C#" #> 2 <#@ assembly name="System.Core" #> 3 <#@ import namespace="System.Linq" #> 4 <#@ import namespace="System.Text" #> 5 <#@ import namespace="System.Collections.Generic" #> 6 <#@ output extension="" #> 7 8 <#@ assembly name="System.Data" #> 9 <#@ assembly name="$(ProjectDir)MySql.Data.dll" #> //MySql.Data.dll需放在項目根目錄也可以自行更改 10 <#@ import namespace="MySql.Data.MySqlClient" #> 11 <#@ import namespace="MySql.Data" #> 12 <#@ import namespace="System.IO" #> 13 <#@ import namespace="Microsoft.VisualStudio.TextTemplating"#> 14 <#@ assembly name="EnvDTE" #> 15 <#@ import namespace="System" #> 16 <#@ assembly name="EnvDTE90" #> 17 <#@ import namespace="EnvDTE90" #> 18 19 <# 20 // 聲明字段 21 MySqlConnection conn; 22 string sql; 23 MySqlCommand command; 24 MySqlDataReader reader; 25 List<string> tablesName; 26 List<string> tablesMark; 27 MySqlDataReader markReader; //用來讀取字段注釋的Reader 28 string templateFileDir = Path.GetDirectoryName(Host.TemplateFile); //獲取tt模板目錄 29 // 數據庫連接 30 string strConn = "Server=localhost;Database=hc;
User=root;Password=1234;charset=utf8;"; //數據庫連接字符串-這個換了環境就要改 31 conn = new MySqlConnection(strConn); 32 conn.Open(); 33 // 查MySql數據庫中有多少張表的sql語句 34 sql = "show table status"; 35 command = new MySqlCommand(sql, conn); 36 reader = command.ExecuteReader(); 37 tablesName = new List<string>(); 38 tablesMark = new List<string>(); 39 // 把表集合賦值到tablesName 40 while (reader.Read()) 41 { 42 tablesName.Add(reader[0].ToString()); //獲取表名稱 43 tablesMark.Add(reader[17].ToString()); //獲取表注釋 44 } 45 reader.Close(); 46 47 // 獲取Visual Studio實例 48 EnvDTE.DTE dte = (EnvDTE.DTE)
((IServiceProvider)Host).GetService(typeof(EnvDTE.DTE)); 49 EnvDTE.ProjectItem templateProjectItem =
dte.Solution.FindProjectItem(Host.TemplateFile); 50 51 // 循環映射模型文件 52 for (int i =0; i < tablesName.Count ; i++) 53 { 54 List<string> columsName = new List<string>(); 55 List<Type> columsType = new List<Type>(); 56 // 獲取數據庫表的字段的名稱 57 sql = string.Format("SHOW COLUMNS FROM {0}", tablesName[i]); 58 command.CommandText = sql; 59 reader = command.ExecuteReader(); 60 while (reader.Read()) 61 { 62 columsName.Add(reader.GetString(0)); 63 } 64 reader.Close(); 65 // 獲取數據庫表的字段的類型 66 sql = string.Format("SELECT * FROM {0} LIMIT 0", tablesName[i]); 67 command.CommandText = sql; 68 reader = command.ExecuteReader(); 69 reader.Read(); 70 for(int z = 0 ; z < reader.FieldCount ; z++){ 71 Type type =reader.GetFieldType(z); 72 columsType.Add(type); 73 } 74 reader.Close(); 75 #> 76 //------------------------------------------------------------------------------ 77 // <auto-generated> 78 // 此代碼由T4模板自動生成 79 // 生成時間 <#= DateTime.Now #> By Cwj-Makemoretime 80 // 對此文件的更改可能會導致不正確的行為,並且如果 81 // 重新生成代碼,這些更改將會丟失。 82 // </auto-generated> 83 //------------------------------------------------------------------------------ 84 85 using System; 86 using System.Collections.Generic; 87 88 /// <summary> 89 /// <#= tablesMark[i] #> 90 /// </summary> 91 public class <#= tablesName[i] #>{ 92 93 <# // 獲取字段的注釋 94 for(int j = 0; j<columsName.Count ; j++){ 95 sql = string.Format("show full fields from {0} where Field = '{1}'" ,
tablesName[i] , columsName[j].ToString()); 96 command.CommandText = sql; 97 markReader = command.ExecuteReader(); 98 markReader.Read(); 99 #> 100 /// <summary> 101 /// <#= markReader[8].ToString() #> 102 /// </summary> 103 public <#= columsType[j] #> <#= columsName[j] #> { get; set;} 104 <# markReader.Close(); 105 } #> 106 } 107 <# 108 byte[] byData = new byte[100]; 109 char[] charData = new char[1000]; 110 // 此地址與T4模板同目錄 111 string path = templateFileDir +"\\"+tablesName[i]+".cs"; 112 try 113 { 114 // 將生成的文件添加到T4模板下關聯 115 File.WriteAllText(path , this.GenerationEnvironment.ToString()); 116 templateProjectItem.ProjectItems.AddFromFile(path); 117 } 118 catch (IOException e) 119 { 120 Console.WriteLine(e.ToString()); 121 } 122 this.GenerationEnvironment.Clear();
//↑↑清空T4模板當前的 文本模板轉換進程(T4)用於組合生成的文本輸出的字符串↑↑ 123 } 124 125 command.Dispose(); 126 conn.Close(); 127 #> 128 129 130 <#= "此文件為固定生成的文件,不必理會。" #> 131 132
運行結果:

文章By Cwj-Makemoretime
允許轉載 但請標注原文地址
