现在的位置: 首页 > 综合 > 正文

创建代码生成器可以很简单:如何通过T4模板生成代码?[上篇]

2012年08月28日 ⁄ 综合 ⁄ 共 4697字 ⁄ 字号 评论关闭

image在《基于T4的代码生成方式》中,我对T4模板的组成结构、语法,以及T4引擎的工作原理进行了大体的介绍,并且编写了一个T4模板实现了如何将一个XML转变成C#代码。为了让由此需求的读者对T4有更深的了解,我们通过T4来做一些更加实际的事情——SQL Generator。在这里,我们可以通过SQL Generator为某个数据表自动生成进行插入、修改和删除的存储过程。[文中源代码从这里下载]

一、代码生成器的最终使用效果

我们首先来看看通过直接适用我们基于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" #>

<#@ output extension="sql" #>

<#@ include file="T4Toolbox.tt" #>

<#@ include file="..\Templates\DeleteProcedureTemplate.tt" #>

<#

    new DeleteProcedureTemplate("TestDb","T_PRODUCT").Render();

#>

<#@ template language="C#" hostspecific="True" #>

<#@ output extension="sql" #>

<#@ include file="T4Toolbox.tt" #>

<#@ include file="..\Templates\InsertProcedureTemplate.tt" #>

<#

    new InsertProcedureTemplate("TestDb","T_PRODUCT").Render();

#>

<#@ template language="C#" hostspecific="True" #>

<#@ output extension="sql" #>

<#@ include file="T4Toolbox.tt" #>

<#@ include file="..\Templates\UpdateProcedureTemplate.tt" #>

<#

    new UpdateProcedureTemplate("TestDb","T_PRODUCT").Render();

#>

二、安装T4工具箱(ToolBox)和编辑器

VS本身只提供一套基于T4引擎的代码生成的执行环境,为了利于你的编程你可以安装一些辅助性的东西。T4 ToolBox是一个CodePlex上开源的工具,它包含一些可以直接使用的代码生成器,比如Enum SQL ViewAzMan wrapperLINQ to SQL classesLINQ to SQL schemaEntity Framework DAL等。T4 ToolBox还提供一些基于T4方面的VS的扩展。当你按照之后,在“Add New Item”对话框中就会多出一个命名为“Code Generation”的类别,其中包括若干文件模板。下面提供的T4模板的编辑工作依赖于这个工具。

image

为了提高编程体验,比如智能感知以及代码配色,我们还可以安装一些第三方的T4编辑器。我使用的是一个叫做Oleg Sych的T4 Editor。它具有免费版本和需要付费的专业版本,当然我使用的免费的那款。成功按装了,它也会在Add New Item”对话框中提供相应的基于T4 的文件模板。

三、创建数据表

T4模板就是输入和输出的一个适配器,这与XSLT的作用比较类似。对于我们将要实现的SQL Generator来说,输入的是数据表的结构(Schema)输出的是最终生成的存储过程的SQL脚本。对于数据表的定义,不同的项目具有不同标准。我采用的是我们自己的数据库标准定义的数据表:T_PRODUCT(表示产品信息),下面是创建表的脚本。

CREATE TABLE [dbo].[T_PRODUCT](

    [ID]                [VARCHAR](50) NOT NULL,

    [NAME]              [NVARCHAR] NOT NULL,

    [PRICE]             [float] NOT NULL,

    [TOTAL_PRICE]       [FLOAT] NOT NULL,

    [DESC]              [NVARCHAR]  NULL,

 

    [CREATED_BY]        [VARCHAR](50) NULL,

    [CREATED_ON]        [DATETIME] NULL,

    [LAST_UPDATED_BY]   [VARCHAR](50) NULL,

    [LAST_UPDATED_ON]   [DATETIME] NULL,

    [VERSION_NO]        [TIMESTAMP] NULL,

    [TRANSACTION_ID]    [VARCHAR](50) NULL,

 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脚本表示。

IF OBJECT_ID( '<<ProcedureName>>', 'P' ) IS NOT NULL

    DROP  PROCEDURE  <<ProcedureName>>

GO

 

CREATE PROCEDURE <<ProcedureName>>

(

    <<ParameterList>>

)

AS

   

    <<ProcedureBody>>

 

GO

为此我定义了一个抽象的模板:ProcedureTemplate。为了表示CUD三种不同的操作,我通过T4模板的“类特性块”(Class Feature Block)定义了如下一个OperationKind的枚举。

<#+ 

    public enum OperationKind

    {

        Insert,

        Update,

        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" #>

   2: <#@ assembly name="Microsoft.SqlServer.Smo" #>

   3: <#@ assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>

   4: <#@ import namespace="System" #>

   5: <#@ import namespace="Microsoft.SqlServer.Management.Smo" #>

   6: <#+

   7: public abstract class ProcedureTemplate : Template

   8: {

   9:     public OperationKind OperationKind {get; private set;}

  10:     public Table Table {get; private set;}

  11:     

  12:     public const string VersionNoField             = "VERSION_NO";

  13:     public const string VersionNoParameterName     = "@p_version_no";

  14:     

  15:     public ProcedureTemplate(string databaseName, string tableName,OperationKind operationKind)

  16:     {

  17:         this.OperationKind     = operationKind;

  18:         Server server = new Server();

  19:         Database database = new Database(server,databaseName);

  20:         this.Table = new Table(database, tableName);

  21:         this.Table.Refresh();

  22:     }

  23:     

  24:     public virtual string GetProcedureName()

  25:     {

  26:         switch(this.OperationKind)

  27:         {

  28:             case OperationKind.Insert:    return "P_" +this.Table.Name.Remove(0,2) + "_I";

  29:             case OperationKind.Update:    return "P_" +this.Table.Name.Remove(0,2) + "_U";

  30:             default:                    return "P_" +this.Table.Name.Remove(0,2) + "_D";

  31:         }        

  32:     }

  33:     

  34:     protected virtual string GetParameterName(string columnName)

  35:     {

  36:         return "@p_" + columnName.ToLower();

  37:     }

  38:     

  39:     protected abstract void RenderParameterList();

  40:     

  41:     protected abstract void RenderProcedureBody();        

  42:  

  43:     public override string TransformText()

  44:     {

  45: #>

【上篇】
【下篇】

抱歉!评论已关闭.