1 T4語法
T4的語法與ASP.NET的方式比較類似。主要包括指令、文本塊、控制塊。
1.1 指令
指令主要包括template, output, assembly, import, include等類型,用以告訴T4引擎如何編譯和運行一個模板。這些指令相當於T4引擎的配置參數。
示例:
<#@ template debug="true" hostspecific="true" language="C#" #>
告訴T4引擎控制塊用c#編寫;
<#@ output extension=".cs" #>
告訴T4引擎生成文件的后綴名是.cs;
<#@ assembly name="System.Core"#>
告訴T4引擎編譯運行時引用System.Core程序集;
<#@ assembly name="$(SolutionDir)\Project.CodeGenerator\bin\Debug\MySql.Data.Dll" #>
告訴T4引擎引用一個特定的位置上的程序集;
<#@ import namespace="System.Data.SqlClient"#>
告訴T4引擎編譯運行時引用某個名稱空間
<#@ include file="../Code/DBSchema.ttinclude"#>
告訴T4引擎編譯運行時引用某個文件,類似於JS的引用
1.2 文本塊
文本塊, T4引擎會將文本塊的內容直接復制到輸出文件中。
1.3 控制塊
控制塊,主要用於控制文本的輸出。在控制塊可以寫任意的C#代碼。
1.4 示例Hello world
<#@ template debug="true" hostspecific="true" language="C#" #> <#@ output extension=".txt" #> Hello, <#Write("World");#>
2 工作原理
轉載自:http://www.olegsych.com/2007/12/text-template-transformation-toolkit/
1> Step1:編譯模板,根據指令編譯模板中的文本塊和控制塊,並生成一個繼承於TextTransformation的類。
2> Step2: T4引擎動態創建類的實例,並調用TransformText方法。
3 在T4中讀取表結構
我們用T4時,主要是基於數據庫或配置文件來生成各類的代碼。所以如何有效地獲取數據庫的結構信息,是比較重要的。 之前看很多人直接把獲取數據庫的信息放在每一個模板中,在更換其它數據庫時,又要重寫模板。一個模板同時支持多個項目時,不同的項目數據庫很有可能是不同的。
主要設計思想如下,簡單地應用了簡單工廠模式。通過DBSchemaFactory類根據不同數據庫類型,獲取數據庫訪問類的接口IDBSchema。最后返回相同的表結構信息。
DBSchema.ttinclude類:
根據不同的數據庫,獲取表結構信息。已包括SQLSERVER和MYSQL。

<#@ assembly name="System.Core"#> <#@ assembly name="System.Data" #> <#@ assembly name="System.xml" #> <#@ assembly name="$(SolutionDir)\bin\Debug\MySql.Data.Dll" #> <#@ import namespace="System"#> <#@ import namespace="System.Collections.Generic"#> <#@ import namespace="System.Data"#> <#@ import namespace="System.Data.SqlClient"#> <#@ import namespace="MySql.Data.MySqlClient"#> <#+ #region Code public class DBSchemaFactory { static readonly string DatabaseType = "SqlServer"; public static IDBSchema GetDBSchema() { IDBSchema dbSchema; switch (DatabaseType) { case "SqlServer": { dbSchema =new SqlServerSchema(); break; } case "MySql": { dbSchema = new MySqlSchema(); break; } default: { throw new ArgumentException("The input argument of DatabaseType is invalid!"); } } return dbSchema; } } public interface IDBSchema : IDisposable { List<string> GetTablesList(); Table GetTableMetadata(string tableName); } public class SqlServerSchema : IDBSchema { public string ConnectionString = "Data Source=.;Initial Catalog=ProjectData;Persist Security Info=True;User ID=sa;Password=123456;"; public SqlConnection conn; public SqlServerSchema() { conn = new SqlConnection(ConnectionString); conn.Open(); } public List<string> GetTablesList() { DataTable dt = conn.GetSchema("Tables"); List<string> list = new List<string>(); foreach (DataRow row in dt.Rows) { list.Add(row["TABLE_NAME"].ToString()); } return list; } public Table GetTableMetadata(string tableName) { string selectCmdText = string.Format("SELECT * FROM {0}", tableName); ; SqlCommand command = new SqlCommand(selectCmdText, conn); SqlDataAdapter ad = new SqlDataAdapter(command); System.Data.DataSet ds = new DataSet(); ad.FillSchema(ds, SchemaType.Mapped, tableName); Table table = new Table(ds.Tables[0]); return table; } public void Dispose() { if (conn != null) conn.Close(); } } public class MySqlSchema : IDBSchema { public string ConnectionString = "Server=localhost;Port=3306;Database=ProjectData;Uid=root;Pwd=;"; public MySqlConnection conn; public MySqlSchema() { conn = new MySqlConnection(ConnectionString); conn.Open(); } public List<string> GetTablesList() { DataTable dt = conn.GetSchema("Tables"); List<string> list = new List<string>(); foreach (DataRow row in dt.Rows) { list.Add(row["TABLE_NAME"].ToString()); } return list; } public Table GetTableMetadata(string tableName) { string selectCmdText = string.Format("SELECT * FROM {0}", tableName); ; MySqlCommand command = new MySqlCommand(selectCmdText, conn); MySqlDataAdapter ad = new MySqlDataAdapter(command); System.Data.DataSet ds = new DataSet(); ad.FillSchema(ds, SchemaType.Mapped, tableName); Table table = new Table(ds.Tables[0]); return table; } public void Dispose() { if (conn != null) conn.Close(); } } public class Table { public Table(DataTable t) { this.PKs = this.GetPKList(t); this.Columns = this.GetColumnList(t); this.ColumnTypeNames = this.SetColumnNames(); } public List<Column> PKs; public List<Column> Columns; public string ColumnTypeNames; public List<Column> GetPKList(DataTable dt) { List<Column> list = new List<Column>(); Column c = null; if (dt.PrimaryKey.Length > 0) { list = new List<Column>(); foreach (DataColumn dc in dt.PrimaryKey) { c = new Column(dc); list.Add(c); } } return list; } private List<Column> GetColumnList(DataTable dt) { List<Column> list = new List<Column>(); Column c = null; foreach (DataColumn dc in dt.Columns) { c = new Column(dc); list.Add(c); } return list; } private string SetColumnNames() { List<string> list = new List<string>(); foreach (Column c in this.Columns) { list.Add(string.Format("{0} {1}", c.TypeName, c.LowerColumnName)); } return string.Join(",", list.ToArray()); } } public class Column { DataColumn columnBase; public Column(DataColumn columnBase) { this.columnBase = columnBase; } public string ColumnName { get { return this.columnBase.ColumnName; } } public string MaxLength { get { return this.columnBase.MaxLength.ToString(); } } public string TypeName { get { string result = string.Empty; if (this.columnBase.DataType.Name == "Guid")//for mysql,因為對於MYSQL如果是CHAR(36),類型自動為Guid result = "String"; else result = this.columnBase.DataType.Name; return result; } } public bool AllowDBNull { get { return this.columnBase.AllowDBNull; } } public string UpColumnName { get { return string.Format("{0}{1}", this.ColumnName[0].ToString().ToUpper(), this.ColumnName.Substring(1)); } } public string LowerColumnName { get { return string.Format("{0}{1}", this.ColumnName[0].ToString().ToLower(), this.ColumnName.Substring(1)); } } } public class GeneratorHelper { public static readonly string StringType = "String"; public static readonly string DateTimeType = "DateTime"; public static string GetQuesMarkByType(string typeName) { string result = typeName; if (typeName == DateTimeType) { result += "?"; } return result; } } #endregion #>
數據庫結構的測試模板02 DBSchema.tt
輸出數據庫的所有表的結構信息

<#@ template debug="true" hostspecific="true" language="C#" #> <#@ output extension=".txt" #> <#@ assembly name="System.Core"#> <#@ import namespace="System"#> <#@ import namespace="System.Collections.Generic"#> <#@ include file="../Code/DBSchema.ttinclude"#> <# var dbSchema=DBSchemaFactory.GetDBSchema(); List<string> tableList=dbSchema.GetTablesList(); foreach(string tableName in tableList) { #> <#= tableName #> <# Table table=dbSchema.GetTableMetadata(tableName); foreach(Column c in table.PKs) { #> <#= c.ColumnName#> <# } #> ColumnName,TypeName,MaxLength,UpColumnName,LowerColumnName <# foreach(Column c in table.Columns) { #> <#=c.ColumnName#>,<#=c.TypeName#>,<#=c.MaxLength#>,<#=c.UpColumnName#>,<#=c.LowerColumnName#> <# } #> <# } dbSchema.Dispose(); #>
注:
1> 在DBSchema.ttinclude,所有的類都被包含在<#+ #>中,<#+ #>是一個類功能的控制塊,其中可以定義任意的C#代碼。這些類多是幫助類,所以又定義在一個可復用的模板中”.ttinclude”.
2> 在02 DBSchema.tt中有一行” <#@ include file="../Code/DBSchema.ttinclude"#>“,是指引用某個位置的文件,在這里指是引用一個可復用的模板。
4用T4生成實體
用T4生成一個代碼的一個常用應用是生成實體類,下面是一個示例代碼(此模板引用了DBSchema.ttinclude):

<#@ template debug="true" hostspecific="true" language="C#" #> <#@ output extension=".cs" #> <#@ assembly name="System.Core"#> <#@ import namespace="System"#> <#@ import namespace="System.Collections.Generic"#> <#@ include file="../Code/DBSchema.ttinclude"#> <# var dbSchema=DBSchemaFactory.GetDBSchema(); List<string> tableList=dbSchema.GetTablesList(); foreach(string tableName in tableList) { Table table=dbSchema.GetTableMetadata(tableName); #> using System; using System.Collections.Generic; using System.Text; namespace Project.Model { [Serializable] public class <#=tableName#> { #region Constructor public <#=tableName#>() { } public <#=tableName#>(<#=table.ColumnTypeNames#>) { <# foreach(Column c in table.Columns) { #> this.<#=c.LowerColumnName#> = <#=c.LowerColumnName#>; <# } #> } #endregion #region Attributes <# foreach(Column c in table.Columns) { #> private <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.LowerColumnName#>; public <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.UpColumnName#> { get { return <#=c.LowerColumnName#>; } set { <#=c.LowerColumnName#> = value; } } <# } #> #endregion #region Validator public List<string> ErrorList = new List<string>(); private bool Validator() { bool validatorResult = true; <# foreach(Column c in table.Columns) { if(!c.AllowDBNull) { if(c.TypeName==GeneratorHelper.StringType) { #> if (string.IsNullOrEmpty(this.<#=c.UpColumnName#>)) { validatorResult = false; this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!"); } <# } if(c.TypeName==GeneratorHelper.DateTimeType) { #> if (this.<#=c.UpColumnName#>==null) { validatorResult = false; this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!"); } <# } } if(c.TypeName==GeneratorHelper.StringType) { #> if (this.<#=c.UpColumnName#> != null && <#=c.MaxLength#> < this.<#=c.UpColumnName#>.Length) { validatorResult = false; this.ErrorList.Add("The length of <#=c.UpColumnName#> should not be greater then <#=c.MaxLength#>!"); } <# } } #> return validatorResult; } #endregion } } <# } dbSchema.Dispose(); #>
注:
1> 在這個模板中,<#= #>為表達式控制塊
2> 除了表達式控制塊,其它的代碼塊的開始<#和結束符#>最好是放在行首,這樣一來容易分辨,二來最終輸出的文本也是你想要的,不然文本塊會亂掉。
5生成多個實體並分隔成多個文件
對於同時生成多個文件的模板可以直接下面的一個幫助類,這個幫助類可以幫助我們將一個文件分隔成多個文件(網上找的)。
分隔文件的幫助類:

<#@ assembly name="System.Core"#> <#@ assembly name="EnvDTE"#> <#@ import namespace="System.Collections.Generic"#> <#@ import namespace="System.IO"#> <#@ import namespace="System.Text"#> <#@ import namespace="Microsoft.VisualStudio.TextTemplating"#> <#+ // T4 Template Block manager for handling multiple file outputs more easily. // Copyright (c) Microsoft Corporation. All rights reserved. // This source code is made available under the terms of the Microsoft Public License (MS-PL) // Manager class records the various blocks so it can split them up class Manager { public struct Block { public String Name; public int Start, Length; } public List<Block> blocks = new List<Block>(); public Block currentBlock; public Block footerBlock = new Block(); public Block headerBlock = new Block(); public ITextTemplatingEngineHost host; public ManagementStrategy strategy; public StringBuilder template; public String OutputPath { get; set; } public Manager(ITextTemplatingEngineHost host, StringBuilder template, bool commonHeader) { this.host = host; this.template = template; OutputPath = String.Empty; strategy = ManagementStrategy.Create(host); } public void StartBlock(String name) { currentBlock = new Block { Name = name, Start = template.Length }; } public void StartFooter() { footerBlock.Start = template.Length; } public void EndFooter() { footerBlock.Length = template.Length - footerBlock.Start; } public void StartHeader() { headerBlock.Start = template.Length; } public void EndHeader() { headerBlock.Length = template.Length - headerBlock.Start; } public void EndBlock() { currentBlock.Length = template.Length - currentBlock.Start; blocks.Add(currentBlock); } public void Process(bool split) { String header = template.ToString(headerBlock.Start, headerBlock.Length); String footer = template.ToString(footerBlock.Start, footerBlock.Length); blocks.Reverse(); foreach(Block block in blocks) { String fileName = Path.Combine(OutputPath, block.Name); if (split) { String content = header + template.ToString(block.Start, block.Length) + footer; strategy.CreateFile(fileName, content); template.Remove(block.Start, block.Length); } else { strategy.DeleteFile(fileName); } } } } class ManagementStrategy { internal static ManagementStrategy Create(ITextTemplatingEngineHost host) { return (host is IServiceProvider) ? new VSManagementStrategy(host) : new ManagementStrategy(host); } internal ManagementStrategy(ITextTemplatingEngineHost host) { } internal virtual void CreateFile(String fileName, String content) { File.WriteAllText(fileName, content); } internal virtual void DeleteFile(String fileName) { if (File.Exists(fileName)) File.Delete(fileName); } } class VSManagementStrategy : ManagementStrategy { private EnvDTE.ProjectItem templateProjectItem; internal VSManagementStrategy(ITextTemplatingEngineHost host) : base(host) { IServiceProvider hostServiceProvider = (IServiceProvider)host; if (hostServiceProvider == null) throw new ArgumentNullException("Could not obtain hostServiceProvider"); EnvDTE.DTE dte = (EnvDTE.DTE)hostServiceProvider.GetService(typeof(EnvDTE.DTE)); if (dte == null) throw new ArgumentNullException("Could not obtain DTE from host"); templateProjectItem = dte.Solution.FindProjectItem(host.TemplateFile); } internal override void CreateFile(String fileName, String content) { base.CreateFile(fileName, content); ((EventHandler)delegate { templateProjectItem.ProjectItems.AddFromFile(fileName); }).BeginInvoke(null, null, null, null); } internal override void DeleteFile(String fileName) { ((EventHandler)delegate { FindAndDeleteFile(fileName); }).BeginInvoke(null, null, null, null); } private void FindAndDeleteFile(String fileName) { foreach(EnvDTE.ProjectItem projectItem in templateProjectItem.ProjectItems) { if (projectItem.get_FileNames(0) == fileName) { projectItem.Delete(); return; } } } }#>
示例模板:
生成某個數據庫下面所有的表的實體,並放在不同的文件里。

<#@ template debug="true" hostspecific="true" language="C#" #> <#@ output extension=".cs" #> <#@ assembly name="System.Core"#> <#@ import namespace="System"#> <#@ import namespace="System.Collections.Generic"#> <#@ include file="../Code/DBSchema.ttinclude"#> <#@ include file="../Code/MultiDocument.ttinclude"#> <# var manager = new Manager(Host, GenerationEnvironment, true) { OutputPath = Path.GetDirectoryName(Host.TemplateFile)}; #> <# var dbSchema=DBSchemaFactory.GetDBSchema(); List<string> tableList=dbSchema.GetTablesList(); foreach(string tableName in tableList) { manager.StartBlock(tableName+".cs"); Table table=dbSchema.GetTableMetadata(tableName); #> using System; using System.Collections.Generic; using System.Text; namespace Project.Model { [Serializable] public class <#=tableName#> { #region Constructor public <#=tableName#>() { } public <#=tableName#>(<#=table.ColumnTypeNames#>) { <# foreach(Column c in table.Columns) { #> this.<#=c.LowerColumnName#> = <#=c.LowerColumnName#>; <# } #> } #endregion #region Attributes <# foreach(Column c in table.Columns) { #> private <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.LowerColumnName#>; public <#=GeneratorHelper.GetQuesMarkByType(c.TypeName)#> <#=c.UpColumnName#> { get { return <#=c.LowerColumnName#>; } set { <#=c.LowerColumnName#> = value; } } <# } #> #endregion #region Validator public List<string> ErrorList = new List<string>(); private bool Validator() { bool validatorResult = true; <# foreach(Column c in table.Columns) { if(!c.AllowDBNull) { if(c.TypeName==GeneratorHelper.StringType) { #> if (string.IsNullOrEmpty(this.<#=c.UpColumnName#>)) { validatorResult = false; this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!"); } <# } if(c.TypeName==GeneratorHelper.DateTimeType) { #> if (this.<#=c.UpColumnName#>==null) { validatorResult = false; this.ErrorList.Add("The <#=c.UpColumnName#> should not be empty!"); } <# } } if(c.TypeName==GeneratorHelper.StringType) { #> if (this.<#=c.UpColumnName#> != null && <#=c.MaxLength#> < this.<#=c.UpColumnName#>.Length) { validatorResult = false; this.ErrorList.Add("The length of <#=c.UpColumnName#> should not be greater then <#=c.MaxLength#>!"); } <# } } #> return validatorResult; } #endregion } } <# manager.EndBlock(); } dbSchema.Dispose(); manager.Process(true); #>
6 其它
T4的編輯工具下載地址http://t4-editor.tangible-engineering.com/Download_T4Editor_Plus_ModelingTools.html
VS默認的編輯工具無高亮,無提示,錯誤不易定位。 沒這個工具,真心不想寫任何T4代碼。
所有示例代碼: CodeGenerator.zip