15 Dec
2009

Unity Extension for Entity Framework POCO Configuration, Repository and Unit of Work

Category:UncategorizedTag: :

In my previous two posts I talk about simple EF4 mappings and some common abstractions I like to work with for my entity persistence operations. In this post I want to take all of the setup code necessary for EF configuration and registration and encapsulate it along with the standard boiler plate IOC container code you might see in an application BootStrapper.

 

A Typical DI Example

Though its pretty typical, I thought it would be good to show an example of how I expect to work with a Repository in my application. The ?End Result? if you will.

The following is how one of my MVC.NET controllers might look (same for domain services, WCF services etc). Dependencies are satisfied via constructor injection when the controller or service is built up by the infrastructure (ControllerFactory or perhaps WCF IInstanceProvider).

public class RosterController
{
   private readonly IRepository<Team> _teamRepository;
 
   public RosterController(IRepository<Team> teamRepository)
   {
       _teamRepository = teamRepository;
   }
 
   public void AddNewPlayer(long teamId, string playerName, string position)
   {
       var team = _teamRepository.First(x => x.ID == teamId);
       team.Players.Add(new Player{ Name = playerName, Position = position });
   }
}

In order to ?wire up? the above example, there is quite a bit that has to happen.

 

The Unity Extension

Unity provides extension points, called UnityContainerExtension?s which are a perfect place to handle IOC initialization for a given component. For those familiar with Castle Windsor facilities, its kind of like that (or StructureMap registries).

The EFRepositoryExtension (could probably use a better name?) handles:

  • Creating the ContextBuilder and registering it with the Container
  • Registering the Generic Repository<T> and UnitOfWork
  • Context Lifetime
  • Entity Configuration & EntitySet Registration
  • Context BuildUp

The end result is nice clean extension I can register in my BootStrapper like so:

var container = IoC.Container; //some static, thread safe container class
 
container.AddNewExtension<EFRepositoryExtension>();
container.Configure<IEFRepositoryExtension>()
         .WithConnection(cnxString)
         .WithContextLifetime(new HttpContextLifetimeManager<IObjectContext>())
         .ConfigureEntity(new TeamConfiguration())
         .ConfigureEntity(new PlayerConfiguration());

A pretty nice API to work with if I get to say so 😉
 
Fluent Interface
public interface IEFRepositoryExtension : IUnityContainerExtensionConfigurator
{
    IEFRepositoryExtension WithConnection(string connectionString);
    IEFRepositoryExtension WithContextLifetime(LifetimeManager lifetimeManager);
    IEFRepositoryExtension ConfigureEntity<T>(EntityConfiguration<T> config);
    IEFRepositoryExtension ConfigureEntity<T>(EntityConfiguration<T> config, string setName);
}

Extension Implementation
public class EFRepositoryExtension : UnityContainerExtension, IEFRepositoryExtension
{
  private ContextBuilder<ObjectContext> _builder;
  private SqlConnection _connection;
 
  protected override void Initialize()
  {
      _builder = new ContextBuilder<ObjectContext>();
      //register the builder instance as a singleton, this will hold all of our 
      //mapping information for the duration of our application as it creates 
      //new data contexts
      Container.RegisterInstance("builder", _builder, 
                                 new ContainerControlledLifetimeManager());
 
      //Register Repo & UOW. Those these are transient instances, they both take
      //a ctor dependency on the ObjectContext which has its lifetime controlled
      //by the Extension. E.g., for an Http current request, all repository and
      //UOW will use the same context/transaction
      Container.RegisterType(typeof(IRepository<>), typeof(Repository<>));
      Container.RegisterType<IUnitOfWork, UnitOfWork>();
  }
 
  public IEFRepositoryExtension WithConnection(string connectionString)
  {
      _connection = new SqlConnection(connectionString);
      return this;
  }
 
  public IEFRepositoryExtension ConfigureEntity<T>(EntityConfiguration<T> config)
  {
      //simple pluralization of the entity set
      ConfigureEntity(config, typeof(T).Name+"s");
      return this;
  }
 
  public IEFRepositoryExtension ConfigureEntity<T>(EntityConfiguration<T> config, 
                                                   string setName)
  {
      //add the configuration
      _builder.Configurations.Add(config);
      //register the set metadata
      _builder.RegisterSet<T>(setName);
      return this;
  }
 
  public IEFRepositoryExtension WithContextLifetime(LifetimeManager lifetimeManager)
  {
      Container.AddNewExtension<StaticFactoryExtension>();
      Container.Configure<IStaticFactoryConfiguration>()
               .RegisterFactory<IObjectContext>(x => 
                   ContextResolver(x, lifetimeManager, _connection));
      
      return this;
  }
 
  //factory func to build context with given lifetime & connection
  static readonly Func<IUnityContainer, LifetimeManager, SqlConnection, object> 
      ContextResolver = (c, l, s) =>
      {
          var context = l.GetValue();
          if (context == null)
          {
              var builder = c.Resolve<ContextBuilder<ObjectContext>>("builder");
              var newContext = builder.Create(s);
              context = new ObjectContextAdapter(newContext);
              l.SetValue(context);
          }
 
          return context;
      };
}

 

The Test

Doing simple service location & using TestLifetime, my prototype (not really a ?test? I suppose)looks like:

var cnxString = ConfigurationManager.ConnectionStrings["Test"].ConnectionString;
var container = IoC.Container; //some static, thread safe container class
 
container.AddNewExtension<EFRepositoryExtension>();
container.Configure<IEFRepositoryExtension>()
    .WithConnection(cnxString)
    .WithContextLifetime(new TestLifetimeManager<IObjectContext>())
    .ConfigureEntity(new TeamConfiguration())
    .ConfigureEntity(new PlayerConfiguration());
 
container.RegisterType<RosterController>();
 
var rosterController = container.Resolve<RosterController>();
 
rosterController.AddNewPlayer(3, "Austin Collie", "");
 
container.Resolve<IUnitOfWork>().Commit();

 

Thoughts and feedback are welcome.

Find me

RSS
Facebook
Twitter
LinkedIn
SOCIALICON
SOCIALICON

Disclaimer

The opinions and content expressed here are my own and not those of my employer.