T4 Tutorial: Creating reusable code generation templates


This article is a part of a series that introduces code generation using C# and Text Templates (also known as T4 Templates) in Visual Studio; explains how to create reusable templates and combine them in complex code generators. In order to follow examples in this article, you need to have Visual Studio 2008 Standard Edition or higher, SQL Server 2005, T4 Toolbox and T4 Editor installed on your computer.

Introduction

In the first article of this series, we created the following code generation file to generate a DELETE stored procedure for a given database table using SMO. This stored procedure is one of the CRUD procedures that would need to be created to encapsulate access to a particular database table. Accessing data exclusively through CRUD stored procedures helps to eliminate SQL injection vulnerabilities, prevent accidental data corruption during casual browsing and may help to improve performance of SQL queries.

<#@ template language=C##>
<#@ output extension=SQL#>
<#@ assembly name=Microsoft.SqlServer.ConnectionInfo#>
<#@ assembly name=Microsoft.SqlServer.Smo#>
<#@ import namespace=Microsoft.SqlServer.Management.Smo#>
<#
    Server server = new Server();
    Database database = new Database(server, “Northwind”);
    Table table = new Table(database, “Products”);
    table.Refresh();
#>
create procedure <#= table.Name #>_Delete
<#
    PushIndent(”\t”);
    foreach (Column column in table.Columns)
    {
        if (column.InPrimaryKey)
            WriteLine(”@” + column.Name + ” ” + column.DataType.Name);
    }
    PopIndent();
#>
as
    delete from <#= table.Name #>
    where
<#
    PushIndent(”\t\t”);
    foreach (Column column in table.Columns)
    {
        if (column.InPrimaryKey)
            WriteLine(column.Name + ” = @” + column.Name);
    }
    PopIndent();
#>    

Note that this file has code generation parameters (database name and table name) embedded in its source code. In order to reuse this code generator in its current form, we would need to create a copy of this file and modify the embedded parameters to specify another database and table names. While this approach certainly works for simple code generators that need to be used a limited number of times, it creates code duplication and makes maintaining code generators more and more difficult.

We also need to extend this code generator to create additional types of stored procedures to encapsulate INSERT, UPDATE and SELECT SQL statements. We could accomplish that by extending this file to generate all of these stored procedures. Again, this approach works well for a simple code generator, but as the number of procedures this template generates grows, it becomes more and more difficult to manage.

Let’s see how we can extend this code generator without introducing code duplication and additional complexity.

Create a new code generation template

Let’s define code generation template (or simply template) as a reusable code generator, which produces a single logical unit of code. In our example, the template will generate a delete stored procedure for a single database table.

  • Create a new C# Class Library project in Visual Studio.
  • Click Project->Add New Item in the main menu and select Template item from the Code Generation folder in the dialog. If you don’t see this folder or this item, make sure you have the latest version of T4 Toolbox installed.

image

  • Enter DeleteProcedureTemplate.tt as the item name and click the Add button.
  • Double-click DeleteProcedureTemplate.tt in the Solution Explorer. You will see the following text in the editor.
<#+
// <copyright file=”DeleteProcedureTemplate.tt” company=”Your Company”>
//  Copyright © Your Company. All Rights Reserved.
// </copyright>

public class DeleteProcedureTemplate : Template
{
    protected override void RenderCore()
    {

    }
}
#>

This file contains a class feature block that defines a template class called DeleteProcedureTemplate. It inherits from a base class called Template, which is defined in T4 Toolbox. Although this file has a .tt extension, it is not intended to be used for code generation directly. Instead it will be reused by other code generation files, as we will see shortly. In the mean time, note that Visual Studio displays an error when trying to process this file.

image

  • Select DeleteProcedureTemplate.tt in the solution explorer and change Custom Tool property from TextTemplatingFileGenerator to an empty value.
image image

TextTemplatingFileGenerator is a custom tool that invokes the code generation Engine to run or transform the code generation file. Clearing the Custom Tool property of the code generation file prevents Visual Studio from trying to process the file and displaying the unnecessary error.

Encapsulate code generation logic in the template class

Template base class defines an abstract method called RenderCore. Each concrete template must override this method and implement code generation logic inside of it.

  • Copy the contents of the original file inside of the RenderCore method in DeleteProcedureTemplate.tt and modify it to look like this:
<#@ assembly name=Microsoft.SqlServer.ConnectionInfo#>
<#@ assembly name=Microsoft.SqlServer.Smo#>
<#@ import namespace=Microsoft.SqlServer.Management.Smo#>
<#+
public class DeleteProcedureTemplate : Template
{
    public string DatabaseName;
    public string TableName;

    protected override void RenderCore()
    {
        Server server = new Server();
        Database database = new Database(server, DatabaseName);
        Table table = new Table(database, TableName);
        table.Refresh();
#>
create procedure <#= table.Name #>_Delete
<#+
        PushIndent(”\t”);
        foreach (Column column in table.Columns)
        {
            if (column.InPrimaryKey)
                WriteLine(”@” + column.Name + ” ” + column.DataType.Name);
        }
        PopIndent();
#>
as
    delete from <#= table.Name #>
    where
<#+
        PushIndent(”\t\t”);
        foreach (Column column in table.Columns)
        {
            if (column.InPrimaryKey)
                WriteLine(column.Name + ” = @” + column.Name);
        }
        PopIndent();
    }
}
#>

Note that code generation parameters database name and table name are now encapsulated as public fields in the DeleteProcedureTemplate class. These fields are used inside of the RenderCore method to retrieve table metadata information and generate the stored procedure with the help of text blocks, expression blocks and class feature blocks.

Reuse the code generation template

Having the code generation logic encapsulated in a template class, we can now reuse it in another code generation file without duplicating the code.

  • Click Project->Add New Item in the main menu and select File item from the Code Generation folder in the dialog.
  • Name the new file Products_Delete.tt and change its contents to look similar to this.
<#@ template language=C#hostspecific=True#>
<#@ output extension=sql#>
<#@ include file=T4Toolbox.tt#>
<#@ include file=DeleteProcedureTemplate.tt#>
<#
    DeleteProcedureTemplate template = new DeleteProcedureTemplate();
    template.DatabaseName = “Northwind”;
    template.TableName = “Products”;
    template.Render();
#>

This code generation file uses include directive to pull in the contents of DeleteProcedureTemplate.tt. It then uses a statement block to create a new instance of the DeleteProcedureTemplate class; assigns database name and table name values and calls the Render method of the template object. Render method ultimately calls the RenderCore method with additional validation checks and exception handling.

  • Open Products_Delete.sql generated by this file.
create procedure Products_Delete
    @ProductID int
as
    delete from Products
    where
        ProductID = @ProductID

At this point we have a delete procedure for the Products table generated by the code generation file Products_Delete.tt with the help of the template defined in DeleteProcedureTemplate.tt. We can continue reusing the template by creating similar code generation files to produce delete procedures for other tables - Customers_Delete.tt, Orders_Delete.tt, etc. We can also create additional templates - InsertProcedureTemplate, UpdateProcedureTemplate, etc. to generate remaining types of CRUD stored procedures.

Conclusion

We have encapsulated logic for generating a DELETE stored procedure as a reusable template, which we can now reuse to generate multiple stored procedures without duplicating the code generation logic. However, table-specific code generation files like Products_Delete.tt and Customers_Delete.tt still contain a lot of duplication - 9 out of 10 lines required to render the template in each file are the same. Another problem with the current version is having a separate code generation file for each stored procedure. Combined with insert, update and select stored procedures, the total number of code generation files becomes difficult to manage even for a simple database like Northwind.

We will address these problems in the next article, by creating a composite code generator that produces multiple types of stored procedures for a given database tables or multiple tables in a given database.

Download

Source code, completed.


Write a Comment

Take a moment to comment and tell us what you think. Some basic HTML is allowed for formatting.

Reader Comments

Great how to, Thanks for the post… Looking forward to future posts…

Hi. Thanks for your tutorials. I have a caching question. Is there a way to cache the generated template assembly in order to avoid multiple compilation when you want to render the same template with different parameters?

Thanks, Andreas. Generated template assembly is cached automatically when Visual Studio is hosting the code generation engine. The assembly will be cached for up 25 times. More details here. This is not the case for the command line code generation host - TextTransform.exe.

[…] Sych on T4 Tutorial: Creating reusable code generation templates: more grist for the […]

I made Templates - ClassTemplate and PropertyTemplate. If I want to render a property template in many times in the class template shouldn’t I be creating a object of property template in inside the classtemplate’s RenderCore() and call property templates Render() ? It seems to not work. Nothing from the property template is being rendered in my class files. Where could I be going wrong ? Can we not use one template inside another ?

Prajeesh, Template.Render method was intended to be called from Generator.RunCore. Try putting the logic for rendering individual properties in a separate method of your ClassTemplate class instead and calling it from ClassTemplate.RenderCore.

A good tool and a great tutorial. I’m wondering wheter VS2008 supports automatic detection of template dependencies when regenerating code. (For Example: “file.tt” refers to “template.tt” and produces “file.g.cs”. How can I have “file.g.cs” regenerated upon a change of “template.tt”?)

I agree. Unfortunately, Visual Studio doesn’t detect when a dependency, such as included file or referenced assembly changes. You have to re-run the code generation manually. The only shortcut is to click the “Transform All Templates” button in the Solution Explorer, which will re-run all T4 code generators in the solution.

When I copy the DeleteProcedure Template code into my template I get:

Error 1 Cannot access a non-static member of outer type ‘Ae9310d1e44874327b8e5dd2ae3c645b5.GeneratedTextTransformation’ via nested type ‘Ae9310d1e44874327b8e5dd2ae3c645b5.GeneratedTextTransformation.DeleteProcedureTemplate’ C:\rick\dev\hydrogen\src\Hydrogen.Common.Entity\Edmx\Generators\DeleteProcedureTemplate.tt 19 9 Hydrogen.Common.Entity

I also get that error on the download above. This occurs for every Push* Pop* and Write* method. Perhaps I am missing something? Where is the class Template defined from which DeleteProcedureTemplate inherits?

Rick, Template class is defined in T4Toolbox\Template.tt, which is installed under Program Files directory by the T4Toolbox installer.