Episode 2 – Injecting the context

Alright, it’s about time for the second post in my series on a reference architecture for back-end services. As you may recall, my last post covered how to build up Repositories using the code-only features of Entity Framework CTP 4. This time I’ll talk a bit about how to connect things together using dependency injection – and I’ll focus on how you can store the DbContext so that it can be used by multiple repositories in a single service operation.

I’m going to assume that you have basic understanding of Dependency Injection. I will be using Unity 2.0  since that is what I’m most familiar with, but you could very easily adapt this to your container of choice, be it nInject, Structure Map, Windsor or what have you.

OperationContext storage

Now, first of all we need somewhere to store the DbContext. Had this been a pure ASP.NET client we could have simply used HttpContext.Current.Request, but in WCF-land it is much nicer to store things in an OperationContext extension.

/// <summary>
/// Extension class for keeping instances of object stored in the current OperationContext. 
/// </summary>
public class OperationContextStorageExtension : IExtension<OperationContext>
{
    private readonly IDictionary<Type, Tuple<object, Action>> _instances;

    private OperationContextStorageExtension()
    {
        _instances = new Dictionary<Type, Tuple<object, Action>>();
    }

    /// <summary>
    /// Store an object
    /// </summary>
    /// <param name="value">Object to store</param>
    /// <param name="completedAction">Optional Action to invoke when the current operation has completed.</param>
    public void Store<T>(T value, Action completedAction = null)
    {
        _instances[typeof(T)] = new Tuple<object, Action>(value, completedAction);
    }

    /// <summary>
    /// Get a stored object
    /// </summary>
    /// <typeparam name="T">Type of the object to get</typeparam>
    /// <returns>The stored object or null if no object of type <typeparamref name="T"/> has been stored</returns>
    public T Get<T>()
    {
        Tuple<object, Action> obj;
        if (!_instances.TryGetValue(typeof(T), out obj))
            return default(T);
        
        return (T)obj.Item1;
    }

    public void Attach(OperationContext owner)
    {
        // Make sure we are notified when the operation is complete
        owner.OperationCompleted += OnOperationCompleted;
    }

    public void Detach(OperationContext owner)
    {
        owner.OperationCompleted -= OnOperationCompleted;
    }

    void OnOperationCompleted(object sender, EventArgs e)
    {
        // Invoke any actions
        foreach(var obj in _instances.Values)
        {
            if (obj.Item2 != null)
                obj.Item2.Invoke();
        }
    }

    /// <summary>
    /// Get or create the one and only StorageExtension
    /// </summary>
    public static OperationContextStorageExtension Current
    {
        get
        {
            var opContext = OperationContext.Current;
            if (opContext == null)
                throw new InvalidOperationException("No OperationContext");

            var currentContext = opContext.Extensions.Find<OperationContextStorageExtension>();
            if (currentContext == null)
            {
                currentContext = new OperationContextStorageExtension();
                opContext.Extensions.Add(currentContext);
            }
            
            return currentContext;
        }
    }
}

Some abstractions

Next, we need way of abstracting away the creation of a DbContext. A simple factory will do:

public interface IContextFactory
{
    DbContext Create();
}

public class DbContextFactory<TContext> : IContextFactory
    where TContext : DbContext, new()
{
    public DbContext Create()
    {
        return new TContext();
    }
}

In my last post, the constructor of the RepositoryBase implementation used a DbContext to get to the IDbSet it required. But again we need a bit of abstraction, so let’s use an interface instead.

public interface ISetProvider
{
    IDbSet<TEntity> CreateSet<TEntity>() where TEntity : class;
}

public class RepositoryBase<TEntity> : IRepository<TEntity>
    where TEntity : class
{
    private readonly IDbSet<TEntity> _dbSet;

    public RepositoryBase(ISetProvider setProvider)
    {
        _dbSet = setProvider.CreateSet<TEntity>();
    }
    ...
}

The adapter

Now to the thing that ties it all together. This adapter will take care of creating the DbContext using an IContextFactory, storing it using the OperationContextStorageExtension and will implement the ISetProvider that can be used by your Repositories. The adapter also implements the IUnitOfWork interface (basically, the SaveChanges-method) so that you can tell it to persist your new and updated objects.

/// <summary>
/// This will act as an ISetProvider and IUnitOfWork using a per-operation DbContext that is
/// automatically disposed of when completed.
/// </summary>
public class WcfObjectContextAdapter : ISetProvider, IUnitOfWork
{
    private readonly IContextFactory _contextFactory;

    /// <summary>
    /// Create a new instance of the adapter
    /// </summary>
    /// <param name="contextFactory">Factory to use to construct the DbContext</param>
    public WcfObjectContextAdapter(IContextFactory contextFactory)
    {
        _contextFactory = contextFactory;
    }

    public IDbSet<TEntity> CreateSet<TEntity>() where TEntity : class
    {
        var context = GetCurrentContext();
        return context.Set<TEntity>();
    }

    public int SaveChanges()
    {
        var context = GetCurrentContext();
        return context.SaveChanges();
    }

    private DbContext GetCurrentContext()
    {
        var storageExtension = OperationContextStorageExtension.Current;

        var dbContext = storageExtension.Get<DbContext>();

        if (dbContext == null)
        {
            // No DbContext has been created for this operation yet. Create a new one...
            dbContext = _contextFactory.Create();
            // ... store it and make sure it will be Disposed when the operation is completed.
            storageExtension.Store(dbContext, () => dbContext.Dispose());
        }

        return dbContext;
    }
}

Of course, you’ll need to be able to inject all this into your Repository.  If you’re into Unity, something like this should work to get your context up:

container.RegisterType<ISetProvider, WcfObjectContextAdapter>("MyContext");
container.RegisterType<IUnitOfWork, WcfObjectContextAdapter>("MyContext");
container.RegisterType<ISetProvider, WcfObjectContextAdapter>("MyContext");
container.RegisterType<IUnitOfWork, WcfObjectContextAdapter>("MyContext");

container.RegisterType<IContextFactory, DbContextFactory<MyDbContext>>("MyContext");

container.RegisterType<WcfObjectContextAdapter>("MyContext", 
    new ContainerControlledLifetimeManager(), 
    new InjectionConstructor(new ResolvedParameter<IContextFactory>("MyContext")));

container.RegisterType<IContextFactory, DbContextFactory<TContext>>("MyContext");

container.RegisterType<WcfObjectContextAdapter>("MyContext", new ContainerControlledLifetimeManager(),
new InjectionConstructor(new ResolvedParameter<IContextFactory>("MyContext")));

Ok, so I agree.. Unity does not have the most user-friendly syntax. Maybe it’s time to start considering alternatives.. Which one is your favorite?

Using it!

Remember our TaskService from last time? It was riddled with using statements, creating new contexts and repositories. Not very DRY.. But with some DI magic and the above-mentioned Adapter, this is what we end up with:

public class TaskService : Contract.ITaskService
{
    private readonly IUnitOfWork _unitOfWork;
    private readonly ITaskRepository _taskRepository;
    private readonly int _pageSize = 10;

    public TaskService(ITaskRepository taskRepository, IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
        _taskRepository = taskRepository;
    }

    public IEnumerable<Contract.Task> ListTasks()
    {
        var tasks = from t in _taskRepository.List()
                    select new Contract.Task() { Id = t.Id, Title = t.Title };

        return tasks;
    }

    public void AddTask(string title, int priority)
    {
        _taskRepository.Add(new Task { Title = title, Priority = priority });
        _unitOfWork.SaveChanges();
    }

    public void RemoveTask(int id)
    {
        var task = _taskRepository.Get(t => t.Id == id);
        _taskRepository.Delete(task);
        _unitOfWork.SaveChanges();
    }
}

Much better looking if you ask me.

I’ve decided to keep the SaveChanges-calls and the IUnitOfWork interface exposed to the service. It would be a no-brainer to let the Adapter make sure to always SaveChanges when an operation completes instead, but that to me feels a little bit too much like magic that’s happening behind the scenes. And I’m not a huge fan of magic.

Summary

In this episode I’ve shown a way of making it easier to get to your Repositories by storing them in the WCF Operation Context and plugging them in using a Dependency Injection framework.

I will try to keep this series going and I would really appreciate to hear from you! Do leave a comment, here or perhaps on Twitter (where I tend to spend far too much time lately).



2 responses to “Episode 2 – Injecting the context”

  1. […] This post was mentioned on Twitter by Anders Ljusberg, Anders Ljusberg. Anders Ljusberg said: Blogged: Episode 2 – Injecting the context! http://ande.rs/96GD4S […]

    Like

  2. Hi,
    I really like what you have done with EF code first.
    I am learning code first 4.1

    I was wondering if you could email me the actual solution with the example for some reasons I cannot get it to work.

    Thanks a lot
    brix

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

About Me

Consultant, Solution Architect, Developer.

Do note that this blog is very, very old. Please consider that before you follow any of the advice in here!

Newsletter

%d bloggers like this: