vendredi 8 juillet 2016

I just spent a long time implementing a Unit Of Work pattern on top of Entity Framework using generic Repositories. What did I actually accomplish?

If you don't care about the code I wrote and just want to have a discussion about abstracting existing abstractions... skip to the last 3 paragraphs.

I was introduced to the idea of Repositories and the Unit Of Work via a Pluralsight lesson I was watching. I also stubmled upon Microsoft's own page detailing this process: http://ift.tt/1u56pfd

So I decided to try it out. I set out to write my own Unit Of Work class with generic Repositories on top of Entity Framework, making absolutely everything along the way utilize interfaces so I could inject my own mocks for testing.

First things first, for this exercise I chose to make a simple Blog application.

So I started with the DbContext. Gotta make sure I use an interface!

    public interface ISimpleBlogContext : IDisposable
{
    IDbSet<Blog> Blogs { get; }
    IDbSet<Post> Posts { get; }

    void SaveChanges();
    IDbSet<T> Set<T>() where T : class;
    DbEntityEntry Entry<T>(T entity) where T : class;
}

I'm sure everybody knows what the IDbSets are for, but the SaveChanges, Set, and Entry methods might look a bit out of place. Don't worry, we'll get to those later.

So now I hooked up my interface into an actual concrete DbContext :

    public class SimpleBlogContext : DbContext, ISimpleBlogContext
{
    public SimpleBlogContext() {
        Database.SetInitializer<SimpleBlogContext>(new DropCreateDatabaseAlways<SimpleBlogContext>());
    }

    public IDbSet<Blog> Blogs { get; set; }
    public IDbSet<Post> Posts { get; set; }

    public DbEntityEntry Entry<T>(T entity) where T : class
    {
        return Entry<T>(entity);
    }

    void ISimpleBlogContext.SaveChanges()
    {
        SaveChanges();
    }

    IDbSet<T> ISimpleBlogContext.Set<T>()
    {
        return Set<T>();
    }

The database initializer is just ensuring that for this test application the database is dropped and recreated each time I run the app. This was just an exercise and not a real app after all. You can see the SaveChanges, Set, and Entry methods implemented here, and they're all nothing more than wrappers for the DbContext methods of the same name.

So now on to the repositories...

I wasn't going to re-write virtually the same repository code for every entity I might add to my application (although in this case I'll only wind up using one repository), so I made a generic repository. Don't skip the interface!

    public interface IGenericRepository<T> : IDisposable
    where T : class 
{
    IEnumerable<T> GetAll();
    T GetById(object id);
    IEnumerable<T> GetByExpression(Expression<Func<T, bool>> expression);
    void Add(T entity);
    void Delete(T entity);
    void Update(T entity);
}

and the concrete version... (notice I'm using my ISimpleBlogContext here instead of a concrete DbContext class because I want to be able to test all the things)

    public class GenericRepository<T> : IGenericRepository<T>
    where T : class 
{

    public GenericRepository(ISimpleBlogContext context)
    {
        this.context = context;
    }

    private ISimpleBlogContext context;

    public void Dispose()
    {
        if (context != null)
        {
            context.Dispose();
            context = null;
        }
    }

    public void Add(T entity)
    {
        context.Set<T>().Add(entity);
    }

    public void Delete(T entity)
    {
        context.Set<T>().Remove(entity);
    }

    public IEnumerable<T> GetAll()
    {
        return context.Set<T>().ToList<T>();
    }

    public IEnumerable<T> GetByExpression(Expression<Func<T, bool>> expression)
    {
        return context.Set<T>().Where<T>(expression).ToList<T>();
    }

    public T GetById(object id)
    {
        return context.Set<T>().Find(id);
    }

    public void Update(T entity)
    {
        context.Entry(entity).State = EntityState.Modified;
    }
}

And now finally, the Unit Of Work class

    public class UnitOfWork : IDisposable
{

    public void Dispose()
    {
        if (context != null)
        {
            context.Dispose();
            context = null;
        }
    }
    public UnitOfWork()
    {
        context = new SimpleBlogContext();
    }

    public UnitOfWork(ISimpleBlogContext context)
    {
        this.context = context;
    }

    private ISimpleBlogContext context;

    public GenericRepository<TEntity> GetRepository<TEntity>() where TEntity : class
    {
        return new GenericRepository<TEntity>(context);
    }

    public void Save()
    {
        context.SaveChanges();
    }

}

I'm still allowing an ISimpleBlogContext to be passed in via the overloaded constructor, but the default constructor is where we finally get our concrete SimpleBlogContext DbContext from.

So now I just have to test all this. So I wrote a simple Console application which does nothing more than generate a couple of fake blogs with a couple of fake posts and save them using the Unit Of Work class. Then it loops the blogs and posts and prints them out so I can verify that they were actually saved to the database.

P.S. Jake is my dog in case you're wondering about the barking...

    class Program
{
    static void Main(string[] args)
    {
        UnitOfWork unitOfWork = new UnitOfWork();

        GenericRepository<Blog> blogRepository = unitOfWork.GetRepository<Blog>();

        Blog paulsBlog = new Blog()
        {
            Author = "Paul",
            Posts = new List<Post>()
            {
                new Post()
                {
                    Title = "My First Post",
                    Body = "This is my first post"
                },
                new Post()
                {
                    Title = "My Second Post",
                    Body = "This is my second post"
                }
            }
        };


        Blog jakesBlog = new Blog()
        {
            Author = "Jake",
            Posts = new List<Post>()
            {
                new Post()
                {
                    Title = "Dog thoughts",
                    Body = "Bark bark bark"
                },
                new Post()
                {
                    Title = "I like barking",
                    Body = "Bark bark bark"
                }
            }
        };


        blogRepository.Add(paulsBlog);
        blogRepository.Add(jakesBlog);
        unitOfWork.Save();

        List<Blog> blogs = blogRepository.GetAll() as List<Blog>;

        foreach (Blog blog in blogs)
        {
            System.Console.WriteLine("ID: {0}, Author: {1}\n", blog.Id, blog.Author);
            if (blog.Posts != null && blog.Posts.Count > 0)
            {
                foreach (Post post in blog.Posts)
                {
                    System.Console.WriteLine("Posted at: {0}, Title: {1}, Body: {2}", post.PostTime, post.Title, post.Body);

                }
            }
            else
            {
                System.Console.WriteLine("No posts");
            }
            System.Console.WriteLine("\n");
        }
    }
}

It works. Yay!

My question, however, is simply... what did I gain exactly by doing any of this?

Isn't DbContext already a Unit Of Work and DbSet is already a repository? It seems all I did was write a really elaborate wrapper for both of those things with no added functionality at all. You might say that it's more test-friendly since everything is using interfaces, but with mocking frameworks like Moq it's already possible to mock a DbSets and DbContexts. My repositories are generic so there is literally zero functionality that is specific to business logic. The Unit Of Work class is just a wrapper for DbContext and the generic Repositories are just wrappers for DbSet.

Can somebody explain to me why anybody would do this? I watched a ~4 hour Pluralsight lesson on this, plus went through all the trouble of actually doing it myself, and I still don't get it.

Aucun commentaire:

Enregistrer un commentaire