I have an application using ASP.NET MVC, Unity, and Linq to SQL.
The unity container registers the type AcmeDataContext which inherits from System.Data.Linq.DataContext, with a LifetimeManager using HttpContext.
There is a controller factory which gets the controller instances using the unity container. I set-up all my dependencies on the constructors, like this:
// Initialize a new instance of the EmployeeController class
public EmployeeController(IEmployeeService service)
// Initializes a new instance of the EmployeeService class
public EmployeeService(IEmployeeRepository repository) : IEmployeeService
// Initialize a new instance of the EmployeeRepository class
public EmployeeRepository(AcmeDataContext dataContext) : IEmployeeRepository
Whenever a constructor is needed, the unity container resolves a connection, which is used to resolve a data context, then a repository, then a service, and finally the controller.
The issue is that IEmployeeRepository exposes the SubmitChanges method, since the service classes DO NOT have a DataContext reference.
I have been told that the unit of work should be managed from outside the repositories, so it would seem I ought to remove SubmitChanges from my repositories. Why is that?
If this is true, does this mean that I have to declare an IUnitOfWork interface and make every service class dependent upon it? How else can I allow my service classes to manage the unit of work?
You shouldn’t try to supply the
AcmeDataContextitself to theEmployeeRepository. I would even turn the whole thing around:AcmeUnitOfWorkthat abstracts away LINQ to SQL.InMemoryAcmeUnitOfWorkfor unit testing.IQueryable<T>repositories.UPDATE: I wrote a blog post on this subject: Faking your LINQ provider.
Below is a step-by-step with examples:
WARNING: This will be a loooong post.
Step 1: Defining the factory:
Creating a factory is important, because the
DataContextimplement IDisposable so you want to have ownership over the instance. While some frameworks allow you to dispose objects when not needed anymore, factories make this very explicit.Step 2: Creating an abstract unit of work for the Acme domain:
There are some interesting things to note about this abstract class. The Unit of Work controls and creates the Repositories. A repository is basically something that implements
IQueryable<T>. The repository implements properties that return a specific repository. This prevents users from callinguow.GetRepository<Employee>()and this creates a model that is very close to what you are already doing with LINQ to SQL or Entity Framework.The unit of work implements
InsertandDeleteoperations. In LINQ to SQL these operations are placed on theTable<T>classes, but when you try to implement it this way it will prevent you from abstracting LINQ to SQL away.Step 3. Create a concrete factory:
The factory created a
LinqToSqlAcmeUnitOfWorkbased on theAcmeUnitOfWorkbase class:Step 4: Register that concrete factory in your DI configuration.
You know self best how to register the
IAcmeUnitOfWorkFactoryinterface to return an instance of theLinqToSqlAcmeUnitOfWorkFactory, but it would look something like this:Now you can change the dependencies on the
EmployeeServiceto use theIAcmeUnitOfWorkFactory:Note that you could even remove the
IEmployeeServiceinterface and let the controller use theEmployeeServicedirectly. You don’t need this interface for unit testing, because you can replace the unit of work during testing preventing theEmployeeServicefrom accessing the database. This will probably also save you a lot of DI configuration, because most DI frameworks know how to instantiate a concrete class.Step 5: Implement an
InMemoryAcmeUnitOfWorkfor unit testing.All these abstractions are there for a reason. Unit testing. Now let’s create a
AcmeUnitOfWorkfor unit testing purposes:You can use this class in your unit tests. For instance:
Step 6: Optionally implement convenient extension methods:
Repositories are expected to have convenient methods such as
GetByIdorGetByLastName. Of courseIQueryable<T>is a generic interface and does not contains such methods. We could clutter our code with calls likecontext.Employees.Single(e => e.Id == employeeId), but that’s really ugly. The perfect solution to this problem is: extension methods:With these extension methods in place, it allows you to call those
GetByIdand other methods from your code:What the nicest thing is about this code (I use it in production) is that -once in place- it saves you from writing a lot of code for unit testing. You will find yourself adding methods to the
AcmeRepositoryExtensionsclass and properties to theAcmeUnitOfWorkclass when new entities are added to the system, but you don’t need to create new repository classes for production or testing.This model has of course some shortcomes. The most important perhaps is that LINQ to SQL isn’t abstract away completely, because you still use the LINQ to SQL generated entities. Those entity contain
EntitySet<T>properties which are specific to LINQ to SQL. I haven’t found them to be in the way of proper unit testing, so for me it’s not a problem. If you want you can always use POCO objects with LINQ to SQL.Another shortcome is that complicated LINQ queries can succeed in test but fail in production, because of limitations (or bugs) in the query provider (especially the EF 3.5 query provider sucks). When you do not use this model, you are probably writing custom repository classes that are completely replaced by unit test versions and you will still have the problem of not being able to test queries to your database in unit tests. For this you will need integration tests, wrapped by a transaction.
A last shortcome of this design is the use of
InsertandDeletemethods on the Unit of Work. While moving them to the repository would force you to have a design with an specificclass IRepository<T> : IQueryable<T>interface, it prevents you from other errors. In the solution I use myself I also haveInsertAll(IEnumerable)andDeleteAll(IEnumerable)methods. It is however easy to mistype this and write something likecontext.Delete(context.Messages)(note the use ofDeleteinstead ofDeleteAll). This would compile fine, becauseDeleteaccepts anobject. A design with delete operations on the repository would prevent such statement from compiling, because the repositories are typed.UPDATE: I wrote a blog post on this subject that describes this solution in even more detail: Faking your LINQ provider.
I hope this helps.