Un modello di testo T4 costituisce una combinazione di blocchi di testo e logica di controllo in grado di generare un file di testo. La logica di controllo è scritta come frammenti di codice programma in Visual C# o Visual Basic. Il file generato può contenere testo di qualsiasi tipo, quale una pagina Web o un file di risorse o un codice sorgente del programma in qualsiasi linguaggio.
Sostanzialmente viene creata una classe partendo dallo schema di una tabella SQL, ovvero attraverso una spefica query viene richiesta la struttura di una tabella SQL e di conseguenza viene generata una classe in grado di contenere le informazioni di una riga della tabella.
Per l'esempio è stato utilizzato il database AdventureWorks2012.
Occorre creare un nuovo progetto visual studio di tipo Class Library, eliminare il file Class1.cs che viene inserito in modo predefinito nel progetto e aggiungere un nuovo elemento di tipo Text Template:
viene generato un file con estensione .tt ed aggiunto al progetto corrente avente il seguente contenuto:
<#@ 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=".txt" #>
Al fine di ottenere il risultato voluto dobbiamo importare i namespace necessari e modifcare alcune direttive in modo da ottenere il seguente risultato:
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ output extension=".cs" #>
I file .tt vengono valutati a tempo di progettazione e producono un file di output a seconda delle impostazioni, nel nostro caso viene generato un file .cs, in linea di massima vige la regola che tutto quello che è presente tra <# e #> viene compilato l'output prodotto finisce nel file generato.
Si possono anche inserire metodi da utilizzare per la generazione dell'output, in questo caso il metodo deve essere compreso tra i delimitatori <#+ e #> (vedremo un esempio più avanti).
Ora aggiungiamo un primo segmento di codice che ci consente di ottenere le informazioni della tabella su cui costruire la classe:
<#
string tableName = "Person.Person";
string className = "Person";
string dbName = "AdventureWorks2012";
using(SqlConnection sqlConnection = new SqlConnection(String.Format("Data Source=(local)\\MSSQLSERVER2012;database={0};Integrated Security=SSPI", dbName)))
{
sqlConnection.Open();
using(SqlCommand cmd = new SqlCommand("SELECT * FROM " + tableName, sqlConnection))
{
DataTable dt = cmd.ExecuteReader().GetSchemaTable();
#>
Quindi aggiungamo i namespace necessari alla classe:
using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
A questo punto iniziamo a dichiarare il namespace e la classe in esso contenuta:
namespace <#= dbName #>
{
public class <#= className #>
{
Il generatore di codice sostuirà <#= dbName #> con il valore in precedenza definito, cioè "AdventureWorks2012", stessa cosa con <#= className #>.
Inseriamo un ciclo che aggiunge un campo privato per ogni colonna della tabella in cui il tipo del campo viene ricavato dal tipo di dato della colonna:
<#
foreach(DataRow dr in dt.Rows)
{
WriteLine(" private " + dr["DataType"].ToString() + (Convert.ToBoolean(dr["AllowDBNull"]) ? "?" : "") + " m_" + dr["ColumnName"] + ";");
}
#>
Inseriamo in costruttore della classe:
public <#= className #>()
{
}
Quindi generiamo le proprietà pubbliche per ogni colonna:
<#
foreach(DataRow dr in dt.Rows)
{
WriteLine(" public " + dr["DataType"].ToString() + (Convert.ToBoolean(dr["AllowDBNull"]) ? "?" : "") + " " + dr["ColumnName"]);
WriteLine(" {");
WriteLine(" get { return m_" + dr["ColumnName"] + "; }");
WriteLine(" set { m_" + dr["ColumnName"] + " = value; }");
WriteLine(" }");
}
}
}
#>
Per rendere compilabile il codice generato chiudiamo le parentesi graffe precedentemente aperte:
}
}
A scopo di esempio aggiungiamo un segmento di codice che non sarà inserito nella classe generata ma salverà su file la classe prodotta:
<#
String path = System.IO.Path.GetDirectoryName(Host.TemplateFile);
Save(System.IO.Path.Combine(path, "Person.cs"));
#>
<#+
private void Save(String fileName)
{
using(System.IO.StreamWriter sw = new System.IO.StreamWriter(fileName))
{
sw.Write(this.GenerationEnvironment.ToString());
}
}
#>
Una volta eseguito il generatore troveremo nella directory di progetto il file "person.cs".
Il risultato finale sarà qualcosa del tipo:
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ output extension=".cs" #>
<#
string tableName = "Person.Person";
string className = "Person";
string dbName = "AdventureWorks2012";
using(SqlConnection sqlConnection = new SqlConnection(String.Format("Data Source=(local)\\MSSQLSERVER2012;database={0};Integrated Security=SSPI", dbName)))
{
sqlConnection.Open();
using(SqlCommand cmd = new SqlCommand("SELECT * FROM " + tableName, sqlConnection))
{
DataTable dt = cmd.ExecuteReader().GetSchemaTable();
#>
using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
namespace <#= dbName #>
{
public class <#= className #>
{
<#
foreach(DataRow dr in dt.Rows)
{
WriteLine(" private " + dr["DataType"].ToString() + (Convert.ToBoolean(dr["AllowDBNull"]) ? "?" : "") + " m_" + dr["ColumnName"] + ";");
}
#>
public <#= className #>()
{
}
<#
foreach(DataRow dr in dt.Rows)
{
WriteLine(" public " + dr["DataType"].ToString() + (Convert.ToBoolean(dr["AllowDBNull"]) ? "?" : "") + " " + dr["ColumnName"]);
WriteLine(" {");
WriteLine(" get { return m_" + dr["ColumnName"] + "; }");
WriteLine(" set { m_" + dr["ColumnName"] + " = value; }");
WriteLine(" }");
}
}
}
#>
}
}
<#
String path = System.IO.Path.GetDirectoryName(Host.TemplateFile);
Save(System.IO.Path.Combine(path, "Person.cs"));
#>
<#+
private void Save(String fileName)
{
using(System.IO.StreamWriter sw = new System.IO.StreamWriter(fileName))
{
sw.Write(this.GenerationEnvironment.ToString());
}
}
#>
e la classe prodotta sarà:
using System;
using System.Data;
using System.Data.SqlClient;
using System.IO;
namespace AdventureWorks2012
{
public class Person
{
private System.Int32 m_BusinessEntityID;
private System.String m_PersonType;
private System.Boolean m_NameStyle;
private System.String? m_Title;
private System.String m_FirstName;
private System.String? m_MiddleName;
private System.String m_LastName;
private System.String? m_Suffix;
private System.Int32 m_EmailPromotion;
private System.String? m_AdditionalContactInfo;
private System.String? m_Demographics;
private System.Guid m_rowguid;
private System.DateTime m_ModifiedDate;
public Person()
{
}
public System.Int32 BusinessEntityID
{
get { return m_BusinessEntityID; }
set { m_BusinessEntityID = value; }
}
public System.String PersonType
{
get { return m_PersonType; }
set { m_PersonType = value; }
}
public System.Boolean NameStyle
{
get { return m_NameStyle; }
set { m_NameStyle = value; }
}
public System.String? Title
{
get { return m_Title; }
set { m_Title = value; }
}
public System.String FirstName
{
get { return m_FirstName; }
set { m_FirstName = value; }
}
public System.String? MiddleName
{
get { return m_MiddleName; }
set { m_MiddleName = value; }
}
public System.String LastName
{
get { return m_LastName; }
set { m_LastName = value; }
}
public System.String? Suffix
{
get { return m_Suffix; }
set { m_Suffix = value; }
}
public System.Int32 EmailPromotion
{
get { return m_EmailPromotion; }
set { m_EmailPromotion = value; }
}
public System.String? AdditionalContactInfo
{
get { return m_AdditionalContactInfo; }
set { m_AdditionalContactInfo = value; }
}
public System.String? Demographics
{
get { return m_Demographics; }
set { m_Demographics = value; }
}
public System.Guid rowguid
{
get { return m_rowguid; }
set { m_rowguid = value; }
}
public System.DateTime ModifiedDate
{
get { return m_ModifiedDate; }
set { m_ModifiedDate = value; }
}
}
}
Per eseguire operazioni di debug è necessario agganciare il debugger, inserento la seguente riga di codice prima del punto di blocco:
System.Diagnostics.Debugger.Launch();
quindi bloccare l'esecuzione con la seguente riga:
System.Diagnostics.Debugger.Break();
Ad esempio aggancimo il debugger subito dopo le dichiarazioni iniziali:
<#@ template debug="true" hostspecific="true" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ assembly name="System.Data" #>
<#@ assembly name="System.Xml" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ import namespace="System.Data" #>
<#@ import namespace="System.Data.SqlClient" #>
<#@ output extension=".cs" #>
<#
System.Diagnostics.Debugger.Launch();
string tableName = "Person.Person";
string className = "Person";
string dbName = "AdventureWorks2012";
...
ed inseriamo il break nel ciclo che crea i campi della classe:
...
<#
foreach(DataRow dr in dt.Rows)
{
System.Diagnostics.Debugger.Break();
WriteLine(" private " + dr["DataType"].ToString() + (Convert.ToBoolean(dr["AllowDBNull"]) ? "?" : "") + " m_" + dr["ColumnName"] + ";");
}
#>
...
Al prossimo salvataggio una finestra di dialogo ci chiederà se vogliamo eseguire il debug di devenv.exe, rispondiamo di si:
Quindi una ulteriore richiesta ci chiede con quale strumento e quale istanza vogliamo eseguire il debug, selezionamo "New instance of Microsoft Visual Studio 2012":
Ora possiamo eseguire il debug del codice:
Per generare codice esternamente a visual studio è possibile utilizzare il tool TextTransform.exe disponibile nella directory:
\Program Files\Common Files\Microsoft Shared\TextTemplating\11.0
Esempio:
TextTransform [options] MyTempate.tt
Per ulteriori informazioni:
http://msdn.microsoft.com/en-us/library/bb126245.aspx
CreateClassFromSqlTable.zip