Sep 3rd 2009 08:24 pm

Generating Business Layer Code T4 against a Linq to Sql DAL

I’ve recently gotten into using Microsoft’s Text Template Transformation Toolkit (T4) to automate create the boilerplate code in our business layer. Because the T4 framework comes with Visual Studion 2008 I didn’t have to purchase and deploy any additional tools to each developer’s machine. Altough T4 is not the easiest thing to understand, the time savings has proven to be exceptional. Once the initial solution was in place, bringing other developers onboard to use and extend the framework has gone very well.

At first I scoured Google for other people’s T4 templates. I hoped to find a plethora sample templates that would save me the trouble of starting from scratch. What I found instead was a handful of tools to replace the code generated by visual studio when saving a dbml. My goal was not to replace this code, but to extend it – to make binding asp.net repeaters and gridviews to filtered and sorted listings of records as flexible and easy as possible.

One exceptional tool I came acroos was the T4Toolbox . While the T4Toolbox provides a version of the aforementioned DataContext and entity code generator based on a dbml file, it also provides an exceptional framework for building very extensive T4 templates. Ultimately I chose not to use the T4Toolbox because it required installation and was much more tool than what I needed.

I then stumbled upon the LINQ to SQL templates for T4 framework (l2st4) project on codeplex.  It also generates a richer data context and entities than the out of the box microsoft. While this wasn’t what I was looking for, the project does a great job of encapsulating the deserializated dbml file. Using it’s ”Data” object we can iterate over all the tables, views, user defined functions, and sprocs in the dbml file and get at their underlying type definitions, relationships, and metadata in a very sensible way.

Using this information, I wrote a T4 template to try and encapsulate most of the boilerplate functionality we need from our data access layer.

My focus was to:

  • set createdby/on and modifiedby/on automatically when inserting/updating records with those fields
  • create static methods to load records by foreign keys e.g.
    public static IQueryable<Order> LoadByCustomer(string customerID, NorthwindDataContext dataContext)
  • create objects to encapsulate sort, filter, and paging information specific to the entity. Allow developers to extend these objects for custom filtering and sorting.

Our applications filter and sort sets of records intensively but are light on creating, updating and deleting records. The use case I followed was to simplify the work needed to bind a gridview/oject data source to the business layer. Given a UI like:

gridview

The filter at the top allows user’s to restrict the records by Company name (contains, starts with, ends with, exact match) and by number of orders (<5, <10, etc.). They can also sort on the company name field and the number of orders per customer.

Within the codebehind of the page/user control in the object datasource selecting event:

//user typed code
protected void ods_Selecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
e.InputParameters[0] = BuildFilter();
e.InputParameters[1] = DataContext;
}

//Note: Customer.Filter is a code generated partial class
private Customer.Filter BuildFilter()
{
//Filter to pass to business layer
var filter = new Customer.Filter();
//Filter Company Name by value of textbox
filter.CompanyName.Value = txtContains.Text.Trim();
//Filter on Company Name by dropdown value - Like, Exact Match, Start With, Ends With
filter.CompanyName.MatchType =
(LinqToSqlCodeGenSample.CodeGen.StringFilter.Match)System.Enum.Parse(
typeof(LinqToSqlCodeGenSample.CodeGen.StringFilter.Match),
 ddlMatchType.SelectedValue);
if (ddlNumberOfOrders.SelectedIndex > 0)
{
Int32 maxOrderCount = Int32.Parse(ddlNumberOfOrders.SelectedValue);
//Set Lamdba expression to filter out customers by the number of orders
filter.FilterExpression = x => x.Orders.Count() < maxOrderCount;
}
return filter;
}

//Stubbed out business layer api (generated by T4 Template)
public static IQueryable<Customer> Load(Filter filter, NorthwindDataContext dataContext)

public static Int32 LoadCount(Filter filter, NorthwindDataContext dataContext)

The T4 template and a sample solution can be found here.

The “filter” objects contain information for sorting, paging, and filtering records. String properties can be filtered by exact match, like, starts with, and ends with.

When a custom sort or filter is necessary, developers can override the filter’s ProcessCustomSortExpression(String sortExpression) method to add their own custom lamda expression to the Filter object’s SortCriteria list.

When filtering records by something more than column name, developer can override the ProcessAdditionalFilterInformation(IQueryable<T> qry, U dataContext) method in a filter’s partial class.

This is by no means a complete solution. Every day we’ve been finding better ways to implement such a framework. I hope this serves as an example of how to use the l2st4 templates to generate code against a dbml file and gives an idea of how useful it can be.

No Comments yet »

Trackback URI | Comments RSS

Leave a Reply

« | »