Writing T4 templates to auto-generate code is almost a norm at this point it seems, it’s built-in to so much of ASP.NET MVC, and used as a first-class tool in most ORMs it seems. So why not use T4 templates to extend the power of LLBLGen as well. While I like to write LLBLGen templates, I’ve found using T4 templates to be a nice addition to LLBLGen in the VS.NET toolbox.
Let’s get to it.
Step 1 – Create a project and add T4 goodies to it
Create a .NET 4+ class library project (or console, whatever you like really), you don’t need to have any of the LLBLGen assemblies as a reference in the project (you will need a licensed copy of LLBLGen on your machine however to execute the T4 templates, since working with the LLBLGen application APIs will require it, unless you have a small project with less than 8 entities, in which case you can use LLBLGen Lite).
Install the “T4 Toolbox” extension (you don’t need this extension, but it’s a really good one, and I’m using it in this short post ;-p).
Also, in this post, I’m using the T4 TemplateFileManager library file (just an additional *.tt file), which will help us in generating files inside the project. You can also get it as a nuget package (“Install-Package T4.TemplateFileManager”).
Step 2 – Write some T4 code
At this point, here’s what my project looks like:
You’ll notice I put the LLBLGen project “Northwind.llblgenproj” in my project, but you don’t have to do that. You can just point to the project wherever it is on your file system, that’ll be fine too. This is just convenient for me for this post.
There are two files that matter here, the “Toolbox.ttinclude” file, and the “Northwind.tt” file. The first is just a container for functions and stuff like that. The second file is where the T4 code that leverages LLBLGen APIs goes.
Toolbox.ttinclude
This file basically gets information from Visual Studio like the current project name, namespaces, and current directory (it uses T4 Toolbox methods to do that)
<#@ assembly name="System.Core" #> <#@ assembly name="System.Configuration" #> <#@ assembly name="EnvDTE" #> <#@ assembly name="Microsoft.VisualStudio.Shell.11.0" #> <#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #> <#@ import namespace="EnvDTE" #> <#@ import namespace="Microsoft.VisualStudio" #> <#@ import namespace="Microsoft.VisualStudio.Shell" #> <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #> <#@ include file="T4Toolbox.tt" #><# // Load VS Project info // See: http://msdn.microsoft.com/en-us/library/EnvDTE(v=vs.110).aspx // See: http://www.olegsych.com/2012/12/t4-toolbox-for-visual-studio-2012/ var dte = (DTE)TransformationContext.Current.GetService(typeof(DTE)); var projectItem = dte.Solution.FindProjectItem(TransformationContext.Current.Host.TemplateFile); var vsProject = projectItem.ContainingProject; var vsProjectName = vsProject.Name; var rootNamespace = (string)vsProject.Properties.Item("RootNamespace").Value; var vsSolution = (IVsSolution)TransformationContext.Current.GetService(typeof(SVsSolution)); IVsHierarchy vsHierarchy; ErrorHandler.ThrowOnFailure(vsSolution.GetProjectOfUniqueName(vsProject.FullName, out vsHierarchy)); uint projectItemId; ErrorHandler.ThrowOnFailure(vsHierarchy.ParseCanonicalName(projectItem.FileNames[1], out projectItemId)); object defaultNamespaceObj; ErrorHandler.ThrowOnFailure(vsHierarchy.GetProperty(projectItemId, (int)VsHierarchyPropID.DefaultNamespace, out defaultNamespaceObj)); var defaultNamespace = (string)defaultNamespaceObj; var currentDirectory = System.IO.Path.GetDirectoryName(TransformationContext.Current.Host.TemplateFile); #> <#+ // put some functions here if you want #>
Northwind.tt
Here’s the code in this file that generates some POCOs for each entity in the LLBLGen project file. The code generation and APIs are similar to the ones you would use in creating LLBLGen templates.
The code below to load an LLBLGen project from a file is provided courtesy of the LLBLGen support team.
<#@ template debug="false" hostspecific="true" language="C#" #> <#@ assembly name="C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0\SD.LLBLGen.Pro.ApplicationCore.dll" #> <#@ assembly name="C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0\SD.LLBLGen.Pro.Core.dll" #> <#@ assembly name="C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0\SD.LLBLGen.Pro.DBDriverCore.dll" #> <#@ assembly name="C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0\SD.LLBLGen.Pro.GeneratorCore.dll" #> <#@ assembly name="C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0\SD.LLBLGen.Pro.VSNetIntegrationSupport.dll" #> <#@ assembly name="C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0\SD.Tools.Algorithmia.dll" #> <#@ assembly name="C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0\SD.Tools.BCLExtensions.dll" #> <#@ assembly name="System.Core" #> <#@ assembly name="System.Configuration" #> <#@ assembly name="EnvDTE" #> <#@ assembly name="Microsoft.VisualStudio.Shell.11.0" #> <#@ assembly name="Microsoft.VisualStudio.Shell.Interop" #> <#@ import namespace="EnvDTE" #> <#@ import namespace="Microsoft.VisualStudio" #> <#@ import namespace="Microsoft.VisualStudio.Shell" #> <#@ import namespace="Microsoft.VisualStudio.Shell.Interop" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.Configuration" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.EntityModel" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.EntityModel.ModelViews" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.EntityModel.TypedLists" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.EntityModel.Validation" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.Mapping" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.TvfCalls" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.TypedViews" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.StoredProcedureCalls" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.EntityModel" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.ProjectClasses" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.CodeGenerationMetaData.Tasks" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.Extensibility" #> <#@ import namespace="SD.LLBLGen.Pro.GeneratorCore" #> <#@ import namespace="System.Collections.Generic" #> <#@ import namespace="SD.Tools.BCLExtensions.CollectionsRelated" #> <#@ import namespace="SD.Tools.BCLExtensions.SystemRelated" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.CodeGenerationMetaData" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.CodeGenerationMetaData.Templates" #> <#@ import namespace="SD.Tools.Algorithmia.Commands" #> <#@ import namespace="SD.LLBLGen.Pro.ApplicationCore.MessageReporting" #> <#@ import namespace="SD.Tools.Algorithmia.GeneralDataStructures.EventArguments" #> <#@ import namespace="System.Windows.Forms" #> <#@ import namespace="System.Xml" #> <#@ import namespace="System.Reflection" #> <#@ import namespace="System.Configuration" #> <#@ output extension=".cs" #><#@ include file="$(ProjectDir)\T4\Toolbox.ttinclude" #><#@ include file="$(ProjectDir)\T4\TemplateFilemanager.ttinclude" #><# // Use: https://github.com/MrJul/ForTea for Intellisense var myCompanyName = "MJCZone Inc."; var llblInstallPath = @"C:\Program Files (x86)\Solutions Design\LLBLGen Pro v4.0"; var llblProjectFile = currentDirectory + @"\Northwind.llblgenproj"; // Load LLBLGen Project info var project = LoadLlblGenProjectFile(llblInstallPath, llblProjectFile); var entities = new HashSet<EntityDefinition>(project.GetAllEntities()); var typedLists = new HashSet<TypedListDefinition>(project.TypedLists); var typedViews = new HashSet<TypedViewDefinition>(project.TypedViews); var valueTypes = new HashSet<ValueTypeDefinition>(project.ValueTypes); var procedures = new HashSet<SPCallDefinition>(project.SPCalls); var functions = new HashSet<TvfCallDefinition>(project.TvfCalls); //see: http://t4toolbox.codeplex.com/SourceControl/latest var manager = TemplateFileManager.Create(this); CodeHeader(myCompanyName); #> <# foreach(var entity in entities){ manager.StartNewFile(entity.Name + "Dto.cs", vsProjectName, "DtoClasses"); CodeHeader(myCompanyName); #> using System; namespace <#= project.Properties.RootNameSpace #>.DtoClasses { public class <#= entity.Name #>Dto { <# foreach(var field in entity.Fields){#> public <#= field.FieldType.RepresentedType.Name #> <#= field.Name #> { get; set; } <# } #> } } <# } #> <# manager.Process(); #> <#+ //File Header void CodeHeader(string companyName){ #>/* ----------------------------------------------------------------------------- Code auto-generated on <#= System.DateTime.Now.ToString() #> Copyright 2013 (c) <#= companyName #> ----------------------------------------------------------------------------- */ <#+ } // Following code courtesy of LLBLGen SD.LLBLGen.Pro.ApplicationCore.ProjectClasses.Project LoadLlblGenProjectFile(string llblInstallPath, string filename){ if (!CoreStateSingleton.GetInstance().IsInitialized) { var configurationToUse = System.Configuration.ConfigurationManager.OpenExeConfiguration(llblInstallPath + @"\CliGenerator.exe"); CoreStateSingleton.GetInstance().Initialize(llblInstallPath, (m,c) => {}, (m,c) => {}, (m,c) => {}); CoreStateSingleton.GetInstance().ConfigurationSettings = new ApplicationConfiguration(configurationToUse, llblInstallPath); CoreStateSingleton.GetInstance().LoadObjectsAndConfigData(); } return SD.LLBLGen.Pro.ApplicationCore.ProjectClasses.Project.Load(filename); } #>
Generated Output
The POCOs generated are very simple, but they give you the basic idea I hope. Here’s a sample Employee POCO:
public class EmployeeDto { public String Address { get; set; } public DateTime BirthDate { get; set; } public String City { get; set; } public String Country { get; set; } public Int32 EmployeeId { get; set; } public String Extension { get; set; } public String FirstName { get; set; } public DateTime HireDate { get; set; } public String HomePhone { get; set; } public String LastName { get; set; } public String Notes { get; set; } public Byte[] Photo { get; set; } public String PhotoPath { get; set; } public String PostalCode { get; set; } public String Region { get; set; } public Int32 ReportsTo { get; set; } public String Title { get; set; } public String TitleOfCourtesy { get; set; } }
And the DTOs are all created nicely within a custom folder: “DtoClasses”. Now it’s up to your creative juices to figure out what you want to do. Coupled with the LLBLGen QuickModel editor (command line creation of your entities, fields, entity relationship), entity grouping, multi-database support, LLBLGen templating, custom properties, settings, custom plugins, value types, and custom TypeConverters (which I use to serialize complex types into nvarchar fields with the ServiceStack OrmLite Jsv and Json serializers), support for EF, OData, OrmLite (with these simple T4 POCOs, just need to add better attribute annotations), it’s pretty powerful stuff :-).
That’s all folks, hopefully this will be useful to someone out there. Cheers.
[…] T4 with LLBLGen (Matt Cowan) […]
Very nice! 🙂
Why not simply call Project.Load to load the project? No xml reader required. 🙂
Also, if you peek into the CliRefresher startup code (it’s in the SDK), you’ll see you need to load the drivers as well if you want to do things with the mappings / relational model data (you’re not doing that here, but in case you want to, you need to 🙂
Frans, thanks, that’s a lot simpler 🙂 , post updated. Good tip on referencing the db drivers too.