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

Smart Code Generator - 如何创建模板

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.14/5 (14投票s)

2007 年 5 月 22 日

8分钟阅读

viewsIcon

89156

Smart Code - 开源代码生成器。

Screenshot - Smart Code IDE

概述

正如本系列第一部分所述,SmartCode 使用模板引擎将某种“模型”转换为可编译代码。在本系列的第二部分中,我将教授如何构建一个用于生成存储过程的模板,以及最终如何使用 CodeSmith 风格代码生成引擎(TahoGen)的新开源实现。

您也可以跳转到本系列的第三部分,玩转 NHibernate 和 Smart Code。

现在 Smart Code 提供了一套新的模板,用于生成 NHibernate + ASPX 的代码,代码基于NHibernate ASP.NET 最佳实践文章。有关这些模板的更多信息,请参阅文章Smart Code Generator 的 NHibernate 模板

引言

大多数实际的代码生成器,无论是商业的还是开源的,都使用基于脚本的模板和模板引擎将数据从一种格式转换为另一种格式。在 .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
                

完成代码编译后,DLL 将可用于 SmartCode Studio。

编写我们的模板

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

所有代码都可在 SmartCode 的源包中获取

    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(length-of-field)。

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

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

  • 产品表中定义的本地列
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){
         //Searchs 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]);
            }
        }
       

    CodeSmith 兼容性

    它是 SmartCode 的扩展,基于 TahoGen,一个 CodeSmith 风格代码生成引擎的开源实现,此实现可以解析任何 CodeSmith 模板,为从 CodeSmith 迁移到 SmartCode 提供了一个起点。

    请参阅源包 TaHoGen 目录中的 NHibernate 示例。

    为了保持模板的基本结构并支持类似 asp 的样式,已经实现了以下扩展。

    • 项目 SmartCode.Model.Mapping (Beta),使用 get/set 属性和类(如基本 CodeSmith Schema)实现了一个对象模型。
    • TaHoGen.Core.dll 由 Philip Laureano 提供。它是一个 100% 免费的开源生成引擎。

    TahoGen 和 CodeSmith 提供的模板语言之间的差异很小。让我们看看下一个标题

           
    <%@ CodeTemplate  Language="C#" TargetLanguage="C#"  Description="Generates a C# class for use with NHibernate."%>
    <%@ Property Name="SourceTable" Type="SmartCode.Model.Mapping.CS.TableSchema" Category="Context" Description="Table that the mapping file" %>
    <%@ Property Name="Namespace" Type="System.String" Default="MyNamespace.Data" Category="Object" Description="The class namespace %>
    
        

    模板读取 SmartCode.Model.Mapping.dll 提供的类型 SmartCode.Model.Mapping.CS.TableSchema 的 SourceTable 属性和类型 System.String 的 Namespace 属性。

    SourceTable 的值由 NHibernateClass 模板的以下代码提供。

        public override PropertyTable GetCustomPropertyTable()
        {
            PropertyTable properties = new PropertyTable();
            SmartCode.Model.Mapping.CS.CSMap csMap = new SmartCode.Model.Mapping.CS.CSMap(Domain);
            csMap.RunMapToCS();
            SmartCode.Model.Mapping.CS.DatabaseSchema databaseSchema = csMap.DatabaseSchema;
    
            properties["SourceTable"] = csMap.GetTableByName(Table.Name);
    
            return properties;
        }
        

    您已经看到,在将 SmartCode.Model 对象映射到 SmartCode.Model.Mapping.CS 对象之后,代码设置了模板的 SourceTable,以与 CodeSmith 对象模型兼容。

    核心模板库

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

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

    结论

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

  • © . All rights reserved.