Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to remove unit of work functionality from repositories using IOC

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?

like image 880
Ed I Avatar asked Nov 08 '10 22:11

Ed I


People also ask

Do we need unit of work with Entity Framework?

Not necessarily. EF already provides the unit of work pattern for you. The only reason to still have a unit of work is if you: want to include non-EF-datasources in an atomic data operation.

What is unit of work in .NET core?

The Unit of Work is a type of business transaction, and it will aggregate all Repository transactions (CRUD) into a single transaction. Only one commit will be made for all modifications. If any transaction fails to assure data integrity, it will be rolled back.

What is unit of work in Entity Framework?

The unit of work class serves one purpose: to make sure that when you use multiple repositories, they share a single database context. That way, when a unit of work is complete you can call the SaveChanges method on that instance of the context and be assured that all related changes will be coordinated.

What is the right way to include a repository into a controller?

By taking advantage of dependency injection (DI), repositories can be injected into a controller's constructor. the following diagram shows the relationship between the repository and Entity Framework data context, in which MVC controllers interact with the repository rather than directly with Entity Framework.


2 Answers

You shouldn't try to supply the AcmeDataContext itself to the EmployeeRepository. I would even turn the whole thing around:

  1. Define a factory that allows creating a new unit of work for the Acme domain:
  2. Create an abstract AcmeUnitOfWork that abstracts away LINQ to SQL.
  3. Create a concrete factory that can allows creating new LINQ to SQL unit of works.
  4. Register that concrete factory in your DI configuration.
  5. Implement an InMemoryAcmeUnitOfWork for unit testing.
  6. Optionally implement convenient extension methods for common operations on your 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:

public interface IAcmeUnitOfWorkFactory
{
    AcmeUnitOfWork CreateNew();
}

Creating a factory is important, because the DataContext implement 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:

public abstract class AcmeUnitOfWork : IDisposable
{
    public IQueryable<Employee> Employees
    {
        [DebuggerStepThrough]
        get { return this.GetRepository<Employee>(); }
    }

    public IQueryable<Order> Orders
    {
        [DebuggerStepThrough]
        get { return this.GetRepository<Order>(); }
    }

    public abstract void Insert(object entity);

    public abstract void Delete(object entity);

    public abstract void SubmitChanges();

    public void Dispose()
    {
        this.Dispose(true);
        GC.SuppressFinalize(this);
    }

    protected abstract IQueryable<T> GetRepository<T>()
        where T : class;

    protected virtual void Dispose(bool disposing) { }
}

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 calling uow.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 Insert and Delete operations. In LINQ to SQL these operations are placed on the Table<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:

public class LinqToSqlAcmeUnitOfWorkFactory : IAcmeUnitOfWorkFactory
{
    private static readonly MappingSource Mapping = 
        new AttributeMappingSource();

    public string AcmeConnectionString { get; set; }

    public AcmeUnitOfWork CreateNew()
    {
        var context = new DataContext(this.AcmeConnectionString, Mapping);
        return new LinqToSqlAcmeUnitOfWork(context);
    }
}

The factory created a LinqToSqlAcmeUnitOfWork based on the AcmeUnitOfWork base class:

internal sealed class LinqToSqlAcmeUnitOfWork : AcmeUnitOfWork
{
    private readonly DataContext db;

    public LinqToSqlAcmeUnitOfWork(DataContext db) { this.db = db; }

    public override void Insert(object entity)
    {
        if (entity == null) throw new ArgumentNullException("entity");
        this.db.GetTable(entity.GetType()).InsertOnSubmit(entity);
    }

    public override void Delete(object entity)
    {
        if (entity == null) throw new ArgumentNullException("entity");
        this.db.GetTable(entity.GetType()).DeleteOnSubmit(entity);
    }

    public override void SubmitChanges();
    {
        this.db.SubmitChanges();
    }

    protected override IQueryable<TEntity> GetRepository<TEntity>() 
        where TEntity : class
    {
        return this.db.GetTable<TEntity>();
    }

    protected override void Dispose(bool disposing) { this.db.Dispose(); }
}

Step 4: Register that concrete factory in your DI configuration.

You know self best how to register the IAcmeUnitOfWorkFactory interface to return an instance of the LinqToSqlAcmeUnitOfWorkFactory, but it would look something like this:

container.RegisterSingle<IAcmeUnitOfWorkFactory>(
    new LinqToSqlAcmeUnitOfWorkFactory()
    {
        AcmeConnectionString =
            AppSettings.ConnectionStrings["ACME"].ConnectionString
    });

Now you can change the dependencies on the EmployeeService to use the IAcmeUnitOfWorkFactory:

public class EmployeeService : IEmployeeService
{
    public EmployeeService(IAcmeUnitOfWorkFactory contextFactory) { ... }

    public Employee[] GetAll()
    {
        using (var context = this.contextFactory.CreateNew())
        {
            // This just works like a real L2S DataObject.
            return context.Employees.ToArray();
        }
    }
}

Note that you could even remove the IEmployeeService interface and let the controller use the EmployeeService directly. You don't need this interface for unit testing, because you can replace the unit of work during testing preventing the EmployeeService from 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 InMemoryAcmeUnitOfWork for unit testing.

All these abstractions are there for a reason. Unit testing. Now let's create a AcmeUnitOfWork for unit testing purposes:

public class InMemoryAcmeUnitOfWork: AcmeUnitOfWork, IAcmeUnitOfWorkFactory 
{
    private readonly List<object> committed = new List<object>();
    private readonly List<object> uncommittedInserts = new List<object>();
    private readonly List<object> uncommittedDeletes = new List<object>();

    // This is a dirty trick. This UoW is also it's own factory.
    // This makes writing unit tests easier.
    AcmeUnitOfWork IAcmeUnitOfWorkFactory.CreateNew() { return this; }

    // Get a list with all committed objects of the requested type.
    public IEnumerable<TEntity> Committed<TEntity>() where TEntity : class
    {
        return this.committed.OfType<TEntity>();
    }

    protected override IQueryable<TEntity> GetRepository<TEntity>()
    {
        // Only return committed objects. Same behavior as L2S and EF.
        return this.committed.OfType<TEntity>().AsQueryable();
    }

    // Directly add an object to the 'database'. Useful during test setup.
    public void AddCommitted(object entity)
    {
        this.committed.Add(entity);
    }

    public override void Insert(object entity)
    {
        this.uncommittedInserts.Add(entity);
    }

    public override void Delete(object entity)
    {
        if (!this.committed.Contains(entity))
            Assert.Fail("Entity does not exist.");

        this.uncommittedDeletes.Add(entity);
    }

    public override void SubmitChanges()
    {
        this.committed.AddRange(this.uncommittedInserts);
        this.uncommittedInserts.Clear();
        this.committed.RemoveAll(
            e => this.uncommittedDeletes.Contains(e));
        this.uncommittedDeletes.Clear();
    }

    protected override void Dispose(bool disposing)
    { 
    }
}

You can use this class in your unit tests. For instance:

[TestMethod]
public void ControllerTest1()
{
    // Arrange
    var context = new InMemoryAcmeUnitOfWork();
    var controller = new CreateValidController(context);

    context.AddCommitted(new Employee()
    {
        Id = 6, 
        Name = ".NET Junkie"
    });

    // Act
    controller.DoSomething();

    // Assert
    Assert.IsTrue(ExpectSomething);
}

private static EmployeeController CreateValidController(
    IAcmeUnitOfWorkFactory factory)
{
    return new EmployeeController(return new EmployeeService(factory));
}

Step 6: Optionally implement convenient extension methods:

Repositories are expected to have convenient methods such as GetById or GetByLastName. Of course IQueryable<T> is a generic interface and does not contains such methods. We could clutter our code with calls like context.Employees.Single(e => e.Id == employeeId), but that's really ugly. The perfect solution to this problem is: extension methods:

// Place this class in the same namespace as your LINQ to SQL entities.
public static class AcmeRepositoryExtensions
{
    public static Employee GetById(this IQueryable<Employee> repository,int id)
    {
        return Single(repository.Where(entity => entity.Id == id), id);
    }

    public static Order GetById(this IQueryable<Order> repository, int id)
    {
        return Single(repository.Where(entity => entity.Id == id), id);
    }

    // This method allows reporting more descriptive error messages.
    [DebuggerStepThrough]
    private static TEntity Single<TEntity, TKey>(IQueryable<TEntity> query, 
        TKey key) where TEntity : class
    {
        try
        {
            return query.Single();
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException("There was an error " +
                "getting a single element of type " + typeof(TEntity)
                .FullName + " with key '" + key + "'. " + ex.Message, ex);
        }
    }
}

With these extension methods in place, it allows you to call those GetById and other methods from your code:

var employee = context.Employees.GetById(employeeId);

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 AcmeRepositoryExtensions class and properties to the AcmeUnitOfWork class 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 Insert and Delete methods on the Unit of Work. While moving them to the repository would force you to have a design with an specific class IRepository<T> : IQueryable<T> interface, it prevents you from other errors. In the solution I use myself I also have InsertAll(IEnumerable) and DeleteAll(IEnumerable) methods. It is however easy to mistype this and write something like context.Delete(context.Messages) (note the use of Delete instead of DeleteAll). This would compile fine, because Delete accepts an object. 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.

like image 191
13 revs, 2 users 99% Avatar answered Nov 16 '22 03:11

13 revs, 2 users 99%


If combining unit of work and repository patterns, some people advocate that UoW should be managed outside of repository so that you could create two repositories (say, CustomerRepository and OrderRepository) and pass them the same UoW instance ensuring that all the changes to the DB will be done atomically when you finally call UoW.Complete().

In a mature DDD solution however, there should not be need for both UoW and a repository. This is because is such a solution aggregate boundaries are defined is such a way, that there is no need of atomic changes involving more than one repository.

Does this answer your question?

like image 22
Szymon Pobiega Avatar answered Nov 16 '22 02:11

Szymon Pobiega