(零)前言
在公司接触的第一个项目里面有一个轻型的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
运行结果: