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






4.82/5 (34投票s)
2006年11月24日
7分钟阅读

103332

3515
SmartCode 是一个基于模板的代码生成器。本教程将介绍构建 SmartCode 模板的过程。
概述
正如本系列文章的第一部分所述,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 引擎纳入其中,您的类必须继承自 TemplateBase
类。引擎会利用反射功能调用由引擎定义的 Run
方法。
System.Collections.ArrayList Run(SmartCode.Model.Domain domain,
SmartCode.Model.NamedObject entity)
传递在“设置代码生成”对话框中定义的每个选定对象的上下文域和实体对象。
其中,“按主键删除行”、“插入一行”以及其他项目是模板,而每个节点下方的对象是分配的实体。
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);
}
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());
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;
}
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,我们可以选择将模板分配到项目级别,使其仅运行一次。每次运行代码生成时,我们都可以在主“设置代码生成”对话框的第二个选项卡“项目模板”中找到此模板,如下图所示。
为了使模板在此选项卡中可用,我们需要将 IsProjectTemplate
属性设置为 true。下面的代码展示了如何构建一个项目级模板。请注意,AllStoreProcedures
调用其他模板来构建代码,为每个域表创建单个文件,其中包含 DeleteRowByPrimaryKey
、Insert
和 Update
的代码。
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,而您……则提供了想象力、创造力和灵感。
可能存在一些拼写错误,英语对我来说很难,希望在未来的修订中能纠正这些错误。