The goal of this exercise is to generate a class that inherits from System.Data.Entity.DbContext. It will contain properties of type System.Data.Entity.DbSet for each class that is already contained in the project. Also, the script will examine these classes for a property called “Id” and, if it doesn’t exist, add it.
Now you could argue that the manipulation of project items is not exactly what you would want to do in a T4 template. As a matter of fact, you would normally do this in a Visual Studio Extension. But for the sake of simplicity, I will demonstrate this in a T4 template since the code is almost the same.
So let’s get it on…. consider the initial project structure in the image below. In the folder “Data Classes” I have a couple of classes that I would like to store in a data base. For that, I need a DbContext class that holds all these classes as properties.
That DbContext class should look like this:
Of course, we programmers are terribly lazy, so why would I want to code that by hand? Also, in real-world projects we typically have lots and lots of data classes. So I’ll create a T4 template (“Add New Item”->”Text Template”) that generates this code for me and call it DataStore.tt. Because I know that I’ll be using Visual Studio automation, I’ll add assembly references to envdte.dll and envdte80.dll. For the Entity Framework classes DbContext and DbSet<> I’ll add references to System.Data.Entity.dll and EntityFramework.dll.
My naked tt file looks like this:
Please note the hostspecific attribute. This attribute must be set to true so that the class that resembles the text template will contain a property called Host which returns an IServiceProvider object that allows us to access Visual Studio.
So now that we’ve got everything set up, these are the steps the T4 template will need to do:
- Locate all affected classes
- Find a property named “Id”
- If no Id property is found, create it
- Generate the DbContext class and its properties
Before we can do that, we must get access to Visual Studio. The class that resembles the text template itself gets generated in the AppData folder of the user. It inherits Microsoft.VisualStudio.TextTemplating.TextTransformation. If the hostspecific attribute is set to “true” as stated above, a property called “Host” is generated into the text template class. The value of the Host property can be casted to IServiceProvider. This interface allows us to query Visual Studio for all sorts of services we need. The service we’re interested in here is EnvDTE.DTE (although the name does not look like it, this is an interface). The interface DTE is “the top-level object in the Visual Studio automation object model” and allows us to access our projects and project items.
Now that we’ve got our DTE, we can go on and execute each of the steps we’ve defined above.
Note: all code I’m showing here is in fact inside the tt file. I’ll put all following methods in so-called “class feature control blocks”. Please refer to this article on MSDN for details.
Locate Affected Classes
At first, I need some methods to iterate the project items in search of classes. The classes I’m interested in are all inside the folder “Data Classes” and I want to restrict the search to that folder. I’ll start with defining two methods that search for that folder:
The first method is called like this:
At line 3, we’ll get the file name otf the currently active document (i.e. the t4 template) and extract its directory path. Then at line 4 we add the relative path we’re interested in (here: “Data Classes”). Now we have the absolute path of the folder we’Re interested in.
Then, from line 9 on, we’ll iterate each item in the current project. A project item may be a file or a folder in the project. The type EnvDTE.ProjectItem contains a property FileNames which contains the absolute path of the item. We just compare it to the path we’re searching for and in case of success return the project item. Otherwise, we continue by iterating the item’s child items until we’ve found what we’re looking for.
So now that we’ve got the project item that represents the folder “Data Classes”, we must iterate the classes defined within it. Let’s look at the method for that:
I’ll just go through each line and explain what it does.
03 Get the folder “Data Classes”
05 Get all child project items of the folder …
06 … that are code files and actually contain code (i.e. not just empty code files)
07 Get all namespace elements from the files
08 Get all types defined in the namespace elements …
09 … that are not enumerations
Of course, we could restrict the returned types even more, for example to not return interfaces or delegates. But for the sake of simplicity, let’s assume that we only have classes and enums in our project.
Find A Property “Id”
Now we need to get the Id property. Actually, this is quite easy (lines 3 and 4), so I’ll something more complex. Lets assume that the property we’re looking for does not need to be called “Id”, but that instead we can identify it if it has the attribute System.Xml.Serialization.XmlElementAttribute applied with the Order property set to zero (
Now let’s go throw each line in the method above and explain it:
03 Get all properties of the type.
04 Find the property named “id” (case-insensitive comparison)
05 If the property exists, return it and exit the method
07 Iterate each property
09 Try to get an attribute named “System.Xml.Serialization.XmlElementAttribute”
10 If the attribute does not exist, continue with next property
12 Try to get the property “Order” on that attribute
13 If the property “Order” does not exist or if its value is not “0”, continue with next property
15-27 Only use the classes property if it’s type suites our needs (i.e. if it can be stored in a database column)
31 No suitable property was found, return null
Create A Property “Id”
The method GetKeyProperty() above returns null if there is no key property. In that case we’ll need to add one ourselves. This is the part you usually wouldn’t do in a T4 template. But then again, this is just a demo. The next method, CreateKeyProperty(), will create a member field named “_id” and a public property named “Id” that accesses that variable.
Again, let’s walk through the code:
03 Cast the CodeType object to a type with which we can actually modify the code
04 Add a private integer field named “_id”
05 Add the System.NonSerializedAttribute to the field (this is not really needed, but for the sake of the demo I wanted to demonstrate this)
06 Add a public integer property named “Id”
At this point, the generated code would look like this:
As you see, the properties implementation is not really useful. So we’ll need to add some custom code by ourselves. Let’s continue with the lines of CreateKeyProperty()…
08 Get the starting point of the properties implementation (that is line 6 of the generated code)
09 Get the ending point of the properties implementation (that is line 12 of the generated code)
11 Get an object that allows us to manipulate the file at the specified starting point
13 Replace the properties implementation with the text an appropriate getter and setter
15 return the generated property
Now, the generated code look like this:
Great! Now we’ve got all the pieces together and are able to generate the DbContext class.
Generate DbContext Class
Before we start generating the DbContext class, let’s talk about how Entity Framework Code First works. Entities should (must?) define key properties that get translated into primary key columns. One way to do this is to add a KeyAttribute to the key property. If you want to keep you data classes (POCOs) free from references to Entity Framework or if you cannot modify them, you can alternatively override the DbContexts OnModelCreating method. In that case, you can add code as this to define the key properties:
Here, I want to go that way. So I’ll need to add each property to the content of the method above. The method that will generate this code looks like this:
The parameter it gets is a dictionary with the found class as key and the key property as value (we’ll get to how to create this parameter further below).
In the introduction, I showed how the generated properties should look like. The method that generates this code is quite simple:
Just as simple is the method that generates the whole DbContext class:
Easy, right? Now we’ll need to put all the pieces together. All the methods we’ve created by now are put into a class feature control block. The next code snippet will be placed into a standard control block (again, please refer to this article on MSDN for details on these terms):
Although most of the code in the snippet above is obvious, let’s go through each line and explain…
01 Define the folder our data classes are located
02 Get the service provider to query for the DTE
03 Query the service provider for the DTE
04 Get the file name of the T4 template – this will be used a the name of the generated class
05 Since the current project is a C# project (haven’t I mentioned that? Well, it is), it has a default namespace which we get here
07 Get the classes defined in the current project in the “Data Classes” folder…
08 … that have more than one property (remember, each class will be stored as a table, so one-column-tables make not much sense)
10 Create a dictionary with the retrieved classes as keys and their key properties as values
12 Actually generate the code
At the end, here’s the generated code and the structure of our solution. Cool, right?
This was a somewhat lengthy article about code generation using T4 templates and code manipulation using Visual Studio automation. Although the scenario in this article is made up, it did show some useful techniques and maybe some starting points for further learning. Also, it shows how powerful T4 templates and Visual Studio automation are and how they can be used in the daily programmers work.