I’m finally getting around to my original goal of Dto Generation. I could probably have skipped everything that came before but that’s too late now.
Yesterday in my searches I discovered something new and wondrous Code Generation and T4 Text Templates. This seems to be exactly what I am looking for. Since my requirements are for run-time text generation, I’m following this guide verbatim. I’ve thrown the new new text templates into a new folder for clarity.
I’ve changed the template’s output extension to “.cs” since I require C# files, at least primarily. Otherwise I left the template as visual studio created it.
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> |
This code generator creates a long C# backing file with who knows what in it. I’m adding a partial class so that the text generator can accept parameters
namespace LeastCost.T4 { partial class DtoTemplate { #region Public Constructors public DtoTemplate() { } #endregion Public Constructors } } |
But what parameters? A TableMessage seems like a good start.
partial class DtoTemplate { #region Private Fields private readonly Messages.TableMessage _table; #endregion Private Fields #region Public Constructors public DtoTemplate(Messages.TableMessage table) { _table = table; } #endregion Public Constructors } |
I can now use those parameters inside the template
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ output extension=".cs" #> class <#= _table.Name #> { public <#= _table.Name #> () { } <# foreach(var column in _table.Columns) { #> public string <#= column.ColumnName #> { get; set; } <# } #> } |
Not perfect, and it uses a string datatype for everything. But I’m going to hook this up to the DtoGeneratorActor and see what output I get. It is worth mentioning at this time that I downloaded the sample “Adventure Works”
Receive<Messages.TableMessage>(x => { var temp = new T4.DtoTemplate(x); var output = temp.TransformText(); }); |
I’ve set a breakpoint there and the first output I get is
class EmployeeDepartmentHistory { public EmployeeDepartmentHistory () { } public string BusinessEntityID { get; set; } public string DepartmentID { get; set; } public string ShiftID { get; set; } public string StartDate { get; set; } public string ModifiedDate { get; set; } public string EndDate { get; set; } } |
This is really not too bad for the first try. I’m a little proud. Maybe a bit of formatting cleanup but the essence of my requirements is there.
My next step will be to create appropriate datatypes. I shall do so by using the mappings here. I came up with a partial solution to at least get things rolling with. I’m sure there are things wrong in there, but for now the point is to get this further along.
internal static class DataTypeConvertor { #region Public Constructors static DataTypeConvertor() { } #endregion Public Constructors #region Public Methods public static string GetDataType(string dataType) { string result; if(!_conversionTable.TryGetValue(dataType, out result)) result = "string"; return result; } #endregion Public Methods #region Private Fields /// <summary> /// Sql Server Data Type to CLR Data Type /// </summary> private static readonly IReadOnlyDictionary<string, string> _conversionTable = new Dictionary<string, string>() { ["bigint"] = "Int64", ["bit"] = "bool", ["char"] = "char", ["date"] = "DateTime", ["datetime"] = "DateTime", ["decimal"] = "decimal", ["float"] = "decimal", ["int"] = "int", ["money"] = "decimal", ["nchar"] = "string", ["numeric"] = "decimal", ["nvarchar"] = "string", ["smallint"] = "Int16", ["smallmoney"] = "decimal", ["time"] = "TimeSpan", ["timestamp"] = "DateTime", ["tinyint"] = "Byte", ["varchar"] = "string" }; #endregion Private Fields } |
And the template now looks like
<#@ template debug="false" hostspecific="false" language="C#" #> <#@ assembly name="System.Core" #> <#@ import namespace="System.Linq" #> <#@ import namespace="System.Text" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="LeastCost.Generation" #> <#@ output extension=".cs" #> using namespace System; class <#= _table.Name #> { public <#= _table.Name #>() { } <# foreach(var column in _table.Columns) { #> public <#= DataTypeConvertor.GetDataType(column.DataType) #> <#= column.ColumnName #> { get; set; } <# } #> } |
Another sample output
using namespace System; class AWBuildVersion { public AWBuildVersion() { } public Byte SystemInformationID { get; set; } public string Database Version { get; set; } public DateTime VersionDate { get; set; } public DateTime ModifiedDate { get; set; } } |
There are a lot of details to fix up, such as Nullables and that pesky space in this sample output
public string Database Version { get; set; } |
public <#= DataTypeConvertor.GetDataType(column.DataType) #> <#= column.ColumnName #> { get; set; } |
For this moment, I am content. T4 is pretty amazing.