8
\$\begingroup\$

I'm working on a generic repository using EF 6 code first and trying to following the Unit of Work pattern. Unity is being used to inject my repositories, and I'm trying to get everything supporting Async.

Here is my EF configuration + context:

public class CustomDbConfiguration : DbConfiguration
{
    public CustomDbConfiguration()
    {
        // this SQL generator just overrides FK and PK generation to remove .'s from constraint names
        SetMigrationSqlGenerator(SqlProviderServices.ProviderInvariantName, () => new CustomSqlGenerator());
    }
}

[DbConfigurationType(typeof(CustomDbConfiguration))]
public class EntityFrameworkContext : DbContext
{
    public EntityFrameworkContext() : base("EntityFrameworkContext")
    {
        Database.SetInitializer(new DropCreateDatabaseIfModelChanges<EntityFrameworkContext>());
    }

    public DbSet<Todo> MyClass { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

        modelBuilder.Properties<string>().Configure(x => x.HasMaxLength(256).IsUnicode(true));
    }
}

Here is my IUnitOfWork and its Implementation:

public interface IUnitOfWork : IDisposable
{
    void Commit();

    Task CommitAsync();
}

public class EntityFrameworkUnitOfWork : IUnitOfWork
{
    private readonly DbContext entityFrameworkContext;

    public EntityFrameworkUnitOfWork(EntityFrameworkContext entityFrameworkContext)
    {
        this.entityFrameworkContext = entityFrameworkContext;
    }

    internal DbSet<T> GetDbSet<T>() where T : class
    {
        return entityFrameworkContext.Set<T>();
    }

    public void Commit()
    {
        entityFrameworkContext.SaveChanges();
    }

    public async Task CommitAsync()
    {
        await entityFrameworkContext.SaveChangesAsync();
    }

    public void Dispose()
    {
        // should this automatically rollback or commit?
    }
}

And here is my IRepository with its implementation:

public interface IRepository<T> where T : class
{
    void Insert(T item);
    T Update(T item, int id);
    void Delete(T item);
    IQueryable<T> Select();
    T Get(int id);
    Task<T> GetAsync(int id);
    T Find(Expression<Func<T, bool>> match);
    Task<T> FindAsync(Expression<Func<T, bool>> match);
    ICollection<T> FindAll(Expression<Func<T, bool>> match);
    Task<ICollection<T>> FindAllAsync(Expression<Func<T, bool>> match);
    int Count();   
    Task<int> CountAsync();
}

public class EntityFrameworkRepository<T> : IRepository<T> where T : class
{
    private readonly DbSet<T> dbSet;

    private EntityFrameworkContext context;

    public EntityFrameworkRepository(EntityFrameworkContext entityFrameworkContext, IUnitOfWork unitOfWork)
    {
        var efUnitOfWork = unitOfWork as EntityFrameworkUnitOfWork;

        dbSet = efUnitOfWork.GetDbSet<T>();

        this.context = entityFrameworkContext;
    }

    public void Insert(T item)
    {
        dbSet.Add(item);
    }

    public T Update(T item, int id)
    {
        if (item == null)
            return null;

        T existing = dbSet.Find(id);

        if (existing != null)
        {
            context.Entry(existing).CurrentValues.SetValues(item);
        }
        return existing;
    }

    public void Delete(T item)
    {
        dbSet.Remove(item);
    }

    public IQueryable<T> Select()
    {
        return dbSet;
    }

    public T Get(int id)
    {
        return dbSet.Find(id);
    }

    public async Task<T> GetAsync(int id)
    {
        return await context.Set<T>().FindAsync(id);
    }

    public T Find(Expression<Func<T, bool>> match)
    {
        return context.Set<T>().SingleOrDefault(match);
    }

    public async Task<T> FindAsync(Expression<Func<T, bool>> match)
    {
        return await context.Set<T>().SingleOrDefaultAsync(match);
    }

    public ICollection<T> FindAll(Expression<Func<T, bool>> match)
    {
        return context.Set<T>().Where(match).ToList();
    }

    public async Task<ICollection<T>> FindAllAsync(Expression<Func<T, bool>> match)
    {
        return await context.Set<T>().Where(match).ToListAsync();
    }

    public int Count()
    {
        return dbSet.Count();
    }

    public Task<int> CountAsync()
    {
        return dbSet.CountAsync();
    }
}

Everything is wired up via Unity:

public static class Bootstrapper
{
    public static IUnityContainer Initialize()
    {
        var container = BuildUnityContainer();
        System.Web.Mvc.DependencyResolver.SetResolver(new UnityDependencyResolver(container));
        GlobalConfiguration.Configuration.DependencyResolver = new Unity.WebApi.UnityDependencyResolver(container);
        return container;
    }

    private static IUnityContainer BuildUnityContainer()
    {
        var container = new UnityContainer();

        container.RegisterType<EntityFrameworkContext>(new PerResolveLifetimeManager());
        container.RegisterType<IUnitOfWork, EntityFrameworkUnitOfWork>();
        container.RegisterType(typeof(IRepository<>), typeof(EntityFrameworkRepository<>)); 

        // register all interfaces and their implementations here
        container.RegisterType<ITodoService, TodoService>();

        return container;
    }
}

I have a few concerns, and was wondering what I could do to alleviate them?

  1. This unit test fails, but I can't figure out why. To my understanding, the PerResolveLifetimeManager will always inject the same EF context. Is there just a bug in my assert line?

    [Test]
    public void TwoResolutionsOfEFShouldBeTheSameObject()
    {
        var target = Bootstrapper.Initialize();
    
        //act
        var context1 = target.Resolve<EntityFrameworkContext>();
        var context2 = target.Resolve<EntityFrameworkContext>();
    
        // assert
        context1.Should().Be(context2);
    }
    
  2. I leaked Entity Framework into my domain services, because I wanted to use ToListAsync() off of my IQueryable<T>. Is this OK? How can I fix that problem? Should I expose IQueryable? If I don't expose IQueryable, how can I get a similar behavior (e.g., deferred execution w/ query building).

  3. In my unit of work, how should I handle rollbacks? Should dispose call save? Is there a way I can reset the context? Should I implement Rollback()?

  4. My EntityFrameworkRepository implementation of IRepository knows about both the DbSet<T> and the underlying context. Is this bad? I added in the context so that I could implement the Async methods.

  5. How do I go about unit testing this without writing an integration test?

\$\endgroup\$
  • 3
    \$\begingroup\$ You might want to ask about point 1 on Stack Overflow, and possibly even edit that part out. This site does not address specific programming issues, fixing code is off-topic here. \$\endgroup\$ – Mathieu Guindon Sep 8 '15 at 0:05
11
\$\begingroup\$

I leaked Entity Framework into my domain services, because [...]

Fail. The only reason you would ever want to wrap EF with your own repository+unit-of-work implementation, would be to make an interface between EF and your code, possibly to enable swapping EF for something else at one point or another. By leaking it, you defeat the entire purpose of the additional abstraction level, and are left with a tremendously over-engineered solution for something that should be simple.

Rewind.

Back in the days of .net 2.0 and ADO.NET SqlConnection and SqlCommand boilerplate, there was a need to abstract away all the bolts and nuts of database interaction. A clean solution was the repository pattern, and with a unit of work to encapsulate a transaction, code that needed to interact with a database was nicely shielded from the inner workings of ADO.NET - and the abstraction layer made it child's play to swap a MySQL backend for an SQL Server Express backend: none of the business-layer code needed to change!

Enter EF.

Entity Framework abstracts away the SqlConnection and SqlCommand boilerplate, and its DbContext cleanly encapsulates a SqlTransaction - nothing is committed until you call SaveChanges(). By changing pretty much nothing, one can swap a MySQL backend for an SQL Server Express backend: none of the data-access-layer code needs to change!

What does that tell us? Entity Framework is a unit-of-work/repository pattern, abstracting away a truckload of boilerplate.

By abstracting EF with a unit-of-work/repository pattern, you've abstracted a unit-of-work/repository with a... unit-of-work/repository, and one that's very much more limited and, no offense, clumsy.

IQueryable<T> Select();

That member, all by itself, makes every other one moot. Exposing IQueryable<T> defers the query execution to the calling business layer code, which may not even be iterating the results and pass them to the view - in which case execution is deferred to the view. Even better: if the view contains code that filters the results, that filter may end up executing on the database backend. And if the view or business code uses functions that would be valid with Linq-to-Object, but that the EF provider doesn't support or can't translate into valid SQL, you've got a bug that you didn't need (well, who needs a bug eh?)...


In my unit of work, how should I handle rollbacks?

You don't. Client (business) code disposes the DbContext without calling SaveChanges. That's all.

How do I go about unit testing this without writing an integration test?

You mock IDbSet<T> and the interface your DbContext is implementing. Simple as that.


The idea behind abstracting the database stuff, is that you need to be able to test/run your business code without actually hitting the database. I use something like this:

public interface IUnitOfWork
{
    void Commit();
    IDbSet<T> Set<T>();
}

The IDbSet<T> can be mocked (you Setup the return value of Set<T>()), and by coding against this IUnitOfWork, you have an abstraction you can use to mock your DbContext:

public class MyContext : DbContext, IUnitOfWork
{
    public void Commit()
    {
        SaveChanges();
    }

    public IDbSet<Foobar> Foobars { get; set; }
    // ...
}

The "repositories" become an implementation detail, they're just for EF to pick up: your code is working against an IDbSet<TEntity> - does that not look just about exactly like a generic repository?

So does this mean EF should be tightly coupled to my domain layer [...]?

If you're coding against IUnitOfWork, you have a loose enough coupling to test everything you need to test, without hitting a database. Yes, you're using an interface (IDbSet<T>) that is essentially some EntityFrameworkRepository<TEntity>... but to swap for a NHibernateRepository<TEntity> you'd have to reimplement all the seam-level code anyway, leaked or not; the business-layer code only ever needs to see/use an IEnumerable<TEntity>, and the TEntity in question. By reusing your entity classes, and only ever exposing a group of them as an IEnumerable<TEntity>, you pretty much shield your business code from even noticing that it's no longer Entity Framework providing them with this TEntity.

\$\endgroup\$
  • \$\begingroup\$ This is great feedback, and I really appreciate it. Of the major projects I've worked on with EF, we always end up introducing something additional like Dapper/ADO.NET for more intricate operations (bulk inserts, etc)... I guess that's why I've always broken out a seperate IRepo. You're right though... why abstract away from EF when it's already abstracting SQL for you? So does this mean EF should be tightly coupled to my domain layer, or should I remove IQueryable from my IRepository? \$\endgroup\$ – Dylan McCurry Sep 8 '15 at 1:22
  • \$\begingroup\$ The actual DbContext should remain an implementation detail: you want your business code to know of some IUnitOfWork, I've edited my answer. \$\endgroup\$ – Mathieu Guindon Sep 8 '15 at 2:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.