zoukankan      html  css  js  c++  java
  • 用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.CodeGeneratorinDebugMySql.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)inDebugMySql.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 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;
                }
            }
        }
    }#>
    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

  • 相关阅读:
    涂抹mysql笔记-安装mysql
    使用Oracle DBLink进行数据库之间对象的访问操作
    mysqldump备份与恢复笔记
    linux6下源码安装mysql5.6
    虚拟机克隆之后,网卡名称从eth0变成eth1之后的解决办法
    linux添加zabbix service并开机自动启动
    理解Linux系统负荷load average
    oracle体系结构理解
    C#中Abstract和Virtual[转载]
    WPF后台写ControlTemplate总结
  • 原文地址:https://www.cnblogs.com/dataadapter/p/3844394.html
Copyright © 2011-2022 走看看