Overview
As described in part one of this series,
SmartCode uses a template engine to transform some kind of "model" into compilable
code. In this second part of the series, I will show how to work SmartCode and teach
how to build a template to generate stored procedures.
Introduction
Most of the real-world code generators, both commercial and open source releases,
use script based templates and template engines to transform data from a format
to another. In the .NET Framework, usually templates are written in ASP.NET style
<% %> syntax. And using .NET class, associated to an specific data model to
transform this input, in an formatted output, specified by the template
SmartCode is a very flexible, powerful
and open source code generator. The most new features of SmartCode is that
does not use ASP.NET style.
The Templates are created in C# or VB.NET (or theoretically in any .NET language
in which a dynamic-link library (DLL) may be created) and compiled as dlls. This
is a very powerful paradigm, to allow you using Visual Studio to create, compile
and debug the templates.
Template Libraries
Code-generation templates are organized in dlls. For a DLL to be considered an SmartCode
library, it has to expose a certain interface, which will be specified in detail
in the next section. To facilitate the creation of this interface, the SmartCode
source package comes with base classes that already declare the necessary properties
and methods, and which may be used to create libraries and templates by the use
of class inheritance.
SmartCode in Action
In SmartCode Each Template look like this (in 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
The code listed is a sample template that will write out all of the column names
from any table.
I we apply this template, to the SQL Server Northwind Employees table, the resulting
output would be.
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
Getting Started
As described SmartCode uses a template engine to transform some kind of model, the
SmartCode Model, into output code, given the output specified by a template
Internal Core Model
The internal core model is the core of SmartCode generator architecture. This contains
a set of classes that make it easily manipulate the public methods and properties,
to generate outputs.
The next figure show the basic Internal Core Model
In order to add the SmartCode engine in the game, you class must inherits from a
class TemplateBase
, the engine, using reflection features, calls the Run
Method defined by:
System.Collections.ArrayList Run(SmartCode.Model.Domain domain,
SmartCode.Model.NamedObject entity)
Passing the context domain and entity objects, defined by each selected object in
the Setting Code Generation Dialog.
Where "Delete Row by primary Key", "Insert One Row" and the other one are Templates
and the objects below each node are the Entities assigned.
The Run
method return to Engine an ArrayList
of values between them, the Output
Code and Filename, to create the output file contents.
Now that it's clear how that SmartCode works, we can concentrate on the implementation
of the TemplateBase
abstract class
Templates interface
A DLL file is considered an SmartCode Template library, whenever it directly or
not inherits from a class SmartCode.Template.TemplateBase
, and has implemented the
following properties:
TemplateBase is an abstract class you must Implement the next Methods
- OutputFileName: The actual name of the file in which to store the generated
code. This property would only be available after the ProduceOutString method(see
below) is called. For example: "Client_Sample3.sql".
Public Overloads Overrides Function OutputFileName() As String
Return Table.Code + "_Sample3.txt"
End Function
- ProduceCode: The routine that generates the code and places it in the internal
StringBuilder property "code". To generate the code, this method uses the internal
properties "Entity" and "Domain", which SmartCode will set prior to invoking it.
Entity and Domain are properties of types Table and Domain, respectively.
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
When you are done with the code sign the assembly, and add to the Global Assembly
Cache to be available to SmartCode Studio.
Writing Our Template
Now that it's clear how that Templates libraries structure, we can begin writing
our template. A good idea to start with our template is build how the output we
like. Here is an example GetRowByPrimaryKey
stored procedure for the Products table
from Northwind database.
All code is available in source.zip package of 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
)
To Build the list of primary key parameters, we can get these values from
Table.PrimaryKeyColumns()
(IList<column>)
Method, this is an List of Column that represent each Primary Key
in the current context object. Here is an simple example of code to extract the
information
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);
Where GetSqlParameterLine is an rutine defined in Common class that returns the
type of the field as a T-SQL type, like varchar(length-of-field).
There is a method worth noting, called WriteLine
, in the class TemplateBase
. In
the core libraries, it is used to write the generated code into the StringBuilder
code
property, line by line. It appends a new-line character to the string
it receives as a parameter and then adds it to the value of the code
property.
There is also a parameter-less WriteLine
, which just adds a new-line character to
the value of the code property.
Now we need build the body of the Store procedure, we can split up into 4 parts:
- The local columns defined in products table
foreach (Column columm in Table.AllColumns())
{
selectStm.AppendFormat(" [{0}].[{1}] as {2},{3}", Table.Name,
columm.Name, columm.Code, Environment.NewLine);
}
The list of values (LOV) an very nice feature added to SmartCode Model, that allow
you lookup for values in parent tables, as shows the next image:
int i = 0;
foreach (Reference reference in Table.InReferences){
foreach (ReferenceJoin join in reference.Joins){
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;
}
selectStm = new StringBuilder(Common.Substring(selectStm.ToString(),
Environment.NewLine.Length + 1));
WriteLine(selectStm.ToString());
The From Part, you can access to external 'InReferences' to build a more complex
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;
}
onJoin = Common.Substring(onJoin, Environment.NewLine.Length + 3);
selectStm.Append(onJoin);
i += 1;
}
And the Where Part
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);
Project Level Templates
In the last sections we have learn to generate code running templates assigned to
entities, But else in the practices usually we have to generate code for all objects
contained in the domain e.g. the list of all tables in the database; a simple way
to make this easier is create a new the template with next code:
foreach (Table table in Domain.ConnectionInfo.Tables)
{
WriteLine(table.Name);
}
And assign the template to any, and only one, entity and run it.
With SmartCode we have the option to assign a template to project level, to run
only one time, each time that we run the code generation, we can found this template
in the second tab called "Project Templates", in the main "Setting Code Generation"
dialog, as shows the next figure:
To be available the template in this tab we need to assign true to the property
IsProjectTemplate
. The next code show how to build a project level template,
notice that AllStoreProcedures
call other templates to build the code to create
only one file with the code for DeleteRowByPrimaryKey
, Insert
and Update
for each
table of the Domain.
public class AllStoreProcedures : TemplateBase
{
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);
base.code.Append(results[0]);
}
}
The Core Template Libraries
The SmartCode package comes with a Beta core sets of template libraries for
generating code in C#, that use stored procedures to access and modify data.
- Stored Procedures Libraries: These templates generate SQL Server 2000 scripts for
creating stored procedures that insert, delete, update, and search for records in
tables.
- Common Data Libraries (
TypedDataset
): This template generate code for XSD to Render
to TypedDataSet
objects used to transport data between the tiers
- Data Tier Libraries: These templates generate code for supporting the data access
classes
- Business Tier Libraries: These templates generate code for supporting the access
to Data Tier
Last changes
Bug fix in DataAccess layer of Core Templates Library
Conclusion
In this article we have view the huge benefits of using templates to avoid repetitive
coding tasks. SmartCode Model provide the Object Model, SmartCode Studio provide
the IDE to customize your Model and you.... provide the Imagination, Creativity
and Inspiration.
Maybe there are several spelling mistakes, English is hard for me, I hope in future
revision catch the mistakes.