Tuesday, March 29, 2011

Generate DataSet .XDS

Last week on my Brochure, I wrote about getting the schema of a Stored Procedure or other T-SQL command. Now I am going to bring this a little farther and generate am .XDS file that can be used to generate a Typed DataSet. This T4 Template include file takes a List and a DataSet name and generates the .XSD.

<#@ assembly name="TsqlDesriptionUtil.dll" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ import namespace="TsqlDesriptionUtil" #>
<#@ import namespace="System.Collections.Generic" #>
<#+
    public class DataSetFromTSqlCmd: TextTransformation
    {
        public string dsName;
        public List<TableDescription> tables;

        public override string TransformText()
        {
#><?xml version="1.0" encoding="utf-8"?>
<xs:schema id="<#= dsName #>" 
  targetNamespace="http://jrcs3.blogspot.com/<#= dsName #>.xsd"
  xmlns:mstns="http://jrcs3.blogspot.com/<#= dsName #>.xsd" 
  xmlns="http://jrcs3.blogspot.com/<#= dsName #>.xsd" 
  xmlns:xs="http://www.w3.org/2001/XMLSchema" 
  xmlns:msdata="urn:schemas-microsoft-com:xml-msdata"
  xmlns:msprop="urn:schemas-microsoft-com:xml-msprop" 
  attributeFormDefault="qualified" 
  elementFormDefault="qualified">
  <xs:annotation>
    <xs:appinfo source="urn:schemas-microsoft-com:xml-msdatasource">
      <DataSource DefaultConnectionIndex="0" 
          FunctionsComponentName="QueriesTableAdapter"
          Modifier="AutoLayout, AnsiClass, Class, Public" 
          SchemaSerializationMode="IncludeSchema" 
          xmlns="urn:schemas-microsoft-com:xml-msdatasource">
        <Connections />
        <Tables />
        <Sources />
      </DataSource>
    </xs:appinfo>
  </xs:annotation>
 <xs:element name="<#= dsName #>" 
        msdata:IsDataSet="true" 
        msdata:UseCurrentLocale="true" 
        msprop:Generator_UserDSName="<#= dsName #>" 
        msprop:Generator_DataSetName="<#= dsName #>" 
        msprop:EnableTableAdapterManager="true">
    <xs:complexType>
      <xs:choice minOccurs="0" maxOccurs="unbounded">
<#+ foreach(TableDescription table in tables)
    { #>
        <xs:element 
            name="<#= table.TableName #>"  
            msprop:Generator_UserTableName="<#= table.TableName #>" 
            msprop:Generator_RowDeletedName="<#= table.TableName #>RowDeleted" 
            msprop:Generator_RowChangedName="<#= table.TableName #>RowChanged" 
            msprop:Generator_RowClassName="<#= table.TableName #>Row" 
            msprop:Generator_RowChangingName="<#= table.TableName #>RowChanging" 
            msprop:Generator_RowEvArgName="<#= table.TableName #>RowChangeEvent" 
            msprop:Generator_RowEvHandlerName="<#= table.TableName 
               #>RowChangeEventHandler" 
            msprop:Generator_TableClassName="<#= table.TableName #>DataTable" 
            msprop:Generator_TableVarName="table<#= table.TableName #>" 
            msprop:Generator_RowDeletingName="<#= table.TableName #>RowDeleting" 
            msprop:Generator_TablePropName="<#= table.TableName #>">
          <xs:complexType>
            <xs:sequence>
<#+ foreach(FieldDescription field in table.Fields)
 { #>
                <xs:element 
                    name="<#= field.FieldName #>" 
                    msprop:Generator_UserColumnName="<#= field.FieldName #>"
                    msprop:Generator_ColumnVarNameInTable="column<#= field.FieldName 
                      #>" 
                    msprop:Generator_ColumnPropNameInRow="<#= field.FieldName #>" 
                    msprop:Generator_ColumnPropNameInTable="<#= field.FieldName 
                      #>Column" 
                    type="xs:<#= field.XsdTypeName #>" 
                    minOccurs="0" />
<#+ } #>
            </xs:sequence>
          </xs:complexType>
        </xs:element>
<#+ } #>
      </xs:choice>
    </xs:complexType>
  </xs:element>
</xs:schema>
<#+ 
  return this.GenerationEnvironment.ToString();
 }
} #>

For Demonstration purposes I wrote a simple T4 Template that includes the template above and uses the library I wrote in this post to get a description of the data.

<#@ template language="C#v3.5" debug="true" 
#><#@ output extension="xsd" 
#><#@ include file="DataSetFromTSqlCmd.tt" 
#><#@ assembly name="TsqlDesriptionUtil.dll" 
#><#@ import namespace="TsqlDesriptionUtil" 
#><#
    // Since I'm generating XML, I will go to extreme measures to avoid new line 
    // characters like the strange formatting of declarations above ^

    // Get the table descriptions for given T-SQL
    string conString = @"Data Source=localhost;" +
      @"Initial Catalog=Northwind;Integrated Security=True";
    string commandString = "EXEC  CustOrderHist @CustomerID='ALFKI'";
    List<TableDescription> tables = 
      TsqlCommandSchema.GetFieldDescription(conString, commandString);

    // Generate XSD:
    var gen = new DataSetFromTSqlCmd();
    gen.dsName = "MyNewDS";
    gen.tables = tables;
    Write(gen.TransformText()); 
#>

Since TsqlCommandSchema.GetFieldDescription supports multiple results sets and Stored Procedure that use temporary tables.

At work I will probably integrate this template into an existing code generation tool using my TTCommucator tool.

Tuesday, March 22, 2011

Getting the Schema of a Stored Procedure Results Set

Visual Studio is pretty good at discovering the schema of a stored procedure if the procedure doesn't use temporary tables and returns only one result set. You can drag the stored procedure on to a DataSet or Linq to Sql Design Surface and it is generated for you. In real life, stored procedures don’t fit into that neat box.

Since I am lazy and don’t like to build DataSets by hand, I wrote this little class. Note that I am actually executing the T-SQL command. Any side effect of the stored procedure will take place; this is acceptable in my environment, it may not be in yours.

I am getting less information from this method than I get from static sources like SQL Server’s SMO; for example, I am not getting the length of strings. I am getting enough information to do what I want to do.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;

namespace TsqlDesriptionUtil
{
    public class TsqlCommandSchema
    {
        public static List<TableDescription> GetFieldDescription(
            string conString, string commandString)
        {
            var rVal = new List<TableDescription>();
            int tableNumber = 1;
            using (var con = new SqlConnection(conString))
            {
                using (var com = new SqlCommand(commandString, con))
                {
                    con.Open();
                    // WARNING: I am executing the command, beware of side effects!
                    SqlDataReader reader = 
                        com.ExecuteReader(CommandBehavior.CloseConnection);
                    // Table Loop
                    do
                    {
                        var fieldDescriptionList = new List<FieldDescription>();
                        // Field Loop
                        for (int i = 0; i < reader.FieldCount; ++i)
                        {
                            Type fieldType = reader.GetProviderSpecificFieldType(i);
                            fieldDescriptionList.Add(
                                new FieldDescription(reader.GetName(i), 
                                reader.GetDataTypeName(i), reader.GetFieldType(i)));
                        }
                        // T-SQL doesn't give table names, so I will make up my own
                        rVal.Add(new TableDescription(string.Format(
                            "Table{0}", tableNumber++), fieldDescriptionList));
                    } while (reader.NextResult());
                    con.Close();
                }
            }
            return rVal;
        }
    }
    public class TableDescription
    {
        public TableDescription(string tableName, List<FieldDescription> fields)
        {
            this.TableName = tableName;
            this.Fields = fields;
        }
        public string TableName { get; set; }
        public List<FieldDescription> Fields { get; set; }
        public int Count
        {
            get
            {
                if (this.Fields != null)
                    return this.Fields.Count;
                else
                    throw new Exception("Fields not loaded");
            }
        }
    }
    public class FieldDescription
    {
        public FieldDescription(string fieldName, string dataTypeName, Type fieldType)
        {
            this.FieldName = fieldName;
            this.DataTypeName = dataTypeName;
            this.FieldType = fieldType;
        }
        public string FieldName { get; set; }
        public string DataTypeName { get; set; }
        public Type FieldType { get; set; }
        public string XsdTypeName
        {
            get
            {
                Type t = this.FieldType;
                return getXsdTypeNameForType(t);
            }
        }

        private static string getXsdTypeNameForType(Type t)
        {
            string rVal = string.Empty;
            //.NET Framework type XML Schema (XSD) type
            switch (t.FullName)
            {
                case "System.Boolean": rVal = "Boolean"; break;
                case "System.Byte": rVal = "unsignedByte"; break;
                case "System.Byte[]": rVal = "base64Binary"; break;
                case "System.DateTime": rVal = "dateTime"; break;
                case "System.Decimal": rVal = "decimal"; break;
                case "System.Double": rVal = "Double"; break;
                case "System.Int16": rVal = "short"; break;
                case "System.Int32": rVal = "int"; break;
                case "System.Int64": rVal = "long"; break;
                case "System.SByte": rVal = "Byte"; break;
                case "System.String": rVal = "string"; break;
                case "System.String[]": rVal = "ENTITIES"; break;
                case "System.TimeSpan": rVal = "duration"; break;
                case "System.UInt16": rVal = "unsignedShort"; break;
                case "System.UInt32": rVal = "unsignedInt"; break;
                case "System.UInt64": rVal = "unsignedLong"; break;
                case "System.Uri": rVal = "anyURI"; break;
                case "System.Xml.XmlQualifiedName": rVal = "QName"; break;
            }
            return rVal;
        }
    }
}

I compiled the above C# source file into an assembly. Here is a xUnit test that demonstrates its use.

[Fact(DisplayName = "Run SP 001")]
public void RunSp001()
{
    string conString = @"Data Source=localhost;Initial Catalog=My_Db;" + 
        "Integrated Security=True";
    string commandString = "EXEC  GetBigTableDataUseTempTab 101";
    List<TableDescription> rVal = 
        TsqlCommandSchema.GetFieldDescription(conString, commandString);

    foreach (var item in rVal) 
    {
        Console.WriteLine(string.Format("{0}: {1}", item.TableName, item.Count));
        foreach(var field in item.Fields)
            Console.WriteLine(
                string.Format("\t{0}\t{1}\t{2}\t{3}", 
                    field.FieldName, 
                    field.DataTypeName, 
                    field.FieldType, 
                    field.XsdTypeName));
    }
}

Monday, March 21, 2011

GetXsdTypeNameForType() function

For a project I'm working on, I need to convert .NET Framework types into XML Schema (XSD) types. I wrote a little function to do the translation:

public static string GetXsdTypeNameForType(Type t)
{
    string rVal = string.Empty;
    switch (t.FullName)
    {
        case "System.Boolean": rVal = "boolean"; break;
        case "System.Byte": rVal = "unsignedByte"; break;
        case "System.Byte[]": rVal = "base64Binary"; break;
        case "System.DateTime": rVal = "dateTime"; break;
        case "System.Decimal": rVal = "decimal"; break;
        case "System.Double": rVal = "double"; break;
        case "System.Int16": rVal = "short"; break;
        case "System.Int32": rVal = "int"; break;
        case "System.Int64": rVal = "long"; break;
        case "System.SByte": rVal = "byte"; break;
        case "System.String": rVal = "string"; break;
        case "System.String[]": rVal = "ENTITIES"; break;
        case "System.TimeSpan": rVal = "duration"; break;
        case "System.UInt16": rVal = "unsignedShort"; break;
        case "System.UInt32": rVal = "unsignedInt"; break;
        case "System.UInt64": rVal = "unsignedLong"; break;
        case "System.Uri": rVal = "anyURI"; break;
        case "System.Xml.XmlQualifiedName": rVal = "QName"; break;
    }
    return rVal;
}

NOTE: There are places where I had to make a decision as to which XSD type would map to System.TimeDate, System.String, System.Byte[], etc. And there's the question of which XSD type I will map to my custom Employee, Appointment or Cow types!

Monday, March 07, 2011

Boise Code Camp 2011 Presentation Notes

Sorry I’m late getting this up. I will give usual excuses of being busy.

Since I developed this talk on this blog, this entry will essentially be a link list. I put a copy of the slides in Google doc and you can reference it here

Template Parts

I presented a slide that showed the basic template parts in a small mock template:

<#@ template language="C#" #>
<#@ directive property=“value” #>
<# var item = "Statement Block"; #>
Text Block 
<#= "Expression Block" #>
<#+ 
    string ClassFeatureBlock(string thingy)
    {
        return thingy;
    } 
#>

Hello World Monty

I showed some of the elementary features of T4 templates by refactoring a basic template that would create a text file that contained the infamous words “Hello World”. A script of that exercise is here

Basic Code Generation Demonstration

To demonstrate Code Generation I set out to generate a simple T-SQL Select Statement using Sql Management Objects (SMO).

The Target Code looked something like this:

Include (SelectSpInclude.tt)
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#@ assembly name="Microsoft.SqlServer.ConnectionInfo" #>
<#@ assembly name="Microsoft.SqlServer.Smo" #>
<#@ import namespace="Microsoft.SqlServer.Management.Smo" #>
<#+
    public class SelectSpInclude: TextTransformation
    {
        public string ServerName;
        public string DbName;
        public string TableName;

        public override string TransformText()
        {
 
        Table table = getTable(ServerName, DbName, TableName);
#>
SELECT <#+ bool forgetComma = true;
 foreach (Column col in table.Columns)
{ #><#+if (!forgetComma) Write("      ,");#>[<#= col.Name #>]
<#+ forgetComma = false;
} #>
FROM <#= table.Name #><#+

        return this.GenerationEnvironment.ToString();
    } 
    Table getTable(string serverName, string dbName, string tableName)
    {
        Server server = new Server(serverName);
        Database database = new Database(server, dbName);
        Table table = new Table(database, tableName);
        table.Refresh();
        return table; 
    }
}
#>
Template (EmployeesSp.tt)
<#@ template language="C#v3.5" #>
<#@ output extension="sql" #>
<#@ include file="SelectSpInclude.tt" #>
<#
    SelectSpInclude gen = new SelectSpInclude();
    gen.ServerName = @"localhost\SQLEXPRESS";
    gen.DbName = "Northwind";
    gen.TableName = "Employees"; Write(gen.TransformText());
#>

Result (EmployeesSp.sql)
SELECT [EmployeeID]
      ,[LastName]
      ,[FirstName]
      ,[Title]
      ,[TitleOfCourtesy]
      ,[BirthDate]
      ,[HireDate]
      ,[Address]
      ,[City]
      ,[Region]
      ,[PostalCode]
      ,[Country]
      ,[HomePhone]
      ,[Extension]
      ,[Photo]
      ,[Notes]
      ,[ReportsTo]
      ,[PhotoPath]
FROM Employees

Somewhere in this part of the presentation, someone asked if it is possible to generate a series of files from a single template, a separate code file for each table in a database. Oleg Sych has a blog post that does just that here

Generating a Nullable ADO.NET DataSet Wrapper

This is when I went to Show and Tell Mode. I demonstrate a Nullable DataSet Wrapper that I wrote in these blog entries. I wrote the templates in VB.NET to generate code in C#.

Part 1, Part 2, Part 3 & Part 4

Sunday, March 06, 2011

T4 Template Tour of First Gear

At the Boise Code Camp, I lead off with a demonstration of some basic features of T4 Templates. The point of the presentation is to get you from no T4 to the level of most blog examples. I got good reviews from the few who attended my session, so I thought I would attempt to write it down. Here goes…

The Script

First, create a Text file and change the extension to "tt". We’ll call it "Hello.tt". I will add a template directive and tell T4 that I want to use C#. I add the text "Hello World" below the directive. When I save it, "Hello.cs" appears below the template.

Template (Hello.tt):
<#@ template language="C#" #>
Hello World
Output (Hello.cs):
Hello World

Also notice that Visual Studio complains that Hello.cs won’t compile. For this demonstration, I don’t want to create a C# code file, so I will tell the template to create a "txt" file with an output directive.

Template (Hello.tt):
<#@ template language="C#" #>
<#@ output extension="txt" #>
Hello World
Output (Hello.txt):
Hello World

Visual Studio is happy because it doesn’t try to compile "txt" files. (Since it doesn't change, I won't repeat the output again.)

Now, let’s add some actual C# template code, I’m going to create a variable called "name" in a Statement Block and reference it in an Expression Block.

Template (Hello.tt):
<#@ template language="C#" #>
<#@ output extension="txt" #>
<# string name = "World"; #>
Hello <#= name #>

Now I’m going to replace the variable with a function. The function will need to be in a Class Feature Block. Just to speed things up, I’m going to use a Write call in a Statement Block to render the name. You can write text to the output using either Expression Blocks OR calls to Write().

Template (Hello.tt):
<#@ template language="C#" #>
<#@ output extension="txt" #>
Hello <# Write(GetName()); #>
<#+ 
    string getName()
    {
        return "Word";
    }
#>

Now I want to move my function into an include file. I will create a new text file and call it "HelloFunctions.tt" I move the Class Feature Block into that file. Notice that there is no template directive. Also, you must save the include file BEFORE you save the template file.

Include (HelloFunctions.tt)
<#+ 
    string getName()
    {
        return "Word";
    }
#>
Template (Hello.tt):
<#@ template language="C#" #>
<#@ include file="HelloFunctions.tt" #>
<#@ output extension="txt" #>
Hello <# Write(GetName()); #>

Now, for a brief look under the hood, I can show you the source code for the Text Transformation. Here I add "debug="true"" to the template file, save it and look in the temp directory (you can get your temp directory by typing "set temp" in a command prompt)

Template (Hello.tt):
<#@ template language="C#" #>
<#@ include file="HelloFunctions.tt" #>
<#@ output extension="txt" #>
Hello <# 
// This comment will show up in .cs file
Write(GetName()); 
#>
Generated C# File (hcs3svyj.0.cs in this case)
namespace Microsoft.VisualStudio.TextTemplatingBFD11436D0A4595408B06EA4E1B2A636 {
    using System;
    using Microsoft.VisualStudio.TextTemplating.VSHost;

    
    #line 1 "c:\vsprojects\SimpleT4\Hello.tt"
    public class GeneratedTextTransformation : 
        Microsoft.VisualStudio.TextTemplating.TextTransformation {
        public override string TransformText() {
            try {
                this.Write("Hello ");

                #line 4 "c:\vsprojects\SimpleT4\Hello.tt"

// This comment will show up in .cs file
Write(getName()); 

          
                #line default
                #line hidden
            }
            catch (System.Exception e) {
                System.CodeDom.Compiler.CompilerError error = new 
                    System.CodeDom.Compiler.CompilerError();
                error.ErrorText = e.ToString();
                error.FileName = "c:\\vsprojects\\SimpleT4\\Hello.tt";
                this.Errors.Add(error);
            }
            return this.GenerationEnvironment.ToString();
        }

        #line 1 "c:\vsprojects\SimpleT4\HelloFunctions.tt"

 string getName()
 {
  return "World";
 }

  
        #line default
        #line hidden
    }

    #line default
    #line hidden
}

These source files have helped me understand what is going on behind the scenes. For example, I didn’t really get the difference between Statement Blocks (<# #>) and Class Feature Blocks (<#+ #>) until I saw the generated.

Afterthought

This isn’t a transcription! Written and spoken communication is different by nature.

In the presentation I would change the value of "World" with different words ("Earth", "Boise", "Boise Code Camp", etc); In a presentation, this added proofiness that the templates were run and some lame humor to lighten the mood of the talk. In the written form, it would force me to repeat the Output after each sample (and make this longer).

Tuesday, February 22, 2011

Boise Code Camp: Code Generation with T4 Templates

I will be presenting at the Boise Code Camp on Saturday, February 26, 2011, 9:30 AM in the Farnsworth room in the BSU SUB!

T4 Templates (Text Template Transformation Toolkit) is the code generator built into Visual Studio. This session is a basic introduction to code generation in C# and .NET using T4 Templates in Visual Studio 2008. I will takes you on a journey starting from the basic "Hello World" template into a world where you can wrote reusable templates and interrogate SQL Server or an ADO.NET DataSet for table descriptions.

Check it out!

Wednesday, February 16, 2011

Four Styles of T4 Template Code

As I work through T4 templates as I get ready for Boise Code Camp, I’ve noticed a few styles of template code. Just for kicks, I am going to name them and give an example of each using a code generation that I blogged about early last year. Each template uses an include file that I present after the four samples. Each template relies on its style to the point of absurdity; I didn’t go as far as to eliminate all text blocks.

Real life templates are mixtures of these styles applied at appropriate places.

I adapted the code generation that I did here

Embed

This can also be called the Classic ASP style. All generated code is added as text blocks or expression blocks (<#= #>). The logic is contained in code blocks mixed in with the text and expression blocks. For simple logic, this style is probably OK. It can be difficult to read and maintain as the logic gets more complex. This is the most concrete approach.

<#@ template language="C#v3.5" debug="true" #>
<#@ output extension="sql" #>
<#@ include file="TableDescriptionUtilCS.tt" #>
<#
    string ConnectionString = @"Data Source=localhost\SQLEXPRESS;" + 
        "Initial Catalog=Northwind;Integrated Security=True";
    string Table = "Employees"; 

    TableDescriptionUtil tdu = new TableDescriptionUtil();
    List<TableDescription> Columns = tdu.GetTableDescription(ConnectionString, Table); 

    int counter;
#>
CREATE PROCEDURE [dbo].[Insert<#=Table #>]
<#
    counter = 1;
    foreach (TableDescription item in Columns)
    {
        #>    @<#= item.ColName #> <#= item.FullTypeName #><#
        if (item.Status == 0x80)
            #> OUT<#
        if (item.ColIsNullable != 0) 
            #> = NULL<#
        if (counter < Columns.Count)
            #>,<#
        counter++;
        #><#= "\r\n"#><#
    }
#>

AS

INSERT INTO [<#=Table #>] (<#
    counter = 1;
    foreach (TableDescription item in Columns)
    {
        if (item.Status != 0x80)
        {
            if (counter > 1)
                #>, <#
            if (counter % 5 == 0)
                #><#= "\r\n    " #><#
            counter++;
            #>[<#= item.ColName #>]<#
        }
    }
#>)
VALUES (<#
    counter = 1;
    foreach (TableDescription item in Columns)
    {
        if (item.Status != 0x80)
        {
            if (counter > 1)
                #>, <#
            if (counter % 5 == 0)
                #><#= "\r\n    " #><#
            counter++;
            #>@<#= item.ColName #><#
        }
    }
#>)

SET @<#
    foreach (TableDescription item in Columns)
    {
        if (item.Status == 0x80)
        {
            #><#=item.ColName #><#
            break;
        }
    }
#> = @@IDENTITY

Inline Write

This style uses code blocks that contain Write(), WriteLine(), PushIndent(), PopIndent(), and other statements.

This approach is more abstract that Embed, it is easier for me to read than Embed or String Function.

<#@ template language="C#v3.5" debug="true" #>
<#@ output extension="sql" #>
<#@ include file="TableDescriptionUtilCS.tt" #>
<# 
    string ConnectionString = @"Data Source=localhost\SQLEXPRESS;" +
        Initial Catalog=Northwind;Integrated Security=True";
    string Table = "Employees";

    TableDescriptionUtil tdu = new TableDescriptionUtil();
    List<TableDescription> Columns = tdu.GetTableDescription(ConnectionString, Table);

    int counter;
#>
CREATE PROCEDURE [dbo].[Insert<# Write(Table); #>]
<#
    PushIndent("    ");
    counter = 1;
    foreach (TableDescription item in Columns)
    {
        Write("@" + item.ColName + " " + item.FullTypeName);
        if (item.Status == 0x80)
            Write(" OUT");
        if (item.ColIsNullable != 0)
            Write(" = NULL");
        if (counter < Columns.Count)
            WriteLine(",");
        counter++;
    }
    PopIndent();
#>


AS

INSERT INTO [<# Write(Table); #>] (<#
    PushIndent("    ");
    counter = 1;
    foreach (TableDescription item in Columns)
    {
        if (item.Status != 0x80)
        {
            if (counter > 1)
                Write(", ");
            if (counter % 5 == 0)
                WriteLine(String.Empty);
            counter++;
            Write(string.Format("[{0}]", item.ColName));
        }
    }
    PopIndent();
#>)
VALUES (<#
    PushIndent("    ");
    counter = 1;
    foreach (TableDescription item in Columns)
    {
        if (item.Status != 0x80)
        {
            if (counter > 1)
                Write(", ");
            if (counter % 5 == 0)
                WriteLine(string.Empty);
            counter++;
            Write(String.Format("@{0}", item.ColName));
        }
    }
    PopIndent();
#>)

SET @<#
    foreach (TableDescription item in Columns)
    {
        if (item.Status == 0x80)
        {
            Write(item.ColName);
            break;
        }
    }
#> = @@IDENTITY

String Function

Here I call a function in an expression block that returns a bit of generated code as a string. The functions are in feature blocks (or include file, linked assemblies, etc.).

Since I did some primitive string based code gen for a number of years, I find myself relying on this approach. This approach can lead to code just as ugly as the Embed approach. This approach is good for adapting old fashioned code gen to T4.

<#@ template language="C#v3.5" debug="true" #>
<#@ output extension="sql" #>
<#@ include file="TableDescriptionUtilCS.tt" #>
<# 
    string ConnectionString = @"Data Source=localhost\SQLEXPRESS;" + 
        "Initial Catalog=Northwind;Integrated Security=True";
    string Table = "Employees";

    TableDescriptionUtil tdu = new TableDescriptionUtil();
     List<TableDescription> Columns = tdu.GetTableDescription(ConnectionString, Table);

     int counter;
#>
CREATE PROCEDURE [dbo].[Insert<#= getTableName(Table) #>]
<#= getParameterList(Columns) #>

AS

INSERT INTO [<#= getTableName(Table) #>] (<#= getFieldNames(Columns) #>)
VALUES (<#= getValueNames(Columns) #>)


SET @<#=getIdentityFieldName(Columns) #> = @@IDENTITY
<#+
    string getTableName(string table)
    {
        // Yes, this is hard core overkill
        return table;
    }
    string getParameterList(List<TableDescription> columns)
    {
        string rVal = string.Empty;
        int counter = 1;
        foreach (TableDescription item in columns)
        {
           rVal += "    @" + item.ColName + " " + item.FullTypeName;
           if (item.Status == 0x80)
               rVal += " OUT";
           if (item.ColIsNullable != 0)
               rVal += " = NULL";
           if (counter < columns.Count)
               rVal += ",\r\n";
           counter++;
       }
       return rVal;
    }
    string getFieldNames(List<TableDescription> columns)
    {
        string rVal = string.Empty;
        int counter = 1;
        foreach (TableDescription item in columns)
        {
            if (item.Status != 0x80)
            {
                if (counter > 1)
                    rVal += ", ";
                if (counter % 5 == 0)
                    rVal += "\r\n    ";
                counter++;
                rVal += string.Format("[{0}]", item.ColName);
           }
        }
        return rVal;
    }
    string getValueNames(List<TableDescription> columns)
    {
        string rVal = string.Empty;
        int counter = 1;
        foreach (TableDescription item in columns)
        {
        if (item.Status != 0x80)
        {
            if (counter > 1)
                rVal += ", ";
            if (counter % 5 == 0)
                rVal += "\r\n    ";
            counter++;
            rVal += string.Format("@{0}", item.ColName);
        }
    }
    return rVal;
    }
    string getIdentityFieldName(List<TableDescription> columns)
    {
        string rVal = string.Empty;
        foreach (TableDescription item in columns)
        {
            if (item.Status == 0x80)
            {
                rVal =item.ColName;
                break;
            }
        }
        return rVal;
    }
#>

Write Function

Here I call a function to generate code a block of code with Write functions.

Unlike the String Function approach, the code generation is relying on a side effect of the function, so this approach isn’t functional.

<#@ template language="C#v3.5" debug="true" #>
<#@ output extension="sql" #>
<#@ include file="TableDescriptionUtilCS.tt" #>
<# 
    string ConnectionString = @"Data Source=localhost\SQLEXPRESS;" + 
        "Initial Catalog=Northwind;Integrated Security=True";
    string Table = "Employees";

    TableDescriptionUtil tdu = new TableDescriptionUtil();
    List<TableDescription> Columns = tdu.GetTableDescription(ConnectionString, Table);

    int counter;
#>
CREATE PROCEDURE [dbo].[Insert<# writeTableName(Table); #>]
<# writeParameterList(Columns); #>

AS

INSERT INTO [<# writeTableName(Table); #>] (<# writeFieldNames(Columns); #>)
VALUES (<# writeValueNames(Columns); #>)


SET @<# writeIdentityFieldName(Columns); #>  = @@IDENTITY
<#+
    void writeTableName(string table)
    {
        Write(table);
    }
    void  writeParameterList(List<TableDescription> columns)
    {
        PushIndent("    ");
        int counter = 1;
        foreach (TableDescription item in columns)
        {
            Write("@" + item.ColName + " " + item.FullTypeName);
            if (item.Status == 0x80)
                Write(" OUT");
            if (item.ColIsNullable != 0)
                Write(" = NULL");
            if (counter < columns.Count)
                WriteLine(",");
            counter++;
        }
        PopIndent();
    }
    void writeFieldNames(List<TableDescription> columns)
    {
         PushIndent("    ");
         int counter = 1;
         foreach (TableDescription item in columns)
         {
             if (item.Status != 0x80)
             {
                 if (counter > 1)
                     Write(", ");
                 if (counter % 5 == 0)
                     WriteLine(String.Empty);
                 counter++;
                 Write(string.Format("[{0}]", item.ColName));
             }
         }
         PopIndent();
    }
    void writeValueNames(List<TableDescription> columns)
    {
        PushIndent("    ");
        int counter = 1;
        foreach (TableDescription item in columns)
        {
            if (item.Status != 0x80)
            {
                if (counter > 1)
                    Write(", ");
                if (counter % 5 == 0)
                    WriteLine(string.Empty);
                counter++;
                Write(String.Format("@{0}", item.ColName));
            }
        }
        PopIndent();
    }
    void writeIdentityFieldName(List<TableDescription> columns)
    {
        foreach (TableDescription item in columns)
        {
            if (item.Status == 0x80)
            {
                Write(item.ColName);
                break;
            }
        }
    }
#>

Include File: TableDescriptionUtil.tt

I included this file in all the examples above.

<#@ assembly name="System.Data.dll" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="Microsoft.VisualStudio.TextTemplating" #>
<#+
public class TableDescriptionUtil : TextTransformation
{
    /// <remarks>
    /// I don't actually call this, but it is necessary for T4 Templates to work
    /// </remarks>
    public override string TransformText()
    {
        return "";
    }
    /// <summary>
    /// Gets a DataReader containing selected column information for all of the
    /// columns in the indecated table from SQL Server System Tables
    /// </summary>
    /// <param name="tableName">Get information on THIS table</param>
    /// <param name="conn">An existing open connection to be used </param>
    /// <returns>A DataReader pointing to the results</returns>
    private SqlDataReader GetTableDefReader(string tableName , SqlConnection conn )
    {
        SqlDataReader reader;// = new SqlDataReader();
        string getDataForTable = 
             " SELECT c.name AS col_name, " +
             "   t.name AS type_name, " +
             "   CAST(c.length AS INT) AS length, " +
             "   c.isnullable, " +
             "   CAST(c.status AS INT) AS status " +
             " FROM " +
             "  sysObjects o " +
             "  JOIN sysColumns c ON c.id = o.id " +
             "  JOIN systypes t on c.xtype = t.xtype AND t.status = 0 " +
             " WHERE o.name = @tableName " +
             " ORDER BY c.colid ";
        using (var cmd = new SqlCommand(getDataForTable, conn))
        {
            cmd.Parameters.AddWithValue("@tableName", tableName);
            reader = cmd.ExecuteReader();
        }
        return reader;
    }

    /// <summary>
    /// Gets a List of column descriptions for a given table
    /// </summary>
    /// <param name="ConnectionString">Connection string to the database</param>
    /// <param name="Table">The name of the table to describe</param>
    /// <returns>A list of column descriptions</returns>
    public List<TableDescription> GetTableDescription(string ConnectionString, 
         string Table) 
    {
        SqlDataReader reader;
        List<TableDescription> Columns = new List<TableDescription>();
        using (var conn = new SqlConnection(ConnectionString))
        {
            conn.Open();
            reader = GetTableDefReader(Table, conn);
            while (reader.Read())
            {
                TableDescription Column = new TableDescription();
                Column.ColName = reader.GetString(0);
                Column.TypeName = reader.GetString(1);
                Column.ColLength = reader.GetInt32(2);
                Column.ColIsNullable = reader.GetInt32(3);
                Column.Status = reader.GetInt32(4);
                Column.FullTypeName = GetTypeName(Column.TypeName, Column.ColLength);
                Columns.Add(Column);
            }
            conn.Close();
        }
        return Columns;
    }
    /// <summary>
    /// Gets the TypeName as it should appear in a SP Parameter List
    /// </summary>
    /// <param name="TypeName"></param>
    /// <param name="ColLength"></param>
    /// <returns>The processed Type Name as a string</returns>
    private string GetTypeName(string typeName , int ColLength )
    {
        switch (typeName.ToLower())
        {
        case "varchar":
        case "nvarchar":
            string lenString  = ColLength.ToString();
            if (ColLength == -1 )
                lenString = "MAX";
            return string.Format("{0}({1})", typeName, lenString);
        default:
            return typeName;
        }
    }
}
public class TableDescription
{
    public string ColName {get; set;}
    public string TypeName {get; set;}
    public string FullTypeName {get; set;}
    public int ColLength {get; set;}
    public int Status {get; set;}
    public int ColIsNullable {get; set;}
}
#>