65.9K
CodeProject 正在变化。 阅读更多。
Home

使用SmartCode进行模板化代码生成

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.82/5 (34投票s)

2006年11月24日

7分钟阅读

viewsIcon

103332

downloadIcon

3515

SmartCode 是一个基于模板的代码生成器。本教程将介绍构建 SmartCode 模板的过程。

SmartCode IDE

概述

正如本系列文章的第一部分所述,SmartCode 使用模板引擎将某种“模型”转换为可编译的代码。在本系列的第二部分中,我将展示如何使用 SmartCode 并教您如何构建一个生成存储过程的模板。

引言

大多数现实世界中的代码生成器(包括商业和开源版本)都使用基于脚本的模板和模板引擎来将数据从一种格式转换为另一种格式。在 .NET Framework 中,模板通常使用 ASP.NET 风格的 <% %> 语法编写。然后,结合与特定数据模型关联的 .NET 类,将输入转换为由模板指定的格式化输出。

SmartCode 是一个非常灵活、强大且开源的代码生成器。SmartCode 最新的特点是它使用 ASP.NET 风格。
模板是以 C# 或 VB.NET(理论上也可以是任何可以创建动态链接库 (DLL) 的 .NET 语言)创建的,并被编译为 DLL。这是一种非常强大的模式,允许您使用 Visual Studio 来创建、编译和调试模板。

模板库

代码生成模板被组织在 DLL 中。要使一个 DLL 被视为 SmartCode 库,它必须公开一个特定的接口,该接口将在下一节中详细说明。为了方便创建此接口,SmartCode 源代码包提供了基类,这些基类已经声明了必要的属性和方法,并且可以通过类继承来创建库和模板。

SmartCode 实战

在 SmartCode 中,每个模板(以 VB.NET 为例)看起来都像这样:

    Imports System
    Imports SmartCode.Template

    Public Class Sample3
        Inherits TemplateBase

        Public Sub New()
            MyBase.Name = "Sample3"
            MyBase.CreateOutputFile = True
            MyBase.Description = "This template demonstrates " & _ 
                                 "using static and dynamic contents."
            MyBase.OutputFolder = "Samples\Sample3"
            MyBase.IsProjectTemplate = False
        End Sub

        Public Overloads Overrides Function OutputFileName() As String
            Return Table.Code + "_Sample3.txt"
        End Function

        Public Overloads Overrides Sub ProduceCode()
            WriteLine("Table: " + Table.Name)
            For Each column As SmartCode.Model.Column In Table.AllColumns
                WriteLine("Column: {0}: {1}", column.Name, column.NetDataType)
            Next
        End Sub
    End Class

列出的代码是一个示例模板,它将输出表中所有列的名称。
如果我们对此模板应用于 SQL Server Northwind 数据库的 Employees 表,则结果输出将是:

  
    Table:          : Employees
    EmployeeID      : System.Int32      
    LastName        : System.String   
    FirstName       : System.String    
    Title           : System.String    
    TitleOfCourtesy : System.String    
    BirthDate       : System.DateTime 
    HireDate        : System.DateTime 
    Address         : System.String    
    City            : System.String    
    Region          : System.String    
    PostalCode      : System.String    
    Country         : System.String    
    HomePhone       : System.String    
    Extension       : System.String    
    Photo           : System.Byte[]   
    Notes           : System.String    
    ReportsTo       : System.Int32      
    PhotoPath       : System.String    
     

入门

正如 SmartCode 所述,它使用模板引擎将某种模型(SmartCode 模型)转换为输出代码,具体输出由模板指定。

内部核心模型

内部核心模型是 SmartCode 生成器架构的核心。它包含一组类,可以轻松地操作公共方法和属性来生成输出。
下图展示了基本的内部核心模型。

SmartCode Classes

为了将 SmartCode 引擎纳入其中,您的类必须继承自 TemplateBase 类。引擎会利用反射功能调用由引擎定义的 Run 方法。

System.Collections.ArrayList Run(SmartCode.Model.Domain domain, 
                                 SmartCode.Model.NamedObject entity)

传递在“设置代码生成”对话框中定义的每个选定对象的上下文域和实体对象。

SmartCode
其中,“按主键删除行”、“插入一行”以及其他项目是模板,而每个节点下方的对象是分配的实体。

Run 方法向引擎返回一个 ArrayList,其中包含输出代码和文件名等值,用于创建输出文件的内容。

现在我们已经清楚了 SmartCode 的工作原理,我们可以集中精力实现 TemplateBase 抽象类。

模板接口

当一个 DLL 文件直接或间接继承自 SmartCode.Template.TemplateBase 类并实现了以下属性时,它就被视为 SmartCode 模板库:

  • Name:模板的名称。例如:“Sample3”。 MyBase.Name = "Sample3"(VB.NET)
  • Description:模板功能的描述。例如:“此模板演示了使用静态和动态内容”。

    MyBase.Description = "This template demonstrates using" & _ 
                         " static and dynamic contents."
  • OutputFolder:用于放置生成代码文件的文件夹的相对路径。它是相对的,因为正如在代码生成结果部分所述,在生成代码之前,SmartCode Studio 会询问要将生成的文件放置在哪个文件夹中。例如:“Samples\Sample3”。 MyBase.OutputFolder = "Samples\Sample3"
  • CreateOutputFile:是否作为代码生成过程的一部分创建文件。虽然模板通常会生成代码以放置在单个文件中,但某些模板可能需要生成多个文件。

    MyBase.CreateOutputFile = True
    IsProjectTemplate:如果模板是为每个实体运行创建的,还是仅为项目运行。
    MyBase.IsProjectTemplate = False '仅为项目运行一次

TemplateBase 是一个抽象类,您必须实现以下方法:

  • OutputFileName:存储生成代码的实际文件名。此属性仅在调用 ProduceOutString 方法(见下文)后可用。例如:“Client_Sample3.sql”。

        Public Overloads Overrides Function OutputFileName() As String
            Return Table.Code + "_Sample3.txt"
        End Function
  • ProduceCode:生成代码并将其放置在内部 StringBuilder 属性“code”中的例程。为了生成代码,此方法使用内部属性“Entity”和“Domain”,SmartCode 会在调用它之前设置这两个属性。Entity 和 Domain 分别是 Table 和 Domain 类型的属性。

        Public Overloads Overrides Sub ProduceCode()
           WriteLine("Table: " + Table.Name)
           For Each column As SmartCode.Model.Column In Table.AllColumns
                WriteLine("Column: {0}: {1}", column.Name, column.NetDataType)
           Next
        End Sub
                

完成代码后,请签名程序集,并将其添加到全局程序集缓存中,以便 SmartCode Studio 可以使用。

编写我们的模板

现在我们已经清楚了模板库的结构,我们可以开始编写我们的模板了。开始模板编写的一个好方法是构建我们喜欢的输出。以下是一个针对 Northwind 数据库的 Products 表的 GetRowByPrimaryKey 存储过程示例。

所有代码均包含在 SmartCode 的 source.zip 包中。

CREATE PROCEDURE usp_Products_GetRowByPrimaryKey
(
     @ProductID int 
)
AS
SELECT 
    [Products].[ProductID] as ProductID,
    [Products].[ProductName] as ProductName,
    [Products].[SupplierID] as SupplierID,
    [Products].[CategoryID] as CategoryID,
    [Products].[QuantityPerUnit] as QuantityPerUnit,
    [Products].[UnitPrice] as UnitPrice,
    [Products].[UnitsInStock] as UnitsInStock,
    [Products].[UnitsOnOrder] as UnitsOnOrder,
    [Products].[ReorderLevel] as ReorderLevel,
    [Products].[Discontinued] as Discontinued,
    [T0].[CategoryName] as CategoryID_CategoryName, 
    [T0].[Description] as CategoryID_Description,
    [T1].[CompanyName] as SupplierID_CompanyName, 
    [T1].[ContactName] as SupplierID_ContactName  
    FROM [Products]      LEFT OUTER JOIN 
    Categories AS T0  ON  T0.[CategoryID] = 
               [Products].[CategoryID]      LEFT OUTER JOIN 
    Suppliers AS T1  ON  T1.[SupplierID] = [Products].[SupplierID] 
    WHERE 
    (
          [ProductID] = @ProductID 
    )        
    

要构建主键参数列表,我们可以从 Table.PrimaryKeyColumns() (IList<column>) 方法中获取这些值。这是一个 Column 列表,代表当前上下文对象中的每个主键。下面是一个提取信息的简单代码示例:

    string inputParameters = String.Empty;
    foreach (Column column in Table.PrimaryKeyColumns())
    {
        inputParameters += "    " + Common.GetSqlParameterLine(column);
    }
    inputParameters = Common.Substring(inputParameters, 
                      Environment.NewLine.Length + 1);
    WriteLine(" ({0} {1} {0})", Environment.NewLine, inputParameters);

其中 GetSqlParameterLine 是 Common 类中定义的例程,它将字段类型返回为 T-SQL 类型,例如 varchar(字段长度)。

TemplateBase 类中有一个值得注意的方法,称为 WriteLine。在核心库中,它用于逐行将生成的代码写入 StringBuilder 属性 code 中。它在接收到的字符串末尾附加一个换行符,然后将其添加到 code 属性的值中。还有一个无参数的 WriteLine,它只在 code 属性的值中添加一个换行符。

现在我们需要构建存储过程的主体,我们可以将其分为 4 部分:

  • Products 表中定义的本地列
    foreach (Column columm in Table.AllColumns())
    {
        selectStm.AppendFormat("    [{0}].[{1}] as {2},{3}", Table.Name, 
                  columm.Name, columm.Code, Environment.NewLine);
    }
    
  • 值列表 (LOV),这是 SmartCode 模型添加的一个非常好的功能,允许您查找父表中的值,如下面的图像所示:

    SmartCode LOV

        int i = 0;
        foreach (Reference reference in Table.InReferences){
            foreach (ReferenceJoin join in reference.Joins){
                //Look for LOV
                foreach (Column lovColumn in join.LOV){
                    if (lovColumn.Name != join.ParentColumn.Name){
                        selectStm.AppendFormat("    [T{0}].[{1}] as {2}_{3},{4}", 
                           i, lovColumn.Name, join.ChildColumn.Code, 
                           lovColumn.Code, Environment.NewLine);
                    }
                }
            }
            i += 1;
        }
        
        //Remove the '\n,'
        selectStm = new StringBuilder(Common.Substring(selectStm.ToString(), 
                                      Environment.NewLine.Length + 1));
        //Write the code
        WriteLine(selectStm.ToString());
        
  • FROM 部分,您可以访问外部“InReferences”来构建更复杂的 FROM。
        i = 0;
        string alignment = "";
    
        foreach (Reference reference in Table.InReferences)
        {
            if  (reference.Alignment == Reference.AlignmentType.Inner)
                 alignment = " INNER JOIN ";
            else if (reference.Alignment == Reference.AlignmentType.Left) 
                 alignment = " LEFT OUTER JOIN ";
            else if (reference.Alignment == Reference.AlignmentType.Right) 
                 alignment = " RIGHT OUTER JOIN ";
    
            selectStm.AppendFormat("    {0} {1} AS T{2} ",alignment + 
                      Environment.NewLine , reference.ParentTable.Name, i);  
    
            string onJoin = "";
            foreach (ReferenceJoin join in reference.Joins)
            {
                onJoin += String.Format(" ON  T{0}.[{1}] = [{2}].[{3}] AND", i, 
                          join.ParentColumn.Name, Table.Name, 
                          join.ChildColumn.Name);
                onJoin += Environment.NewLine;
            }
            //Remove the last AND
            onJoin = Common.Substring(onJoin, Environment.NewLine.Length + 3);
            selectStm.Append(onJoin);
    
            i += 1;
        }
        
  • WHERE 部分
                
        string keyCondition = String.Empty;
        foreach (Column column in Table.PrimaryKeyColumns())
        {
            keyCondition += String.Format(" [{0}] = @{1} AND {2}", 
                            column.Name, column.Code, Environment.NewLine);
        }
        keyCondition = Common.Substring(keyCondition, 
                      (Environment.NewLine.Length + 5));
    
        WriteLine("    WHERE {0}({0} {1} {0})", Environment.NewLine, 
                  keyCondition);         
    
    
    
  • 项目级模板

    在最后几节中,我们学习了如何生成运行分配给实体的模板的代码。但实际上,我们通常需要为域中包含的所有对象生成代码,例如数据库中的所有表列表。一种简化的方法是创建一个包含以下代码的新模板:

     
        foreach (Table table in Domain.ConnectionInfo.Tables)
        {
            WriteLine(table.Name);
        }
       
    然后将模板分配给任意一个(且仅一个)实体并运行它。

    使用 SmartCode,我们可以选择将模板分配到项目级别,使其仅运行一次。每次运行代码生成时,我们都可以在主“设置代码生成”对话框的第二个选项卡“项目模板”中找到此模板,如下图所示。

    SmartCode Project Level templates

    为了使模板在此选项卡中可用,我们需要将 IsProjectTemplate 属性设置为 true。下面的代码展示了如何构建一个项目级模板。请注意,AllStoreProcedures 调用其他模板来构建代码,为每个域表创建单个文件,其中包含 DeleteRowByPrimaryKeyInsertUpdate 的代码。

     
        public class AllStoreProcedures : TemplateBase
        {
            // Constructor.
            public AllStoreProcedures()
            {
                this.IsProjectTemplate = true;
                this.createFile = true;
                this.description = "Generates all stored procedure for all entities in 
                                    the domain";
                this.name = "AllStoreProcedures";
                this.outputFolder = @"Stored Procedures\AllStoreProcedures";
            }
    
            public override string OutputFileName()
            {
                return Domain.Name + "_AllSps.sql";
            }
    
            public override void ProduceCode()
            {
    
                foreach (Table table in Domain.ConnectionInfo.Tables)
                {
                    if (table.IsTable && table.PrimaryKeyColumns().Count > 0)
                    {
                        RunTemplate(new DeleteRowByPrimaryKey(), table);
                        RunTemplate(new Insert(), table);
                        RunTemplate(new Update(), table);
                    }
                }
            }
    
            private void RunTemplate(TemplateBase template, Table table)
            {
                System.Collections.ArrayList results = template.Run(Domain, table);
    
                //The code prop, containts the output string. append the results, 
                //look for Run 
                base.code.Append(results[0]);
            }
        }
       

    核心模板库

    SmartCode 包附带了一套Beta 核心模板库,用于在 C# 中生成代码,这些代码使用存储过程来访问和修改数据。

    • 存储过程库:这些模板生成 SQL Server 2000 脚本,用于创建插入、删除、更新和搜索记录的存储过程。
    • 通用数据库(TypedDataset):此模板为 XSD 生成代码,以呈现用于在各个层之间传输数据的 TypedDataSet 对象。
    • 数据层库:这些模板为支持数据访问类生成代码。
    • 业务层库:这些模板为支持数据层访问生成代码。

    最近更新

    Core Templates Library 的 DataAccess 层中的 Bug 修复。

    结论

    在本文中,我们看到了使用模板来避免重复编码任务的巨大好处。SmartCode 模型提供了对象模型,SmartCode Studio 提供了自定义模型的 IDE,而您……则提供了想象力、创造力和灵感。

    可能存在一些拼写错误,英语对我来说很难,希望在未来的修订中能纠正这些错误。
    © . All rights reserved.