Click here to Skip to main content
15,880,427 members
Articles / Programming Languages / C#

Decoupled LINQ to SQL framework

Rate me:
Please Sign up or sign in to vote.
5.00/5 (3 votes)
15 Feb 2009CPOL12 min read 45.4K   460   34   4
A decoupled LINQ to SQL framework using Dependency Injection (Unity) and Policy Injection (simple AOP).

Introduction

This article extends on the idea of a previous article which was a quick demo of combining dependency injection with LINQ to SQL, which can be seen here.

The framework defined in this article uses Microsoft's Dependency Injection framework called Unity, and Microsoft's simple version of AOP called Policy Injection. Both of these frameworks are found in the Microsoft Enterprise Library 4+. By using Dependency Injection, we can rely on IoC to create objects that have all of their dependencies wired up. In this case, we're going to ensure that only one DataContext (or, in our case, IDataContext) is used between any of the entities or services created. This way, we don't have to manage the scope of the DataContext manually.

Since all of the objects in this framework will be aware of the IDataContext thanks to Dependency Injection, we can then add some "nice to have" methods to our entities such "Save" (example: Article.Save();). I use EntitySpaces quite a bit, and I like this syntax as compared with LINQ to SQL. Using Unity as the Dependency Injection framework, we define the data layer in the configuration file. The configuration essentially maps interfaces to objects, which means it is very easy to change the type of object we want to be instantiated. This means, we can create a mock data layer, or easily map to a completely different data layer.

I've thrown PolicyInjection in the mix since the way the framework is put together, it is very easy to implement. This allows for extremely easy method based logging and caching (amongst other things). It is a good example of combining dependency injection with Policy Injection.

The source project is based on NUnit tests.

Background

This article uses the following technologies that you'll need to be aware of: LINQ to SQL, Dependency Injection, Policy Injection framework (AOP).

The reality is that Microsoft made it quite a bit hard to use Dependency Injection with LINQ to SQL since the most important things are not interfaced such as the System.Data.Linq.DataContext and the System.Data.Linq.Table<> classes. I suppose this article should really be called something like: Forcing LINQ to SQL to use interfaces. To make this work, I've created an IDataContext interface. It contains all of the properties and methods that are found in the System.Data.Linq.DataContext that can be implemented by custom classes:

C#
int ExecuteCommand(string command, params object[] parameters);
IEnumerable<TResult> ExecuteQuery<TResult>(string query, params object[] parameters);
IEnumerable ExecuteQuery(Type elementType, string query, params object[] parameters);
DbCommand GetCommand(IQueryable query);
ITable GetTable(Type type);
MetaModel Mapping { get; }
void SubmitChanges();
IEnumerable<TResult> Translate<TResult>(DbDataReader reader);
IEnumerable Translate(Type elementType, DbDataReader reader);

You'll notice that the GetTable<T> method is missing from the list. This is because this method can not be implemented by other classes since there is no direct way to construct a System.Data.Linq.Table<>. Microsoft did expose an interface of ITable which contains the basic methods required by the table; however, it isn't IEnumerable<T>, and cannot be used to write LINQ queries against. So, to get the functionality of both ITable and IEnumerable<T> into the IDataContext with one method, I've created another method:

C#
IEnumerableTable<T> GetITable<T>() where T : class;

This method exposes a custom interface that extends ITable and IEnumerable<T>. Now, we can call IDataContext.GetITable<T>() to query the tables, and still call all the ITable methods (such as InsertOnSubmit) on the returned object.

In this article, I've created a simple XML data context (XDataContext) to retrieve and store data in XML files instead of a SQL database. This has been kept quite simple, so not all interface members have been implemented. It was created simply to show that it can be done (it does work though :)

To keep things simple, the data structure that will be used will consist of Members, Articles, and Comments.

Setting up the DataContext

In order to map the generated LINQ to SQL DataContext to the IDataContext, we need to make a partial class for the generated class and ensure it implements IDataContext. Since the generated DataContext doesn't contain a definition for GetITable<T>, we also have to define this:

C#
public IEnumerableTable<T> GetITable<T>() where T : class
{
    return new EnumerableTable<T>(this.GetTable(typeof(T)));
}

The EnumerableTable class is literally just a wrapper class to expose both the ITable and IEnumerable<T>. This is essentially how we get around not being able to instantiate a LINQ Table<> object.

Setting up the data model classes

Each entity model definition is created by creating an interface that simply defines the properties of the model, and then having the interface inherit from IBaseEntity which exposes the dependency on the IDataContext and the basic methods that should be included in the entity such as Save and Delete. Each LINQ to SQL entity then needs to implement the model by creating a partial class for it. Then, in order for Dependency Injection to work, a constructor method is created for the LINQ to SQL classes that has an IDataContext object as a parameter (when Unity constructs an object, it looks for the constructor with the most parameters, and since this new constructor has more parameters than the default generated one, Unity knows that the IDataContext is a dependency).

In this project, IArticle, IMember, and IComment will be manually created, and partial classes for Article, Member, and Comment will need to be created to ensure that the LINQ to SQL classes implement these interfaces.

A class diagram of how the entities are setup:

Image 1

Setting up the service layer classes

A service is setup to expose methods to interact with the data for each table. A service interface needs to be created to define any data function that should take place. It then needs to inherit from the IBaseService<> which exposes the dependency on the IEntityServiceFactory (which in turn has a reference to the IDataContext and all other data services). Once the interface is setup, then the actual service as a class is created. This class inherits from BaseService<> which already defines the basic properties and methods required. For Dependency Injection to work, the constructor for each service is created that has an IEntityServiceFactory object as its parameter.

A class diagram of how the data services are setup:

Image 2

Setting up the configuration

The configuration section for Unity defines IoC containers. Each container maps interfaces to real objects, and in each mapping, we can define the lifetime of the object that Dependency Injection creates. Since we only want one DataContext created for the LINQ to SQL container, we can define it as a singleton. This maps the IDataContext to a singleton of the LINQ to SQL generated object.

XML
<type type="IDataContext" mapTo="LinqUnity.Linq.DataContext, LinqUnity">
  <lifetime type="singleton"/>
    <typeConfig 
      extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement, 
        Microsoft.Practices.Unity.Configuration">
      <constructor/>
      <!-- Ensure it is created with the default empty parameter constructor -->
    </typeConfig>
</type>

Now we need to map our data model interfaces to real objects; in this case, the generated LINQ to SQL classes.

XML
<type type="LinqUnity.Model.IMember, LinqUnity" 
    mapTo="LinqUnity.Linq.Member, LinqUnity"/>
<type type="LinqUnity.Model.IComment, LinqUnity" 
    mapTo="LinqUnity.Linq.Comment, LinqUnity"/>
<type type="LinqUnity.Model.IArticle, LinqUnity" 
    mapTo="LinqUnity.Linq.Article, LinqUnity"/>

And finally, we need to setup the data services. The syntax is really hard to look at, but this is how Microsoft made the string syntax for defining generic types. What this is actually doing is mapping IBaseService<T> to a real service. For example, the first mapping is mapping IBaseService<Member> to MemberService.

XML
<!-- The mangled syntax is Microsoft's standard for generic types -->
<type 
    type="TheFarm.Data.Linq.IBaseService`1[[LinqUnity.Linq.Member, LinqUnity]], 
         TheFarm.Data.Linq" 
    mapTo="LinqUnity.Service.MemberService, LinqUnity"/>
<type 
    type="TheFarm.Data.Linq.IBaseService`1[[LinqUnity.Linq.Comment, LinqUnity]], 
         TheFarm.Data.Linq" 
    mapTo="LinqUnity.Service.CommentService, LinqUnity"/>
<type 
    type="TheFarm.Data.Linq.IBaseService`1[[LinqUnity.Linq.Article, LinqUnity]], 
         TheFarm.Data.Linq" 
    mapTo="LinqUnity.Service.ArticleService, LinqUnity"/>

The EntityServiceFactory object

Now, we need a way to get Dependency Injection to build all of the objects for us. With the above configuration in place, the IoC container will give us a LinqUnity.Linq.DataContext object when an IDataContext is requested, a LinqUnity.Linq.Article object when an IArticle is requested, and so on. To get this to work, the EntityServiceFactory class has been created which has some methods to get Unity to create these objects for us:

  • T CreateEntity<T>() which creates an new entity of the specified type.
  • TQuery GetService<TEntity, TQuery>() which creates a data service with the interface type of TQuery and the entity type of TEntity.
  • T BuildEntity<T>(T entity) which "re-wires" up an existing entity object with all of its dependencies. In this case, entities are all dependent on the IDataContext.

The EntityServiceFactory implements IEntityServiceFactory which you'll notice is a property of the IBaseService<T> and therefore a dependency since it is a parameter of each data service constructor. The XML configuration above does not define a mapping to the IEntityServiceFactory, so in that case, Dependency Injection wouldn't actually be able to wire up all of the objects. However, when the EntityServiceFactory is constructed, it inserts itself into the container that it resolved from Unity at runtime as a singleton:

C#
container.RegisterInstance<IEntityServiceFactory>(this, 
                new ContainerControlledLifetimeManager());

This mapping could be defined in the XML as well, but then another custom class would need to be created to create the Unity objects, etc... I wanted to keep the EntityServiceFactory as the basic object to use for the framework so that implementation of this framework didn't require any knowledge of Unity. The default constructor for the EntityServiceFactory will load the container defined in the configuration file called DataLayer. Alternatively, you can pass a different container name to the overloaded constructor method.

Each service is dependent on the IEntityServiceFactory because each service may need a reference to the IDataContext and potentially the other data services.

Using the code

Implementing the data services

Normally, with LINQ to SQL, we would write queries based on the table properties generated on the DataContext, such as:

C#
var article = myDataContext.Articles.Where(x => x.ArticleId == 1).SingleOrDefault();

or:

C#
var article = myDataContext.GetTable<Article>().
     Where(x => x.ArticleId == 1).SingleOrDefault() 

This can't be done with this framework since neither Articles nor GetTable<T> are members of the IDataContext. Instead, we need to use the custom GetITable<T> method that has been created to expose an IEnumerable<T> object to query:

C#
var article = myDataContext.GetITable<Article>().
     Where(x => x.ArticleId == 1).SingleOrDefault() 

With the above syntax, our data service methods might look something like this:

C#
public List<IMember> GetMemberStartingWith(char c)
{
    return (from m in this.Factory.DataContext.GetITable<Member>()
            where m.Name.StartsWith(c.ToString())
            select (IMember)m)
            .ToList();                
}

As stated in the beginning of this article, one of the downfalls of this is that we're not querying directly against the System.Data.Linq.Table<T>, so we lose the additional extension methods available on the System.Data.Linq.Table<T> object as compared to the IEnumerable<T> object.

Exposing the data services

The EntityServiceFactory includes the basic methods for creating services and entities with all of their dependencies wired up; however, a nicer implementation would be to extend this class and expose properties for accessing each of the data services. In this example, this class is called ServiceFactory, and is quite simple with three properties: CommentService, ArticleService, and MemberService. Each call to one of these properties will return a new service object created from Dependency Injection. In its most simple form, one of the properties may look like:

C#
public IArticleService ArticleService
{
    get
    {
        return this.GetService<Article, IArticleService>();
    }
}

Policy Injection

Policy Injection is a simple AOP type of framework found in Microsoft's Enterprise Library. In this example we'll use Policy Injection to get logging and caching happening at the method level by simply attributing the methods you want logged or cached. To implement Policy Injection, we change the above properties code to:

C#
public IArticleService ArticleService
{
    get
    {
        IArticleService service = this.GetService<Article, IArticleService>();
        return PolicyInjection.Wrap<IArticleService>(service);
    }
}

Policy Injection requires that an object extends MarshalByRefObject, or that it implements an interface containing the methods that will be used in Policy Injection. Since all of our classes are interfaced, this is really easy to do.

To cache the output of a method, all you have to do is add the CachingCallHandler:

C#
[CachingCallHandler(0, 5, 0)]
public new List<IComment> SelectAll()
{
    return base.SelectAll()
        .Cast<IComment>()
        .ToList();
}

Now, the output of SelectAll() will be cached for 5 minutes. Logging is just as easy; however, it requires some entries in the configuration file (see the source code and Microsoft's documentation for more details):

C#
[LogCallHandler(BeforeMessage = "Begin", AfterMessage = "End")]
public IMember CreateNew(string name, string email, string phone)
{
    Member member = this.Factory.CreateEntity<Member>();
    member.Name = name;
    member.Email = email;
    member.Phone = phone;
    member.DateCreated = DateTime.Now;
    return (IMember)member;
}

The above will create a log entry before the method is called with the passed in parameter values, and after the method is called with the value of the returned object. The configuration section for the logging application block will allow you to configure exactly what is logged and how it is formatted.

Though attributing is quite easy, you can configure Policy Injection in the configuration file as well to dynamically change what is cached, logged, etc... without recompiling. However, the methods that are targeted still need to exist inside of an object that is wrapped or created with Policy Injection.

Using the data services

All you have to do to use the data services is create a ServiceFactory and access the properties to call the appropriate methods. This will create a new IMember:

C#
ServiceFactory factory = new ServiceFactory();
IMember newMember = factory.MemberService
    .CreateNew("Shannon", "blah@blah.com", "12345676"); 

Behind the scenes, this has created a new Member object, and also called the InsertOnSubmit method of its corresponding member ITable. To save the changes to the DataContext, we can just call:

C#
newMember.Save(); 

Calling factory.DataContext.SubmitChanges() would also do the same thing (but I think, the above is nicer to use :) LINQ to SQL doesn't have a nice way (as far as I know) to run an update on one entity or table, it simply will update all changes made, so the Save() method is really just a wrapper for the DataContext.SubmitChanges().

Since we've declared the IDataContext to be a singleton, this means that we don't have to worry about which DataContext created which entity, since it will always be the same when it is resolved from the factory. This allows us to create different entities from different services, link them together, save the changes to the database, and not have to worry about any errors regarding mismatched DataContexts:

C#
IMember newMember = memberService.CreateNew("My Name", 
     "blah@blah.com", "00000000");
IArticle newArticle = articleService.CreateNew("My Article", 
     "Some text...", "Some description...");

//Create 20 new comments with the IMember and IArticle created above 
List<IComment> comments = new List<IComment>();
for (int i = 0; i < 20; i++)
    comments.Add(commentService.CreateNew("My Comment", newMember, newArticle));

//save all new comments to the database at once
factory.DataContext.SubmitChanges();

Using Dependency Injection to map to alternate data contexts

As mentioned in the beginning of this article, I've created a test data context called XDataContext which stores data in XML files instead of a database. I've defined a second container in the configuration file which is exactly the same as the SQL container; however, the IDataContext is mapped to this XDataContext instead of the LINQ to SQL DataContext. I didn't create custom entities since the LINQ to SQL entities are quite simple to begin with and already take care of the entity relationships.

To use this other container, all we have to do is construct the EntityServiceFactory with the name of the container.

The XDataContext manages identity seeding and incrementing as well as tracking additions and deletions.

Points of interest

The "nice to have" methods such as Delete() and Save() that now exist on these entities also come with a catch. Using the EntityServiceFactory's CreateEntity<T> method to create an entity automatically wires up the entity's dependencies with the IDataContext so that Save() and Delete() can be called. However, when these entities are returned from a data source, they don't have their dependencies setup. In order to get this working, we have to use the BuildEntity<T> method of the EntityServiceFactory to wire up the dependencies for each object. This probably comes with a bit of a performance overhead. For example, the SelectAll() method:

C#
public virtual List<T> SelectAll()
{
    List<T> entities = (from x in Factory.DataContext.GetITable<T>() select x).ToList();
    entities.ForEach(x => Factory.BuildEntity<T>(x));
    return entities;
}

calls BuildEntity for each item returned from the data store. Considering there may be hundreds or thousands of rows, this may come at a cost. However, apart from the BuildEntity performance overhead, there's negligible overhead as compared to running normal LINQ to SQL with a lot of iterations.

On another note, I've read in quite a few places that serializing LINQ to SQL entities to XML is not possible without some trickery, so for this example, I've just implemented IXmlSerializable and custom serialized these objects.

Conclusion

I thought this was quite a fun exercise to show off some really cool technologies. It was also quite interesting trying to get around Microsoft's LINQ to SQL class structure to implement mock services without using some sort of type mocking library.

Best to download the source to see what is actually going on!

References

Here's more info on the technologies used:

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Web Developer The Farm Digital
Australia Australia
Shannon Deminick is the Technical Director of The Farm Digital, a Sydney based digital services agency.

Comments and Discussions

 
QuestionCreateNew Method LogCallHandler How to work Pin
jun chan14-May-12 2:56
jun chan14-May-12 2:56 
Generalhard to understand Pin
lxxsumi822-Oct-10 17:04
lxxsumi822-Oct-10 17:04 
GeneralThis wont work very well Pin
muttok15-Mar-09 22:30
muttok15-Mar-09 22:30 
AnswerRe: This wont work very well Pin
Shannon Deminick16-Mar-09 14:46
Shannon Deminick16-Mar-09 14:46 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.