Sunday, December 19, 2010

Generating a Nullable ADO.NET DataSet Wrapper

Part 4: The Template and putting it all together

So, I have my target code, my DataSet describer and some T4 template tools (including the ITextTemplatingEngineHost class I took from MSDN), but I haven’t generated any DataSet Wrapper code! It is Christmas time, but that’s still a lame excuse.

Running the Template

In Part 3, I created a class to pass parameters to a T4 Template and an overly simple template that echoed the parameters to the generated document. Here I’m using the same basic from the Test Console Application, but I am referencing a template that will generate the DataSet Wrapper and passing the appropriate parameters to generate the wrapper I want. This code use a DataSet in an assembly.
// Set up some parameters:
var parms = new TTCommucator.GetParams("setting.txt");
parms.CurrentParameters.Add("DsSource", "Assembly");
parms.CurrentParameters.Add("AssembyPath", @"C:\GeneratorProject\MyAssembly.dll");
parms.CurrentParameters.Add("DataSetName", "MyDS");
parms.CurrentParameters.Add("FileNameSpace", "MyAssembly.DataSets");
parms.CurrentParameters.Add("DsNameSpace", "NewProject.DataSetWrappers");
parms.Save();

// Set the Template File
string templateFileName = "DataSetX.tt";

// Set up host and engine
CustomCmdLineHost host = new CustomCmdLineHost();
Engine engine = new Engine();
host.TemplateFileValue = templateFileName;

//Read the text template.
string input = File.ReadAllText(templateFileName);

//Transform the text template.
string result = engine.ProcessTemplate(input, host);
// Save logic would be here…

Notes on the Template

In the template, I am using TTCommucator.GetParams to read the parameter I’ve written in code above; same as in Part 3. I use the DataSetDesciber.ReflectDataSet.DataSetDef from Part 2 to get the tables, columns, types, etc. from the target DataSet. The generated code will be different from the target code in Part 1. For example:
  • I’m using .NET type names (like System.Int32) instead of C# type names (like int). That’s what Type.ToString() returns and I see no reason to convert it just to look pretty.
  • I’m not using Nullable’s ? operator. No good reason, it just seems more consistent with the .NET types in code (I don’t see System.Int32? in code all that often).
The purpose of the target code is to provide a starting point for generation. The code will change as a result of what you learn in writing the generation and testing. In the Template I am generating C# code, but the template code is in VB.NET; it is easier for me to keep straight which is which.

The Template

<#@ template debug="true" hostspecific="true" language="VB" #>
<#@ output extension=".cs" #>
<#@ assembly name="C:\GeneratorProject\TTCommucator.dll" #>
<#@ assembly name="C:\GeneratorProject\DataSetDesciber.dll" #>
<#@ import namespace="DataSetDesciber.ReflectDataSet" #>
<#@ import namespace="TTCommucator" #>
<# 
    ' Load the Parameter object
    Dim parms As new GetParams(Host.ResolvePath("setting.txt"))
    parms.Load()

    ' Get the namespace
    Dim FileNameSpace As String = parms.CurrentParameters.Item("FileNameSpace")
    Dim DsNameSpace As String = parms.CurrentParameters.Item("DsNameSpace")

    ' a description of the DataSet to be wrapped
    Dim refx As DataSetDef = GetDsDescripter(parms)
#>
// Generated with DataSetWrapper Version 1
using System;
using System.Collections;
using System.Collections.Generic;
using <#=DsNameSpace #>;

namespace <#=FileNameSpace #>
{
    public class <#=refx.DataSetName #>X
    {
        private <#=refx.DataSetName #> _ds;
        public <#=refx.DataSetName #>X(<#=refx.DataSetName #> ds)
        {
            _ds = ds;
        }
<#For Each table As TableDef In refx.TableDefList #>
        public <#=table.TableName#>DataTableX <#=table.TableName#>
        {
            get { return new <#=table.TableName#>DataTableX(_ds.<#=table.TableName#>); }
            //set { _ds.<#=table.TableName#> = value; }
        }
<#Next#>
        public <#=refx.DataSetName #> DataSet
        {
            get { return _ds; }
        }
    }

<#For Each table As TableDef In refx.TableDefList #>
    public class <#=table.TableName#>DataTableX: IEnumerable<<#=table.TableName#>RowX>
    {
        private <#=refx.DataSetName #>.<#=table.TableName#>DataTable _theTable;
        public <#=table.TableName#>DataTableX(<#=refx.DataSetName #>.<#=table.TableName#>DataTable table)
        {
            _theTable = table;
        }

        public <#=refx.DataSetName #>.<#=table.TableName#>DataTable Table
        {
            get { return _theTable; }
        }

        #region IEnumerable<<#=table.TableName#>RowX> Members

        public IEnumerator<<#=table.TableName#>RowX> GetEnumerator()
        {
            //return new <#=table.TableName#>RowXEnum(_rows);
            return new <#=table.TableName#>TableEnum(_theTable);
        }

        #endregion

        #region IEnumerable Members

        IEnumerator IEnumerable.GetEnumerator()
        {
            return (IEnumerator)this;
        }

        #endregion
    }

    public class <#=table.TableName#>TableEnum : IEnumerator<<#=table.TableName#>RowX>
    {
        private <#=refx.DataSetName #>.<#=table.TableName#>DataTable _collection;
        private int curIndex;
        private <#=table.TableName#>RowX curBox;

        public <#=table.TableName#>TableEnum(<#=refx.DataSetName #>.<#=table.TableName#>DataTable collection)
        {
            _collection = collection;
            curIndex = -1;
            curBox = default(<#=table.TableName#>RowX);
        }

        public bool MoveNext()
        {
            //Avoids going beyond the end of the collection.
            if (++curIndex >= _collection.Count)
                return false;
            else
                // Set current <#=table.TableName#>RowX to next item in collection.
                curBox = new <#=table.TableName#>RowX((<#=refx.DataSetName #>.<#=table.TableName#>Row)_collection.Rows[curIndex]);
            return true;
        }

        public void Reset() { curIndex = -1; }

        public void Dispose() { }

        public <#=table.TableName#>RowX Current
        {
            get { return curBox; }
        }

        object IEnumerator.Current
        {
            get { return curBox; }
        }
    }

    public class <#=table.TableName#>RowX
    {
        <#=refx.DataSetName #>.<#=table.TableName#>Row _row;
        public <#=table.TableName#>RowX(<#=refx.DataSetName #>.<#=table.TableName#>Row row)
        {
            _row = row;
        }
        /// <summary>
        /// The wrapped DataRow
        /// </summary>
        public <#=refx.DataSetName #>.<#=table.TableName#>Row Row
        {
            get { return _row; }
        }
        #region Properties
        <#For Each column As ColumnDef In table.ColumnDefList#>
            <#If column.AllowDBNull Then#>


        public <#=GetNullableType(column.DataType.ToString())#> <#=column.ColumnName#>
        {
            get
            {
                if (_row.Is<#=column.ColumnName#>Null())
                    return null;
                else
                    return _row.<#=column.ColumnName#>;
            }
            set
            {
                if (<#=GetHasValueFunction(column.DataType.ToString())#>)
                    _row.Set<#=column.ColumnName#>Null();
                else
                    _row.<#=column.ColumnName#> =  
                        <#=GetNullableValueName(column.DataType.ToString())#>;
            }
        }
        <#Else#>

        public <#=column.DataType.ToString()#> <#=column.ColumnName#>
        {
            get { return _row.<#=column.ColumnName#>; }
            set { _row.<#=column.ColumnName#> = value; }
        }
        <#End If#>
    <#Next#>

        #endregion Properties
    }
<#Next#>
}
<#+ 
    ''' <summary>
    ''' Gets a DataSetDef bases on parms passed in
    ''' </summary>
    ''' <param name="parms">
    ''' The GetParms object containing parameters needed to create DataSetDef:<br />
    ''' AssembyPath: The path of the assembly containig the DataSet<br />
    ''' DataSetName: The fully qualified name of the dataset
    ''' </param>
    ''' <returns>The DataSetDef</returns>
    Function GetDsDescripter(parms As GetParams) AS DataSetDef
        Dim AssembyPath As String = parms.CurrentParameters.Item("AssembyPath")
        Dim DataSetName As String = parms.CurrentParameters.Item("DataSetName")
        Return DataSetReflector.GetDataSetDef(AssembyPath, DataSetName)
    End Function
    ''' <summary>
    ''' Gets the nullable type name for a given type name
    ''' </summary>
    ''' <param name="typeName">
    ''' The type name as a string (as returned from Type.ToString())</param>
    ''' <returns>The Nullable type name</returns>
    ''' <remarks>
    ''' Since strings are nullable and not compatible with Generic Nullable class, 
    ''' must be treated special 
    ''' </remarks>
    Function GetNullableType(typeName As String) As String
        Dim rVal as string
        Select Case typeName
        Case "System.String"
            rVal = typeName
        Case Else
            rVal = "Nullable<" & typeName & ">"
        End Select
        Return rVal
    End Function
    ''' <summary>
    ''' Gets the string used to check nullablity in a set property method
    ''' </summary>
    ''' <param name="typeName">
    ''' The type name as a string (as returned from Type.ToString())
    ''' </param>
    ''' <returns>
    ''' string of an expression that will return true if value is null
    ''' </returns>
    ''' <remarks>
    ''' Most nullable types have a HasValue property but not all
    ''' </remarks>
    Function GetHasValueFunction(typeName As String) As String
        Dim rVal as string
        Select Case typeName
        Case "System.String"
            rVal = "System.String.IsNullOrEmpty(value)"
        Case Else
            rVal = "!value.HasValue"
        End Select
        Return rVal
    End Function
    ''' <summary>
    ''' Gets the string used on the right side of set property method
    ''' </summary>
    ''' <param name="typeName">
    ''' The type name as a string (as returned from Type.ToString())
    ''' </param>
    ''' <returns>The Value property name</returns>
    ''' <remarks>
    ''' Most nullable types have a Value property but not all
    ''' </remarks>
    Function GetNullableValueName(typeName As String) As String
        Dim rVal as string
        Select Case typeName
        Case "System.String"
            rVal = "value"
        Case Else
            rVal = "value.Value"
        End Select
        Return rVal
    End Function
#>

Generating a Nullable ADO.NET DataSet Wrapper: The Series

No comments: