Having fun with code
  • Blog
  • Favorites
    • Favorite Books
    • Favorite Libraries
    • Favorite Tools
  • Utilities
    • SP 2010 Colors
  • Contact
  • Home /
  • .NET /
  • T4 with LLBLGen

T4 with LLBLGen

November 8, 2013 / Matt C. / .NET

In this post, we look at leveraging T4 templates to extend the power of LLBLGen inside of Visual Studio .NET. Writing T4 templates to auto-generate code is almost a norm at this point it seems, it’s built-in to ASP.NET MVC, and used as a first-class tool in most ORMs. 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.
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:

T4-Project

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 :-).

T4-Project-Pocos

That’s all folks, hopefully this will be useful to someone out there. Cheers.

.net, ado.net, C#, code generation, entity framework, llblgen, odata, orm, servicestack, T4

3 comments on “T4 with LLBLGen”

  1. Dew Drop – November 11, 2013 (#1664) | Morning Dew says:
    November 11, 2013 at 8:11 am

    […] T4 with LLBLGen (Matt Cowan) […]

  2. Frans Bouma says:
    November 9, 2013 at 4:59 am

    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 🙂

    • Matt C. says:
      November 9, 2013 at 10:00 am

      Frans, thanks, that’s a lot simpler 🙂 , post updated. Good tip on referencing the db drivers too.

Categories

  • .NET (20)
  • ASP.NET MVC (4)
  • JAMstack (2)
    • Headless CMS (2)
  • Miscellaneous (2)
  • Python (1)
  • REST (3)
  • SharePoint (8)
  • WordPress (1)

Tags

.net ado.net autofac binding C# chrome code generation command line console application csv dapper di entity framework integration ioc job scheduling engine json jsv learning llblgen load balancing micro-orm mycorp odata orm people editor people picker picker controls picker dialog plugins pmp python Quartz.NET rest saf service application servicestack sharepoint smo soap sql sqlalchemy tornado web server validation web api

Archives

  • May 2020 (2)
  • November 2013 (1)
  • June 2013 (1)
  • May 2013 (1)
  • March 2013 (1)
  • December 2012 (1)
  • November 2012 (1)
  • October 2012 (3)
  • September 2012 (2)
  • June 2012 (2)
  • May 2012 (1)
  • April 2012 (1)
  • February 2012 (2)
  • January 2012 (1)
  • December 2011 (2)
  • September 2011 (2)
(c) 2013 Having fun with code - "FunCoding" theme designed by mattjcowan using the Theme Blvd framework