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:
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:
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:
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:
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:
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.
<type type="IDataContext" mapTo="LinqUnity.Linq.DataContext, LinqUnity">
<lifetime type="singleton"/>
<typeConfig
extensionType="Microsoft.Practices.Unity.Configuration.TypeInjectionElement,
Microsoft.Practices.Unity.Configuration">
<constructor/>
</typeConfig>
</type>
Now we need to map our data model interfaces to real objects; in this case, the generated LINQ to SQL classes.
<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
.
<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:
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:
var article = myDataContext.Articles.Where(x => x.ArticleId == 1).SingleOrDefault();
or:
var article = myDataContext.GetTable<Article>().
Where(x => x.ArticleId == 1).SingleOrDefault()
This can't be done with this framework since neither Article
s 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:
var article = myDataContext.GetITable<Article>().
Where(x => x.ArticleId == 1).SingleOrDefault()
With the above syntax, our data service methods might look something like this:
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:
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:
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
:
[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):
[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
:
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:
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 DataContext
s:
IMember newMember = memberService.CreateNew("My Name",
"blah@blah.com", "00000000");
IArticle newArticle = articleService.CreateNew("My Article",
"Some text...", "Some description...");
List<IComment> comments = new List<IComment>();
for (int i = 0; i < 20; i++)
comments.Add(commentService.CreateNew("My Comment", newMember, newArticle));
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:
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: