首页 > 代码库 > 用T4 Template生成代码

用T4 Template生成代码

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#>
View Code

数据库结构的测试模板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();#>
View Code

 

注:

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();#>
View Code

注:

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 upclass 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;            }        }    }}#>
View Code

示例模板:

生成某个数据库下面所有的表的实体,并放在不同的文件里。

<#@ 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);#>
View Code

 6 其它

T4的编辑工具下载地址http://t4-editor.tangible-engineering.com/Download_T4Editor_Plus_ModelingTools.html

VS默认的编辑工具无高亮,无提示,错误不易定位。 没这个工具,真心不想写任何T4代码。

 

所有示例代码: CodeGenerator.zip