Step 1: Create the Code Generator Project
1.
Launch Visual Studio 2010.
2.
From the Start page click on the "New Project…" link.
3.
Select Class Library from the list of installed templates.
4.
Name the project CodeGeneratorT4.
5.
Click the OK button. Visual Studio will create your project and add the
Class1.cs file.
6.
Right click on the Class1.cs file in the Solution Explorer and delete
this file from the project.
Step 2: Create a Text Template that Creates a Class
1.
Right click on the CodeGeneratorT4 project in the Solution Explorer and
select AddàNew Item… from the pop-up
menu.
2.
Select Text Template from the list of installed templates.
3.
Name the file BusinesObject.tt and click the Add button.
4.
Open the BusinessObject.tt file. You will get a security warning
message from Visual Studio but you should just click the OK button.
5.
There are two lines of code in the file.
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ output extension=".txt" #>
These blocks of code are called Directives. A Directive
starts "<#@" and tell the text templating engine how to generate
the code. A complete list of directives can be found here.
The "debug" attribute can be set to true or
false. Setting it to true will enable you to debug this template. The "language"
attribute is set to C#. The language attribute specifies the language that is
used in the expression blocks in the template. You could set this to VB is you
prefer to code in Visual Basic.
The second line contains the output directive. The "extension"
attribute is used to specify the file extension for the file that will be
created by this template. Change this setting from ".txt" to
".cs" because we will be creating a C# class file.
6.
In this example we are going to read from a database so we need to use
objects in the System.Data and System.XML assemblies. To reference an
assembly you use an assembly directive and set the name attribute to the
assembly you want to reference. Add the following lines of code to the
template.
<#@ assembly name="System.Data" #>
<#@ assembly name="System.XML" #>
7.
Once you've referenced your assemblies you need to specify an import
directive to use the namespace defined in the assembly. This is equivalent to
the "using" statement in C#.
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
8.
The next step is to create a Control Block that executes code to open
the database and get a table schema. Code blocks start and end with <#
#>.
<#
string tableName = "Person.Address";
string className = "Address";
SqlConnection cn = new SqlConnection("Data Source=(local);User
ID=aspalliance;Password=aspalliance;Initial Catalog=AdventureWorks;");
cn.Open();
SqlCommand cmd = new SqlCommand("SELECT * FROM " + tableName, cn);
DataTable dt = cmd.ExecuteReader(CommandBehavior.CloseConnection).GetSchemaTable();
#>
The sample code here creates a variable called tableName
which is used to specify the table in the database that you want to map. In
this instance the table name is Person.Address. Since we don't want the class
name to be Person.Address I created a second variable so you can specify a
class name. If you wanted to map a different table you could easily just
change these two variables.
The next few lines use standard C# code to open a database
and load the schema for a table. One bad thing about using T4 templates is
that you don't get intellisense, so you have to make sure you spell everything
correctly and use the correct case.
If you save the file you will see any complier errors in the
Error window. Fix any errors before moving to the next step. Saving actually
runs your code through the T4 engine and will generate the file for you. You
don't use the play button (F5) like a regular project.
9.
The next step is to create Text blocks which will contain the using
statements for the file we are going to generate. Enter the following
statements in your template. Remember, these lines of code will be echoed
exactly as they are written to the output file because they are not contained
between any <# tags.
using System;
using System.Data.SqlClient;
using System.Data;
10. Now
save your file again. Whenever you save the template will try to generate the
output file. If you look in your Solution Explorer you should see a file
underneath the BusinessObjects.tt file.
If you open this file you'll see that the using statements
from your Text Block are generated in this file.
11. Now
we'll write the code to declare our class. Remember that our class is going to
be named whatever value you set the className variable to.
public class <#= className #>
The <#= syntax signifies an Expression control block. An
Expression control block will be evaluated and automatically converted into a
string by the engine. Notice that I'm using the className variable that was
defined in the previous code block. If you save now you should see the
following output.
12. Now
let's build the rest of the class. This example will generate a property for
each field in the table. Add the following code.
{
<#
foreach(DataRow dr in dt.Rows)
{
Write(" public " + dr["ColumnName"] + " { get; set; }" +
Environment.NewLine);
}
#>
}
The first open curly bracket is for the class and is a text
block. The second line starts a control block which loops around each row in
the DataTable and creates a public property for each column. Notice I'm using
the "Write()" method which you can use to control what gets written
to the output file. If you save your file you should see the following output.
using System;
using System.Data.SqlClient;
using System.Data;
public class Address
{
public AddressID { get; set; }
public AddressLine1 { get; set; }
public AddressLine2 { get; set; }
public City { get; set; }
public StateProvinceID { get; set; }
public PostalCode { get; set; }
public rowguid { get; set; }
public ModifiedDate { get; set; }
}
Notice the bug here. I didn't specify the data type for the
property. I'm going to create a Class Feature Block which is what you use to
create functions within your template. A class feature block starts with
<#+ and must be placed at the end of the file they are in. The function I
create will look at the SQL data type and convert it to the C# equivalent.
<#+
private string GetCSharpType(DataRow dr)
{
bool isNullable = Convert.ToBoolean(dr["AllowDBNull"]);
switch (dr["DataType"].ToString())
{
case "System.Int16":
if (isNullable)
return "Nullable<short>";
else
return "short";
case "System.Int32":
if (isNullable)
return "Nullable<int>";
else
return "int";
case "System.String":
return "string";
case "System.DateTime":
return "DateTime";
case "System.Byte":
if (isNullable)
return "Nullable<byte>";
else
return "byte";
case "System.Byte[]":
return "Binary";
case "System.Boolean":
return "bool";
case "System.Decimal":
return "double";
case "System.Guid":
return "Guid";
default:
throw new Exception("Type not known");
}
}
#>
13. Now
that the GetCSharpType function is in place we can call it when creating the
property. Change the line to the following.
Write(" public " + GetCSharpType(dr) + " " + dr["ColumnName"] + " { get; set; }" + Environment.NewLine);
14. Now
save your template and your class file should be created.
Your template file should look like the following image.