用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[]);
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[]);
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 > )
{
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[].ToString().ToUpper(), this.ColumnName.Substring());
}
} public string LowerColumnName
{
get
{
return string.Format("{0}{1}", this.ColumnName[].ToString().ToLower(), this.ColumnName.Substring());
}
}
} 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() == 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
用T4 Template生成代码的更多相关文章
- C# 通过T4自动生成代码
通过T4模板生成代码,运行时实现 关键代码段:Host using Microsoft.VisualStudio.TextTemplating; using System; using System. ...
- T4模板生成代码。 数据实体层与数据仓储层。备注
文件生成模板:TempleteManager.ttinclude <#@ assembly name="System.Core" #><#@ assembly n ...
- 使用T4模板生成代码的学习
之前做项目使用的都是Db First,直接在项目中添加Entity Framework,使用T4模板(T4模板引擎之基础入门)生成DAL BLL层等(T4模板是一个同事给的,也没有仔细研究,代码如下: ...
- 【转】- 使用T4模板批量生成代码
前言 之前在 “使用T4模板生成代码 - 初探” 文章简单的使用了T4模板的生成功能,但对于一个模板生成多个实例文件,如何实现这个方式呢?无意发现一个解决方案 “MultipleOutputHelpe ...
- T4模板根据数据库表和列的Description生成代码的summary的终极解决方案
相信很多人都用T4模版生成代码,用T4模版生成标准代码真的很方便.我们经常根据表生成相关的代码, 但是估计很多人都遇见过同一个问题, 特别是我们在生成model的时候,代码中model中的Summar ...
- 【Reship】use of tangible T4 template engine
1.first of all 之前在 “使用T4模板生成代码 – 初探” 文章简单的使用了T4模板的生成功能,但对于一个模板生成多个实例文件,如何实现这个方式呢?无意发现一个解决方案 “Multipl ...
- CSharpGL(12)用T4模板生成CSSL及其renderer代码
CSharpGL(12)用T4模板生成CSSL及其renderer代码 2016-08-13 由于CSharpGL一直在更新,现在这个教程已经不适用最新的代码了.CSharpGL源码中包含10多个独立 ...
- MVC实用架构设计(三)——EF-Code First(3):使用T4模板生成相似代码
前言 经过前面EF的<第一篇>与<第二篇>,我们的数据层功能已经较为完善了,但有不少代码相似度较高,比如负责实体映射的 EntityConfiguration,负责仓储操作的I ...
- T4 反射实体模型生成代码(Demo)
1.新建一个T4 Script <#@ template language="C#" debug="True" #> <#@ output ...
随机推荐
- Microsoft Visual Studio正忙解决办法
问题描述 前段时间用vs2015进行开发.出现如下问题,关闭vs进程重启vs还是无法解决. 如何解决 进入本地项目.vs文件夹 这个文件夹下有个.suo文件,删除该文件,用任务管理器杀掉vs的进程,重 ...
- 快速入门系列--WCF--06并发限流、可靠会话和队列服务
这部分将介绍一些相对深入的知识点,包括通过并发限流来保证服务的可用性,通过可靠会话机制保证会话信息的可靠性,通过队列服务来解耦客户端和服务端,提高系统的可服务数量并可以起到削峰的作用,最后还会对之前的 ...
- knockoutjs中使用mapping插件绑定数据列表
使用KO绑定数据列表示例: 1.先申请V,T,T2三个辅助方法,方便调试.声明viewModel和加载数据时的映射条件mapping 2.先使用ko.mapping.fromJS()将原来的 ...
- Razor基本语法
前言: Razor引擎的核心是识别@符号及尖括号: 1.<...></...> 2.<.../> [之所以说是"尖括号"而非& ...
- T-SQL---多值模糊查询的处理
多值模糊查询的处理 所谓多值模糊查询,就是应用程序中传递过来多个参数,对这些参数做拆分,拆分之后,对拆分结果的key值分别做模糊查询处理 对于精确匹配时,不管是单个Key值还是多个Key值,都很容易处 ...
- 让微信扫描直接下载你的APK
去年在做一个项目的时候,用微信扫描apk下载失效,显示空白页,网上找找发现在后面加一个字符串即可,于是简单搞定.过了一阵子,今年不行了,我的敏捷个人下载二维码也失效了,今天和大家说一下,现在如何解决这 ...
- 利用Navigation Timing测量页面加载时间
最近在看一本名为<web性能实践日志>的书籍,其中第十三章"网络计时"中介绍了一种比较新的计算页面各部分加载时间方法,这也是W3C Web性能工作小组正在做的事情,接下 ...
- 解决matplotlib的中文问题
matplotlib的强大无需我去言说,但它对使用中文的我来说却有一点瑕疵,那就是--在默认状态下,matplotlb无法在图表中使用中文. 在网上查找了一些资料,发现matplotlib本身是支持U ...
- a[href^=""]的解释
a[href^="绝对路径"]这个算一种属性选择器,在这里是用来选择链接属性为“绝对路径”的a标签,其中的^其实是"以什么为开始"(begin with)的意思 ...
- mvc 对一个或者多个实体验证失败
为了解决“对一个或者多个实体验证失败”,关闭调用SaveChanges() 的实体属性合法性验证(谨慎使用) db.Configuration.ValidateOnSaveEnabled = fals ...