创建代码生成器可以很简单:如何通过T4模板生成代码?[上篇]
一、代码生成器的最终使用效果我们首先来看看通过直接适用我们基于T4的SQL生成模板达到的效果。右图(点击看大图)是VS2010的Solution Explorer,在Script目录下面,我定义了三个后缀名为.tt的T4模板。它们实际上是基于同一个数据表(T_PRODUCT)的三个存储过程的生成创建的模板文件,其中P_PRODUCT_D.tt、P_PRODUCT_I.tt和P_PRODUCT_D.tt分别用于记录的删除、插入和修改。自动生成的扩展名为.sql的同名附属文件就是相应的存储过程。 基于三种不同的数据操作(Insert、Update和Delete),我创建了3个重用的、与具体数据表无关的模板: InsertProcedureTemplate、UpdateProcedureTemplate和DeleteProcedureTemplate。这样做的目的为为了实现最大的重用,如果我们需要为某个数据表创建相应的存储过程的时候,我们可以直接使用它们传入相应的数据表名就可以了。实际上,P_PRODUCT_D.tt、P_PRODUCT_I.tt和P_PRODUCT_D.tt这三个T4模板的结构很简单,它们通过<#@include>指令将定义着相应ProcedureTemplate的T4模板文件包含进来。最终的存储过程脚本通过调用ProcudureTempalte的Render方法生成。其中构造函数的参数表示的分别是连接字符串名称(在配置文件中定义)和数据表的名称。 <#@ template language="C#" hostspecific="True" #> <#@ include file="T4Toolbox.tt" #<#@ include file="..TemplatesDeleteProcedureTemplate.tt" #<# new DeleteProcedureTemplate("TestDb","T_PRODUCT").Render(); #>
new InsertProcedureTemplate("TestDb",1)"><#@ include file="..TemplatesUpdateProcedureTemplate.tt" #>
二、安装T4工具箱(ToolBox)和编辑器VS本身只提供一套基于T4引擎的代码生成的执行环境,为了利于你的编程你可以安装一些辅助性的东西。T4 ToolBox是一个CodePlex上开源的工具,它包含一些可以直接使用的代码生成器,比如Enum SQL View、AzMan wrapper、LINQ to SQL classes、LINQ to SQL schema和Entity Framework DAL等。T4 ToolBox还提供一些基于T4方面的VS的扩展。当你按照之后,在“Add New Item”对话框中就会多出一个命名为“Code Generation”的类别,其中包括若干文件模板。下面提供的T4模板的编辑工作依赖于这个工具。 为了提高编程体验,比如智能感知以及代码配色,我们还可以安装一些第三方的T4编辑器。我使用的是一个叫做Oleg Sych的T4 Editor。它具有免费版本和需要付费的专业版本,当然我使用的免费的那款。成功按装了,它也会在Add New Item”对话框中提供相应的基于T4 的文件模板。 三、创建数据表T4模板就是输入和输出的一个适配器,这与XSLT的作用比较类似。对于我们将要实现的SQL Generator来说,输入的是数据表的结构(Schema)输出的是最终生成的存储过程的SQL脚本。对于数据表的定义,不同的项目具有不同标准。我采用的是我们自己的数据库标准定义的数据表:T_PRODUCT(表示产品信息),下面是创建表的脚本。 [ID] [VARCHAR](50) NOT NULL,
[NAME] [NVARCHAR] float] FLOAT] DESC] [NVARCHAR] [CREATED_ON] [DATETIME] [LAST_UPDATED_ON] [DATETIME] TIMESTAMP] CONSTRAINT [PK_T_PRODUCT] PRIMARY KEY CLUSTERED( [ID] ASC)ON [PRIMARY]) 每一个表中有6个公共的字段:CREATED_BY、CREATED_ON、LAST_UPDATED_BY、LAST_UPDATED_ON、VERSION_NO和TRANSACTION_ID分别表示记录的创建者、创建时间、最新更新者、最新更新时间、版本号(并发控制)和事务ID。 四、创建抽象的模板:ProcedureTemplate我们需要为三不同的数据操作得存储过程定义不同的模板,但是对于这三种存储过程的SQL结构都是一样的,基本结果可以通过下面的SQL脚本表示。 ,'P' ) IS NULL
DROP PROCEDURE <<ProcedureName>> ? ( <<ParameterList>> ) <<ProcedureBody>> GO 为此我定义了一个抽象的模板:ProcedureTemplate。为了表示CUD三种不同的操作,我通过T4模板的“类特性块”(Class Feature Block)定义了如下一个OperationKind的枚举。 <#+
{ Insert,1); font-size: 8pt; overflow: visible'> Update,1); font-size: 8pt; overflow: visible'> Delete } #> 然后下面就是整个ProcedureTemplate的定义了。ProcedureTemplate直接继承自T4Toolbox.Template(来源于T4 ToolBox,它继承自TextTransformation)。ProcedureTemplate通过SMO(SQL Server Management Object)获取数据表的结构(Schema)信息,所以我们需要应用SMO相关的程序集和导入相关命名空间。ProcedureTemplate具有两个属性Table(SMO中表示数据表)和OperationKind(表示具体的CUD操作的一种),它们均通过构造函数初始化。简单起见,我们没有指定Server,而默认采用本机指定的数据库。 1: <#@ assembly name="Microsoft.SqlServer.ConnectionInfo" #>
3: <#@ assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
5: <#@ import "Microsoft.SqlServer.Management.Smo" #>
7: abstract class ProcedureTemplate : Template 9: public OperationKind OperationKind {get; private set;} 11: 13: string VersionNoParameterName = "@p_version_no"; 15: public ProcedureTemplate(string databaseName,1)">string tableName,OperationKind operationKind) 17: this.OperationKind = operationKind;
19: Database database = new Database(server,databaseName);
21: this.Table.Refresh();
23: 25: { 27: { 29: case OperationKind.Update: "_U"; 31: } 33: 35: { 37: } 39: void RenderParameterList();
41: void RenderProcedureBody();
43: override string TransformText() 45: #> ,1)">'P' ) IS NOT NULL 48: GO 50: CREATE PROCEDURE [dbo].[<#= GetProcedureName() #>] 52: <#+ 54: this.RenderParameterList();
56: #> 58: AS 60: <#+ 63: PopIndent(); 65: WriteLine("nGO");
67: } 69: #> 存储过程的参数我们采用小写形式,直接在列名前加上一个"p_”(Parameter)前缀,列名到参数名之间的转化通过方法GetParameterName实现。存储过程名称通过表明转化,转化规则为:将"T_”(Table)改成"P_”(Procedure)前缀,并添加"_I"、"_U"和"_D"表示相应的操作类型,存储过程名称的解析通过GetProcedureName实现。整个存储过程的输出通过方法TransformText输出,并通过PushIndent和PopIndent方法控制缩进。由于CUD存储只有两个地方不一致:参数列表和存储过程的主体,我定义了两个抽象方法RenderParameterList和RenderProcedureBody让具体的ProcedureTemplate去实现。 五、为CUD操作创建具体模板基类ProcedureTemplate已经定义出了主要的转化规则,我们现在需要做的就是通过T4模板创建3个具体的ProcedureTemplate,分别实现针对CUD存储过程的生成。为此我创建了三个继承自ProcedureTemplate的具体类:InsertProcedureTemplate、UpdateProcedureTemplate和DeleteProcedureTemplate,它只需要实现RenderParameterList和RenderProcedureBody这两个抽象方法既即可,下面是它们的定义。 <#@ include file="ProcedureTemplate.tt" #>
<#+ { { for(int i=0; i<this.Table.Columns.Count;i++) { Column column = this.Table.Columns[i];
if(column.Name != VersionNoField)
{ if(i<this.Table.Columns.Count -1) { WriteLine("{0,-20}[{1}],",GetParameterName(column.Name),column.DataType.Name.ToUpper());
} ,1); font-size: 8pt; overflow: visible'> } } } WriteLine("INSERT INTO [dbo].[{0}]",1)">this.Table.Name);
PushIndent("t");
"]"); } } PopIndent(); "VALUES"); WriteLine(GetParameterName(column.Name) + ",1); font-size: 8pt; overflow: visible'> WriteLine(GetParameterName(column.Name));
} PopIndent(); } #> public UpdateProcedureTemplate(void RenderParameterList()
{ this.Table.Columns.Count -1) { WriteLine(else
if(!column.InPrimaryKey) ,1)">"[" +column.Name+"WHERE");
WriteLine(,GetParameterName(column.Name));
} class DeleteProcedureTemplate : ProcedureTemplate foreach (Column column in this.Table.Columns) "TIMESTAMP"); "DELETE FROM [dbo].[{0}]",1)">this.Table.Name); "tt"); if (column.InPrimaryKey) } } #> 至于三个具体的ProcedureTemplate如何生成参数列表和主体部分,在这里就不在多做说明了。这里唯一需要强调的是:脚本的输出是通过TextTransformation的静态WriteLine方法实现,它和Console的同名方法使用一致。针对我们之前定义的数据表T_PRODUCT的结果,通过在文章开头定义的三个TT模板,最终将会生成如下的三个存储过程。 ,1)">PROCEDURE [dbo].[P_PRODUCT_I]
@p_id [VARCHAR],1); font-size: 8pt; overflow: visible'> @p_name [NVARCHAR],1); font-size: 8pt; overflow: visible'> @p_price [FLOAT],1); font-size: 8pt; overflow: visible'> @p_total_price [ @p_created_on [DATETIME],1); font-size: 8pt; overflow: visible'> @p_last_updated_by [VARCHAR] ) INSERT INTO [dbo].[T_PRODUCT]
( [ID],1); font-size: 8pt; overflow: visible'> [NAME],1); font-size: 8pt; overflow: visible'> [PRICE],1); font-size: 8pt; overflow: visible'> [TOTAL_PRICE],1); font-size: 8pt; overflow: visible'> [DESC],1); font-size: 8pt; overflow: visible'> [CREATED_BY],1); font-size: 8pt; overflow: visible'> [CREATED_ON],1); font-size: 8pt; overflow: visible'> [LAST_UPDATED_BY],1); font-size: 8pt; overflow: visible'> [LAST_UPDATED_ON],1); font-size: 8pt; overflow: visible'> [TRANSACTION_ID]
) ( @p_id,1); font-size: 8pt; overflow: visible'> @p_name,1); font-size: 8pt; overflow: visible'> @p_price,1); font-size: 8pt; overflow: visible'> @p_total_price,1); font-size: 8pt; overflow: visible'> @p_desc,1); font-size: 8pt; overflow: visible'> @p_created_by,1); font-size: 8pt; overflow: visible'> @p_created_on,1); font-size: 8pt; overflow: visible'> @p_last_updated_by,1); font-size: 8pt; overflow: visible'> @p_last_updated_on,1); font-size: 8pt; overflow: visible'> @p_transaction_id ) '[dbo].[P_PRODUCT_U]',1)">PROCEDURE [dbo].[P_PRODUCT_U] @p_version_no [TIMESTAMP],1)">VARCHAR]
SET [NAME] = @p_name,1); font-size: 8pt; overflow: visible'> [PRICE] = @p_price,1); font-size: 8pt; overflow: visible'> [TOTAL_PRICE] = @p_total_price,1)">DESC] = @p_desc,1); font-size: 8pt; overflow: visible'> [CREATED_BY] = @p_created_by,1); font-size: 8pt; overflow: visible'> [CREATED_ON] = @p_created_on,1); font-size: 8pt; overflow: visible'> [LAST_UPDATED_BY] = @p_last_updated_by,1); font-size: 8pt; overflow: visible'> [LAST_UPDATED_ON] = @p_last_updated_on,1); font-size: 8pt; overflow: visible'> [VERSION_NO] = @p_version_no,1); font-size: 8pt; overflow: visible'> [TRANSACTION_ID] = @p_transaction_id [ID] = @p_id AND
[VERSION_NO] = @p_version_no GO ,1)">PROCEDURE [dbo].[P_PRODUCT_D]
TIMESTAMP] ID = @p_id 六、局限性
|
- asp.net-mvc-4 – 防伪标记和Web测试
- 详解asp.net core封装layui组件示例分享
- asp.net-mvc-3 – OutputCache和自定义gzip压缩过滤器
- asp.net-mvc-2 – ASP.NET MVC 2显示名称DataAnnotion from
- asp.net-mvc – 更正错误后ValidationSummary是否仍然可见?
- asp.net-mvc – ASP.NET MVC:返回查询字符串完整的视图
- asp.net-mvc – 带有ASP.NET标识的MVC 5 – 用户登录时获取
- 如何使用WebAPI没有ASP.NET MVC?
- asp.net-mvc-3 – 如何在MVC应用程序中使用POCO
- 一款经典的ajax登录页面 后台asp.net