Click here to Skip to main content
15,879,535 members
Articles / Programming Languages / C#

Mo+- An evolution of the template based code generator

Rate me:
Please Sign up or sign in to vote.
4.42/5 (8 votes)
18 Jul 2014CPOL9 min read 28.8K   183   20   6
Comparing the relative effectiveness of CodeSmith, T4, and Mo+ in meeting code generation requirements.

Introduction

We believe in the power of a template based approach to code generation. But, we believe in much more. We believe you need to fully control the structure and behavior of your code. We believe you need to easily manage exceptions, business rules, and other special rules and presentations. We believe you need the ability to easily define and maintain your coding best practices. We believe you need full support in maintaining, not just creating your generated code.

We believe that evolving a template based code generation approach into a model driven and model oriented approach is the best way to manage high quality generated code in conjunction with your custom code.

Object oriented programmers know the benefits of object oriented languages over procedural languages in creating large and complex software, benefits such as encapsulation, reusability, modularity, clarity, etc. Why shouldn't you have these same benefits from a code generation perspective? You do with Mo+! The Mo+ approach to code generation is the only one that is template based, model driven, truly model oriented, and focuses on code maintenance as well as initial code generation.<o:p>

In this article, we will compare two template based code generators (CodeSmith, and T4) with Mo+. We will use each approach to create a set of simple data access layer classes for each table in the Northwind SQL Server database. We will compare the end results, and the templates, in particular how the templates can be used as building blocks for more complex tasks.

Background

The Mo+ model oriented programming language and the Mo+ Solution Builder IDE for model oriented development was introduced in this Code Project article.

The Mo+ open source technology is available at moplus.codeplex.com. Video tutorials and other materials are also available at this site. The Mo+ Solution Builder also contains extensive on board help.

If you are following this article while using Mo+ Solution Builder and need to know how to load a solution model from a database, watch this tutorial on Loading a Model from SQL Server. You can use the templates attached to this article.

The Problem To Solve

We want to generate a set of simple data access layer C# classes, one for each table in the Northwind SQL Server database. The class will only include get/set properties for each table column. The following Category class is an example of the desired output that corresponds with the Categories table:

C#
using System;
namespace Test3.DAL
{
    public class Category
    {
 
        /* this property gets/sets CategoryID */
        public int CategoryID { get; set; }
 
        /* this property gets/sets CategoryName */
        public string CategoryName { get; set; }
 
        /* this property gets/sets Description */
        public string Description { get; set; }
 
        /* this property gets/sets Picture */
        public byte[] Picture { get; set; }
    }
} 

Solving It With CodeSmith

CodeSmith is a particularly powerful and flexible template based code generator. To solve this problem, we created a Generator project within the Visual Studio project where we wanted the DAL class files. The Generator project sets up the properties for, and calls the following Master template:

ASP.NET
<%@ CodeTemplate Src="TemplateBase.cs" Inherits="Lib.Templates.TemplateBase"  OutputType="None" Language="C#" TargetLanguage="Text" Debug="False" %>
<%@ Property Name="SourceDatabase" Type="SchemaExplorer.DatabaseSchema" Optional="False" %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Property Name="ResultsFolder" Type="System.String" Default="DAL" Optional="False" %>
<%@ Import Namespace="System.IO" %>
<%@ Register Name="DALEntity" Template="DALEntity.cst" %>
 
<script runat="template">
public override void Render(TextWriter writer)
{
    if (!Directory.Exists(ResultsFolder)) Directory.CreateDirectory(ResultsFolder);
    
    foreach (TableSchema table in SourceDatabase.Tables)
    {
        CreateDALEntity(table);
    }
}
public void CreateDALEntity(TableSchema table)
{
    DALEntity dalEntity = this.Create<DALEntity>();
    dalEntity.SourceTable = table;
    dalEntity.ClassName = GetClassName(table.Name.ToCSharpIdentifier().ToPascalCase());
 
    OutputFile outputFile = new OutputFile(GetClassFileName(ResultsFolder, dalEntity.ClassName));
    
    dalEntity.RenderToFile(outputFile, true);
}
</script> 

For each database table, the CreateDALEntity method is called to create a DAL class file. This method creates an instance of the <code>DALEntity template, sets up that template's properties, and then renders that template's content to an output DAL class file.

The Master template inherits this base class that provides reusable methods to get best practice information for a class:

C#
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using CodeSmith.Engine;
 
namespace Lib.Templates
{
    public partial class TemplateBase : CodeTemplate
    {
        public string GetClassName(string name)
        {
            string className = name;
            if (className.EndsWith("ies") == true)
            {
                // replace with y
                className = className.Substring(0, className.Length - 3) + "y";
            }
            else if (className.EndsWith("xes") == true
                     || className.EndsWith("ses") == true)
            {
                // chop off es
                className = className.Substring(0, className.Length - 2);
            }
            else if (className.EndsWith("as") == true
                     || className.EndsWith("is") == true
                     || className.EndsWith("os") == true
                     || className.EndsWith("us") == true)
            {
                // leave as is
            }
            else if (className.EndsWith("s") == true)
            {
                // chop off s
                className = className.Substring(0, className.Length - 1);
            }
            return className;
        }
 
        public string GetClassFileName(string directory, string className)
        {
            return String.Format("{0}/{1}.cs", directory, className);
        }
    }
}

The DALEntity template builds the content for the DAL class file:

ASP.NET
<%@ Template Language="C#" TargetLanguage="C#" Description="An example on creating a class with properties from a database table." %>
<%@ Property Name="SourceTable" Type="SchemaExplorer.TableSchema" Category="DataSource" Optional="False" %>
<%@ Property Name="Namespace" Type="System.String" Default="Test.DAL" Optional="False" %>
<%@ Property Name="ClassName" Type="System.String" Optional="False" %>
<%@ Assembly Name="SchemaExplorer" %>
<%@ Import Namespace="SchemaExplorer" %>
<%@ Import Namespace="CodeSmith.Core.Extensions" %>
 
using System;
namespace <%= Namespace %>
{
    public class <%= ClassName %>
    {
        <% foreach (var column in SourceTable.Columns) { %>
        
        /* this property gets/sets <%= column.Name.ToCSharpIdentifier().ToPascalCase() %> */
        public <%= column.SystemType.FullName %> <%= column.Name.ToCSharpIdentifier().ToPascalCase() %> { get; set; }
        <% } %>
    }
} 

This template utilizes the Namespace and ClassName properties set up by the Master template, and then produces property content for each column in the table.

The Category class generated by these templates looks like:

C#
using System;
namespace Test.DAL
{
    public class Category
    {
        
        /* this property gets/sets CategoryID */
        public System.Int32 CategoryID { get; set; }
        
        /* this property gets/sets CategoryName */
        public System.String CategoryName { get; set; }
        
        /* this property gets/sets Description */
        public System.String Description { get; set; }
        
        /* this property gets/sets Picture */
        public System.Byte[] Picture { get; set; }
    }
} 

Solving It With T4

T4 is also an effective template based code generator. To solve this problem, we created a Master template within the Visual Studio project where we wanted the DAL class files. The Master template is the high level driver in generating the code:

T4
<#@ template language="C#" hostspecific="true" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<#@ assembly name="Microsoft.SqlServer.Smo" #>
<#@ assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<#@ import namespace="System.IO" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="Microsoft.SqlServer.Management.Common" #>
<#@ import namespace="Microsoft.SqlServer.Management.Smo" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#
    string connectionString = @"Server=INCODE-1;Trusted_Connection=True;";
    string databaseName = "Northwind";
    string resultsFolder = @"\DAL\";

    if (!Directory.Exists(resultsFolder)) Directory.CreateDirectory(resultsFolder);

    Server server = new Server(new ServerConnection(new SqlConnection(connectionString)));
    Database database = server.Databases[databaseName];
    foreach (Table table in database.Tables)
    {
        string tableName = table.Name;
        if (!tableName.Equals("sysdiagrams"))
        {
            MasterTemplateHelper.CreateDALEntity(Host, connectionString, databaseName, tableName, resultsFolder);
        }
    }
#>
<#+
public class MasterTemplateHelper
{
    public static void CreateDALEntity(ITextTemplatingEngineHost host, string connectionString,
                                       string databaseName, string tableName, string resultsFolder)
    {
        string projectNamespace = "Test2.DAL";
        string className = tableName.Replace(" ", "");
        string classFileName;
        string relativeOutputFilePath = null;

        className = GetClassName(className);
        classFileName = GetClassFileName(resultsFolder, className);
    
        string templateFile = host.ResolvePath("DALEntity.tt");
        string templateContent = File.ReadAllText(templateFile);

        TextTemplatingSession session = new TextTemplatingSession();
        session["Namespace"] = projectNamespace;
        session["ClassName"] = className;
        session["ConnectionString"] = connectionString;
        session["DatabaseName"] = databaseName;
        session["TableName"] = tableName;

        var sessionHost = (ITextTemplatingSessionHost) host;
        sessionHost.Session = session;

        Engine engine = new Engine();
        string generatedContent = engine.ProcessTemplate(templateContent, host);

        relativeOutputFilePath = resultsFolder + className + ".cs";
        WriteTemplateOutputToFile(relativeOutputFilePath, host, generatedContent);
    }

    public static string GetClassName(string name)
    {
        string className = name;
        if (className.EndsWith("ies") == true)
        {
            // replace with y
            className = className.Substring(0, className.Length - 3) + "y";
        }
        else if (className.EndsWith("xes") == true || className.EndsWith("ses") == true)
        {
            // chop off es
            className = className.Substring(0, className.Length - 2);
        }
        else if (className.EndsWith("as") == true
                 || className.EndsWith("is") == true
                 || className.EndsWith("os") == true
                 || className.EndsWith("us") == true)
        {
            // leave as is
        }
        else if (className.EndsWith("s") == true)
        {
            // chop off s
            className = className.Substring(0, className.Length - 1);
        }
        return className;
    }

    public static string GetClassFileName(string directory, string className)
    {
        return String.Format("{0}/{1}.cs", directory, className);
    }

    public static void WriteTemplateOutputToFile(
        string relativeOutputFilePath,
        Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost Host,
        string templateText)
    {
        string outputPath = System.IO.Path.GetDirectoryName(Host.TemplateFile);
        string outputFilePath = outputPath + relativeOutputFilePath;
        System.IO.File.WriteAllText(outputFilePath, templateText);
    }
}
#>

For each database table, the CreateDALEntity method is called to create a DAL class file. This method accesses the <code>DALEntity template, sets up global TextTemplatingSession variables (this could also be done with CallContext), and then renders the <code>DALEntity template's content to an output DAL class file.

The Master template also includes MasterTemplateHelper methods to get best practice information for a class, and for generating an output file.

The DALEntity template builds the content for the DAL class file:

T4
<#@ template language="C#" hostspecific="true" #>
<#@ parameter name="Namespace" Type="System.String" Default="MyProject.DAL" Optional="False" #>
<#@ parameter name="ClassName" Type="System.String" Optional="False" #>
<#@ parameter name="ConnectionString" Type="System.String" Optional="False" #>
<#@ parameter name="DatabaseName" Type="System.String" Optional="False" #>
<#@ parameter name="TableName" Type="System.String" Optional="False" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Xml" #>
<#@ assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<#@ assembly name="Microsoft.SqlServer.Smo" #>
<#@ assembly name="Microsoft.SqlServer.Management.Sdk.Sfc" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="Microsoft.SqlServer.Management.Common" #>
<#@ import namespace="Microsoft.SqlServer.Management.Smo" #>
<#
    Server server = new Server(new ServerConnection(new SqlConnection(ConnectionString)));
    Database database = server.Databases[DatabaseName];
    Table sourceTable = database.Tables[TableName];
#>
using System;
namespace <#= Namespace #>
{
    public class <#= ClassName #>
    {
<#
foreach (Column column in sourceTable.Columns)
{
#>
        
        /* this property gets/sets <#= column.Name #> */
        public <#= DALTemplateHelper.GetClrType(column.DataType.ToString()) #> <#= column.Name #> { get; set; }
<#
}#>
    }
}
<#+
public class DALTemplateHelper
{
    public static string GetClrType(string sqlType)
    {
        switch (sqlType)
        {
            case "bigint":
                return "long";

            case "binary":
            case "image":
            case "timestamp":
            case "varBinary":
                return "byte[]";

            case "bit":
                return "bool";

            case "char":
            case "nchar":
            case "ntext":
            case "nvarchar":
            case "text":
            case "varchar":
            case "xml":
                return "string";

            case "datetime":
            case "smalldatetime":
            case "date":
            case "time":
            case "datetime2":
                return "DateTime";

            case "decimal":
            case "money":
            case "smallmoney":
                return "decimal";

            case "float":
                return "double";

            case "int":
                return "int";

            case "real":
                return "float";

            case "uniqueidentifier":
                return "Guid";

            case "smallint":
                return "short";

            case "tinyint":
                return "byte";

            case "variant":
            case "udt":
                return "object";

            case "structured":
                return "DataTable";

            case "datetimeoffset":
                return "DateTimeOffset";

            default:
                return "object";
        }
    }
}
#>

This template utilizes the Namespace, ClassName, ConnectionString, DatabaseName, and TableName properties set up by the Master template, and then produces property content for each column in the table. A DALTemplateHelper method is also provided to get the CLR type from the SQL Server type.

The Category class generated by these templates looks like:

C#
using System;
namespace Test2.DAL
{
    public class Category
    {
        
        /* this property gets/sets CategoryID */
        public int CategoryID { get; set; }
        
        /* this property gets/sets CategoryName */
        public string CategoryName { get; set; }
        
        /* this property gets/sets Description */
        public string Description { get; set; }
        
        /* this property gets/sets Picture */
        public byte[] Picture { get; set; }
    }
} 

Solving It With Mo+

To solve this problem with Mo+, we created a Solution model in the same directory as the Visual Studio project where we wanted the DAL class files. Using the MDLSqlModel SQL Server spec template (attached), this model is loaded with basic Entity and Property information that corresponds to tables and columns in the Northwind database. Unlike with CodeSmith or T4, with Mo+, you have ready access to model information that will make it much easier on the code generation side. You also have the ability to define and augment your model programatically or manually with the information you need to make it easier to generate code.

The Solution level Master template is the high level driver in generating the code. This template has no content, and its output area is as follows (images are shown next to code blocks to show proper syntax highlighting):

ASP.NET
<%%:
foreach (Entity)
{
<%%>DALEntity%%>
}
%%>  

Master Template Output

This template goes through each Entity (table) in the solution, and calls the Entity level DALEntity template to output its content. At line 4, the DALEntity template is embodied within output tags to signify the template to produce its output.

With model information and model oriented templates, creating master templates is dead simple. There are no additional hoops to jump through in setting up properties or setting up calls to other templates. Instead of needing to pass in all of the relevant data in a procedure like manner, the templates being called encapsulate all of the information they need to produce their content AND output from the model and/or other templates (if necessary, templates can also have parameters to pass in additional data).

The content area of the DALEntity template produces the content for the DAL class:

ASP.NET
<%%-using System;
namespace %%><%%=Solution.Namespace%%><%%-
{
    public class %%><%%=DALClassName%%><%%-
    {%%>
    <%%:
    foreach (Property)
    {
        <%%=DALProperty%%>
    }
    %%><%%-
    }
}%%> 

DAL Entity template content

The information to build the DAL class content is pulled directly from the model, in a model oriented way based on which instance of Entity this template call is made. The namespace is retrieved from the Solution. The class name is retrieved from an Entity level template called DALClassName. Then, the template goes through each Property for the Entity, and gets the property content from the Property level DALProperty template.

Unlike with CodeSmith or T4 where you have to pass data into a template with a procedural approach, with Mo+, the encapsulated nature of templates encourages natural building blocks. In fact, Mo+ templates can be used like any built in model oriented property (or method if the template has parameters) to retrieve information to be used in expressions, or to build up content within property tags (as above).

With CodeSmith or T4 , you have little support over where, when, or how to output your generated documents. Your documents generally get regenerated every time. With Mo+ however, full control over output decision making is built in. The output area of the DALEntity template embodies the decision making for writing the DAL class content to disk:

ASP.NET
<%%=Solution.SolutionDirectory%%><%%-\DAL\%%><%%=DALClassFileName%%>
<%%:
if (File(Path) != Text)
{
    update(Path)
}
%%> 

Line 1 builds up the Path where the DAL class file is to be stored. At line 3, if the contents of the file on disk is different than the Text (template content), then the update statement is called to update the Text of the file at Path. Encapsulating the output decision making here at the template level improves the reusability, since this decision making does not have to be replicated by each calling template.

Instead of using a procedure to get class name information, the Entity level DALClassName template is a reusable building block:

C#
<%%:
var className = EntityName.CapitalCase().Replace(" ", "").Replace("_", "")
if (className.EndsWith("ies") == true)
{
    // replace with y
    className = className.Substring(0, className.Length - 3) + "y"
}
else if (className.EndsWith("xes") == true
          || className.EndsWith("ses") == true)
{
    // chop off es
    className = className.Substring(0, className.Length - 2)
}
else if (className.EndsWith("as") == true
          || className.EndsWith("is") == true
          || className.EndsWith("os") == true
          || className.EndsWith("us") == true)
{
    // leave as is
}
else if (className.EndsWith("s") == true)
{
    // chop off s
    className = className.Substring(0, className.Length - 1)
}
%%>
<%%=className%%> 

For further simplification, the above logic could be used to create the name for the Entity when loading the model from the database.

Finally, the Property level DALProperty template builds up the content for the get/set property:

ASP.NET
<%%-
        /* this property gets/sets %%><%%=PropertyName%%><%%- */
        public %%><%%=CSharpDataType%%><%%- %%><%%=PropertyName%%><%%- { get; set; }%%>

Two other templates used in this example, DALClassFileName and CSharpDataType are attached.

The Category class generated by these templates looks like:

C#
using System;
namespace Test3.DAL
{
    public class Category
    {

        /* this property gets/sets CategoryID */
        public int CategoryID { get; set; }

        /* this property gets/sets CategoryName */
        public string CategoryName { get; set; }

        /* this property gets/sets Description */
        public string Description { get; set; }

        /* this property gets/sets Picture */
        public byte[] Picture { get; set; }
    }
} 

Scaling Your Templates

Clearly, you can solve the above code generation problem easily with CodeSmith , T4, or Mo+, How well do these methods scale as the complexity and scope of your code generation needs increase?

How can you scale to manage exceptions? Say for example that certain properties in your DAL classes needed to be read only. This would be difficult to do in CodeSmith or T4, if you cannot glean from the database schema directly which properties should be read only. But it is easy with Mo+. You can easily tag properties or other model elements with any number of free form tag keywords such as READ_ONLY. An updated DALProperty template to manage the read only property exceptions could look something like:

C#
<%%-
%%>
<%%:
if (Tags.Contains("READ_ONLY") == true)
{
<%%-
        /* this property gets %%><%%=PropertyName%%><%%- */
        private %%><%%=CSharpDataType%%><%%- %%><%%=PropertyName.UnderscoreCase()%%><%%-
        public %%><%%=CSharpDataType%%><%%- %%><%%=PropertyName%%><%%- { get { return %%><%%=PropertyName.UnderscoreCase()%%><%%-;) }%%>
}
else
{
<%%-
        /* this property gets/sets %%><%%=PropertyName%%><%%- */
        public %%><%%=CSharpDataType%%><%%- %%><%%=PropertyName%%><%%- { get; set; }%%>
}
%%> 

How can you scale to wire together distant elements in the same or different layers? Say for example that you want to augment your DAL classes to include List properties of other DAL classes. This would be cumbersome to do in CodeSmith or T4, where you would need to retrieve your distant table, and call the GetClassName function to retrieve the name of the class. But again, it is easy with Mo+. Just reuse the DALClassName template with an Entity from a model search, or directly from a related Entity in the model. In the updated DALEntity template,, property information is created for each related Collection for the Entity:

C#
<%%-using System;
namespace %%><%%=Solution.Namespace%%><%%-
{
    public class %%><%%=DALClassName%%><%%-
    {%%>
    <%%:
    foreach (Property)
    {
        <%%=DALProperty%%>
    }
    foreach (Collection)
    {
        <%%-
        List<%%><%%=ReferencedEntity.DALClassName%%><%%-> %%><%%=ReferencedEntity.DALClassName%%><%%-List { get; set; }%%>
    }
    %%><%%-
    }
}%%>  

 

In Summary

Hopefully this article has given you a good idea on how the Mo+ model oriented approach compares with other template based approaches, and how a model oriented approach is ideally suited to scale and address more complex code generation requirements. Please ask questions if anything is not clear or should be expanded upon.

Also, please let me know if there are better or more scalable ways to solve the above problem using either CodeSmith or T4. I will gladly update the article to show the best use of those methods.

Finally, I hope you try Mo+ and the Mo+ Solution Builder. The free, open source product is available at moplus.codeplex.com. In addition to the product, this site contains sample packs for building more complete models and generating complete working applications.

Become a Member!

The Mo+ community gets additional support, and contributes to the evolution of Mo+ via the web site at https://modelorientedplus.com Being a member gives Mo+ users additional benefits such as additional forum support, member contributed tools, and the ability to vote on and/or contribute to the direction of Mo+. Also, we will be running monthly contests for members, where you can win $ for developing solutions using Mo+.

If you are the least bit interested in efficient model oriented software development, please sign up as a member. It's free, and you won't get spam email!

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Intelligent Coding Solutions, LLC
United States United States
I enjoy coding like an excellent beer. My particular passion and experience lies in the realm of modeling and code generation. In the late 80s and early 90s, I was involved in early modeling and code generation tools that reached the marketplace, including a tool that modeled FORTRAN programs and generated FORTRAN for parallel supercomputer architectures, and a tool that managed Shlaer-Mellor models and generated C++ code. Over the years, I have applied Shlaer-Mellor, UML, and custom modeling and various code generation techniques to greatly benefit the development of enterprise applications.

My current passion and endeavor is to foster the evolution of model oriented development. In particular, I am passionate about evolving the Mo+ model oriented programming language and related model oriented development tools, with as much community input as possible to achieve higher levels in the ability to create and maintain code. The open source site is at https://github.com/moplus/modelorientedplus.

Comments and Discussions

 
QuestionGeneric and Reflection Pin
Aydin Homay23-Sep-13 20:11
Aydin Homay23-Sep-13 20:11 
AnswerRe: Generic and Reflection Pin
Dave Clemmer24-Sep-13 4:56
Dave Clemmer24-Sep-13 4:56 
GeneralRe: Generic and Reflection Pin
Aydin Homay24-Sep-13 5:53
Aydin Homay24-Sep-13 5:53 
GeneralMessage Closed Pin
20-Oct-14 22:31
thanhlamvd20-Oct-14 22:31 
GeneralMy vote of 4 Pin
cjb11017-Jul-13 21:13
cjb11017-Jul-13 21:13 
GeneralRe: My vote of 4 Pin
Dave Clemmer18-Jul-13 3:57
Dave Clemmer18-Jul-13 3:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.