Posts Tagged ‘POCO Class Generator’

Whilst playing with the Entity Framework, I though it would be pretty cool if the auto generated code would create and implement an interface along with the standard data context and entity classes. This would make unit testing with a mocking framework really easy, and allow you to write tests without having to access a data access store. Usually I write a wrapping repository to mock out, but this is a little tiresome. Since the Entity Framework is really flexible, this shouldn’t be too much trouble. So lets give it a bash.

I used the POCO class T4 templates as a basis. You can read more about them here. I am no T4 expert, but the code is intuitive enough to make sense of it, and more importantly to make some modifications to it.

Making the Changes

We first create a new class library project and create a folder for our EDMX and supporting classes in the repo.

01-New Project

After this we create a standard EDMX

02-Add New EDMX

I am going to play with a basic invoicing model

03-Model

I am also going to be doing this in design first mode, but it is no different if you are generating off an existing database. If we take a look at the generated code behind we can see a couple of things.

04-Designer Code

We see that our container inherits from the ObjectContext base class, we also see that outside of this class is a region for all the generated entities. These entities by default derive from the EntityObject class. The objective of the POCO T4 template is to decouple this using dynamic proxies and some black voodoo magic. Also if we take a look at the properties for the ObjectContext we notice that the Code Generation Strategy is set to Default.

05-Code Generation Strategy

What we are going to do next is to tell the EDMX to use our own custom code generation strategy. Right clicking on the design surface, we select “Add Code Generation Item..” from the context menu.

06-Add Code Generation Item

If you have installed the POCO generator extension then you will see it in the dialog box. Select this.

07-POCO Entity Generator

What this does is adds two T4 templates to the project, one for generating the new ObjectContext and the other for generating the entities. These .TT files have the generated code behind .CS files. You can explore them as well as the template that generated them. To help in viewing the T4 template I recommend downloading a copy of Tangible T4 Editor, this gives you some nice highlighting and some intellisense on the T4 template code.

08-Template Files

Next, we need to copy the .Context.TT file and paste it in the folder, this will give us a file to work with for the interface. Rename it to something more appropriate like “Context.Interface.tt”. We now dive into this new file and make some changes so that it generates an interface off the EDMX file.

10-Interface Definition

We create a prefix an “I” onto the container name, and implement the IDisposable interface onto the interface. Also make sure to change “class” to interface. We then remove all the unnecessary stuff like the constructor definitions.

11- Property Definition

For the property definitions we change them to a return type of IObjectSet. This is so we can mock out the return types for any LinQ / Lambda queries against the interface. Also make sure we only implement the GET portion of the property. Also get rid of all the implementation stuff around the property so that only the definition is left.

Onto the function imports. We do a similar thing, reduce to definitions and remove the implementation stuff.

12-Function Imports

For the VB.Net version of the POCO class generator there is a bug in the function import code, I blogged on this here. You will have to code around this. To be fair, I haven’t tested the ObjectResult return type, but if you cant instantiate it then you will have to do something similar to the properties and find an appropriate interface to substitute.

13-Generated Interface

If all goes well, on saving the template we get a nice clean interface generated against our context entities.

We also need to add some of the functionality found in the ObjectContext, like SaveChanges, etc. So it may be invoked through the interface. Add what you need.

18-Additional Context Methods

The next task is now modifying the generator for the ObjectContext so that it implements our interface.

14-Context Implementing Interface

We do this by modifying the definition of the ObjectContext to implement the interface.

We also change the property definition to return IObjectset. Also note for VB.Net, since you need to implement interfaces explicitly, you will also need to add this on the end of the property.

15-Properties Implement IObjectset

We can now inspect the new generated ObjectContext.

16-New Generated Context

Testing it Out

Lets write some implementation code to test. Typically one write some form of service that implements the data context. We also want to unit test the service by mocking out the repository.

Lets create a basic service.

17-Create Service

Some things to note here. We use a constructor dependency injection pattern here to allow us to inject our own flavor of repository. Also we have a method called “CreateInvoice” that receives some information (via a front end or wherever) and creates the header and associative entities for an invoice. You should be able to eyeball the implementation.

We now need a little helper class that implements the IObjectSet interface so we can return it. We also dont want it to be completely dumb as it would be nice if we could base our assertions off this object to verify the integrity of the data the service method is producing. So we base it off a List class. This was pulled off MSDN

19-Fake Obkect Set

We call this FakeObjectSet appropriately. Notice it is a generic.

We also add Rhino Mocks as our mocking framework.

20- Add Rhino Mocks

We then create a fancy mocking test to verify the behaviour of the service method. I am not going to worry about philosophies of mocking vs. stubbing here. This is just an example of a test.

21- Create fancy mock test

Some interesting points here are:

We use the generate our FakeObjectSets with fake data relevant to the test.

We use the mocking framework to return this data to the service.

The Repo is mocked.

We can query the repo after the fact to verify the data in it. We compare this to our input data to ensure all business rules have been applied.

We then run the test.

22- Verify Results

Green lights baby…green lights.

For getting started with the Entity Framework follow my multi part tutorial

Ok I give up. Is someone able to explain to me why the VB POCO generator T4 template refuses to generate my function imports. Not only that, but it doesn’t even generate the region for the function imports.
This pic shows the T4 code clearly defining the function import region

And this pic clearly shows the code NOT being generated.

Any ideas?

UPDATE: Found the problem

To answer my question, it seems that yes there is a bug. Looking at the template I cam across this nugget:

If edmFunction.ReturnParameter Is Nothing Then
  Continue For
End If

Essentially what this does is checks the return type of the import function and quits the rest of the iteration if the import function has no return type. Seems logical? NO.

You see in VB functions that don’t return a value are Subs. Since the generator can only handle functions, any function imports that don’t return a collection will not be generated. This is an issue for non-query functions.

You got a couple of options:

  1. Ensure your procs ALWAYS return something. Not always feasible.
  2. Modify the T4 template to handle Subs

I will try options 2.

Keep you posted

UPDATE: Found a potential solution

Ok I did my work around and replaced my “Function Imports” section in the T4 template with the following:

<#
region.Begin("Function Imports")
#>
<#
For Each edmFunction As EdmFunction In container.FunctionImports
Dim parameters As IEnumerable(Of FunctionImportParameter)  = FunctionImportParameter.Create(edmFunction.Parameters, code, ef)
Dim paramList As String = String.Join(", ", parameters.Select(Function(p) "ByVal " & p.FunctionParameterName & " As " & p.FunctionParameterType).ToArray())
If edmFunction.ReturnParameter Is Nothing Then
#>
<#=Accessibility.ForMethod(edmFunction)#> Sub <#=code.Escape(edmFunction)#>(<#=paramList #>)
<#
For Each parameter As FunctionImportParameter In parameters
If Not parameter.NeedsLocalVariable Then
Continue For
End If
#>
Dim <#=parameter.LocalVariableName #> As ObjectParameter
If <#=If(parameter.IsNullableOfT, parameter.FunctionParameterName & ".HasValue", parameter.FunctionParameterName & " IsNot Nothing")#> Then
<#=parameter.LocalVariableName#> = New ObjectParameter("<#=parameter.EsqlParameterName#>", <#=parameter.FunctionParameterName #>)
Else
<#=parameter.LocalVariableName#> = New ObjectParameter("<#=parameter.EsqlParameterName#>", GetType(<#=parameter.RawClrTypeName #>))
End If
<#
Next
#>
MyBase.ExecuteFunction("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join(", ", parameters.Select(Function(p) p.ExecuteParameterName).ToArray()))#>)
End Sub
<#
Else
Dim returnTypeElement As String = code.Escape(ef.GetElementType(edmFunction.ReturnParameter.TypeUsage))
#>
<#=Accessibility.ForMethod(edmFunction)#> Function <#=code.Escape(edmFunction)#>(<#=paramList #>) As ObjectResult(Of <#=returnTypeElement #>)
<#
For Each parameter As FunctionImportParameter In parameters
If Not parameter.NeedsLocalVariable Then
Continue For
End If
#>
Dim <#=parameter.LocalVariableName #> As ObjectParameter
If <#=If(parameter.IsNullableOfT, parameter.FunctionParameterName & ".HasValue", parameter.FunctionParameterName & " IsNot Nothing")#> Then
<#=parameter.LocalVariableName#> = New ObjectParameter("<#=parameter.EsqlParameterName#>", <#=parameter.FunctionParameterName #>)
Else
<#=parameter.LocalVariableName#> = New ObjectParameter("<#=parameter.EsqlParameterName#>", GetType(<#=parameter.RawClrTypeName #>))
End If
<#
Next
#>
Return MyBase.ExecuteFunction(Of <#=returnTypeElement#>)("<#=edmFunction.Name#>"<#=code.StringBefore(", ", String.Join(", ", parameters.Select(Function(p) p.ExecuteParameterName).ToArray()))#>)
End Function
<#
End If
Next
region.End()
#>

It seems to build and generate what seems to be a decent Sub. Please note, this is UNTESTED.