Pages

Wednesday, June 13, 2012

A Generic Repository for the Entity Framework

Note: In this post I am going to be demonstrating techniques that depend on the Entity Framework 4.1 or higher.

Recently I’ve been using a simple Repository oriented architecture for my Entity Framework projects. Coupled with the new DbContext class and the entity code generation items added in the Entity Framework 4.1 release you can quickly scaffold up a data layer and business layer for your application.

To start, let’s define an interface for our repository:

public interface IRepository : IDisposable
{
    void Insert<E>(E entity) where E : class;

    void Update<E>(E entity) where E : class;

    void Delete<E>(E entity) where E : class;

    IQueryable<E> Select<E>() where E : class;

    E Select<E>(object key) where E : class;

    int SaveChanges();

    int SaveChanges(bool validateEntities);
}

This interface is pretty standard, defining all of the usual CRUD operations with the addition of the SaveChanges methods that can be mapped to Linq to SQL or EF data contexts. Next we will need to create the base repository class:

public abstract class BaseRepository : IRepository
{
    public abstract void Insert<E>(E entity) where E : class;

    public abstract void Update<E>(E entity) where E : class;

    public abstract void Delete<E>(E entity) where E : class;

    public int SaveChanges()
    {
        return SaveChanges(true);
    }

    public abstract int SaveChanges(bool validateEntities);

    public abstract IQueryable<E> Select<E>() where E : class;

    public abstract E Select<E>(object key) where E : class;

    public abstract void Dispose();
}

This BaseRepository class is abstract so that we can create child repositories for Linq to SQL, EF, and any other data provider we need. We will hide the explicit implementation from our callers using this class.

Now we have to create an generic repository implementation for our data layer, which in this case is an Entity Framework layer that uses the new DbContext object.

public class DbContextRepository<T> : BaseRepository where T : DbContext
{
    #region Fields

    private T _context;
    private Type _contextType;

    #endregion

    #region Properties

    public T Context
    {
        get
        {
            if (_context == null)
                _context = Activator.CreateInstance<T>();

            return _context;
        }
    }

    #endregion

    #region Methods

    public override void Insert<E>(E entity)
    {
        Context.Set<E>().Add(entity);
    }

    public override void Update<E>(E entity)
    {
        Context.Set<E>().Attach(entity);
        Context.Entry<E>(entity).State = EntityState.Modified;
    }

    public override void Delete<E>(E entity)
    {
        Context.Set<E>().Attach(entity);
        Context.Entry<E>(entity).State = EntityState.Deleted;
    }

    public override int SaveChanges(bool validateEntities)
    {
        if (!Context.Database.Connection.IsDatabaseOnline())
            throw new Exception("The database is currently not online.");

        Context.Configuration.ValidateOnSaveEnabled = validateEntities;
        return Context.SaveChanges();
    }

    public override IQueryable<E> Select<E>()
    {
        if (!Context.Database.Connection.IsDatabaseOnline())
            throw new Exception("The database is currently not online.");

        return Context.Set<E>();
    }

    public override E Select<E>(object key)
    {
        if (!Context.Database.Connection.IsDatabaseOnline())
            throw new Exception("The database is currently not online.");

        return Context.Set<E>().Find(key);
    }

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

    #endregion
}


This generic implementation is a little bit more complex, so let’s break it down.

First, this is a generic repository implementation for contexts that inherit from DbContext. We want to make this class generic so that we can use any data context that we may create, not just a specific context that we may "hardcode" into the class.

Every method in this class uses the our EF data context, so we need to expose it. We are going to add a private property called Context of type T. Our context object is instantiated using the Reflection Activator.CreateInstance method. The overload that we are using to instantiate our object requires a Generic Type parameter, so we add that to the method call, Activator.CreateInstance<T>().

Next we need to implement our BaseRepository abstract methods. We do this by adding the appropriate methods mapping them to the DbContext’s object Set collection operations.

Finally we implement IDisposable to satisfy the IDisposable interface.

So, from here we have the infrastructure to finally implement concrete repository classes. There are many approaches you can use, but I am going to outline one that I have used for ASP.Net based website projects.

Disclaimer: This implementation is heavily tied to WebForms and OOTB Microsoft Web Controls, so please do not think that this is the only way to proceed.

So, I know I just said that we can finally implement concrete repository classes, but I am going to throw one more generic abstract class into the mix for good luck. For me, the purpose of this class is to consolidate business logic that may be central to the technology you have decided to use in your application.

For instance, in this example, I am implementing methods that will map to ObjectDataSources that I am using in my presentation layer. So, when you see Select() methods with multiple overloads for paging, sorting, and filtering, or RowCount() methods, this has been done to accommodate ObjectDataSources that may use one of these repository objects as a datasource.

Again, if you were using MVC for your presentation layer, or WinForms or webservices, you may implement this class differently.

public abstract class GenericEntityRepository<T>
where T : class
{
    #region Properties

    public BaseRepository Repository
    {
        get
        {
            return Activator.CreateInstance(BaseRepositoryType) as BaseRepository;
        }
    }

    public abstract Type BaseRepositoryType { get; }

    #endregion

    #region Methods

    public virtual void Delete(T entity)
    {
        ExecuteQuery(query =>
        {
            query.Delete<T>(entity);
            query.SaveChanges();
        });
    }

    internal virtual void ExecuteQuery(Action<IRepository> query)
    {
        if (query == null)
            throw new ArgumentException("The query argument can not be null.");

        using (var queryRepository = Repository)
        {
            query(queryRepository);
        }
    }

    public virtual void Insert(T entity)
    {
        ExecuteQuery(query =>
        {
            query.Insert<T>(entity);
            query.SaveChanges();
        });
    }

    public virtual IList<T> Select()
    {
        List<T> results = new List<T>();

        ExecuteQuery(query =>
        {
            results = query.Select<T>().ToList();
        });

        return results;
    }

    public virtual IList<T> Select(int rowNumber, int pageSize)
    {
        // There is a bug in the EF stack that does not allow you to call the Skip()
        // function without calling the OrderBy() function.
        string defaultSort = StaticMethods.CreateSortExpression<T>(string.Empty, string.Empty);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().AsQueryable<T>().OrderBy(defaultSort).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(string sortByColumnName, int rowNumber, int pageSize)
    {
        if (string.IsNullOrEmpty(sortByColumnName))
            return Select(rowNumber, pageSize);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().OrderBy(sortByColumnName, false).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(Expression<func<bool>> where)
    {
        if (where == null)
            return Select();

        List<T> results = new List<T>();

        ExecuteQuery(query =>
        {
            results = query.Select<T>().Where<T>(where).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(Expression<func<bool>> where, int rowNumber, int pageSize)
    {
        if (where == null)
            return Select(rowNumber, pageSize);

        // There is a bug in the EF stack that does not allow you to call the Skip()
        // function without calling the OrderBy() function.
        string defaultSort = StaticMethods.CreateSortExpression<T>(string.Empty, string.Empty);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().Where<T>(where).AsQueryable<T>().OrderBy(defaultSort).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual IList<T> Select(Expression<func<bool>> where, string sortByColumnName, int rowNumber, int pageSize)
    {
        if (where == null)
            return Select(sortByColumnName, rowNumber, pageSize);

        if (string.IsNullOrEmpty(sortByColumnName))
            return Select(where, rowNumber, pageSize);

        List<T> results = null;

        ExecuteQuery(query =>
        {
            results = query.Select<T>().Where<T>(where).AsQueryable<T>().OrderBy(sortByColumnName, false).Skip(rowNumber).Take(pageSize).ToList();
        });

        return results;
    }

    public virtual T Select(object key)
    {
        if (key == null)
            throw new ArgumentNullException("key", "The key argument can not be null.");

        T entity = null;

        ExecuteQuery(query =>
        {
            entity = query.Select<T>(key);
        });

        return entity;
    }

    public virtual int RowCount()
    {
        int rowCount = -1;

        ExecuteQuery(query =>
        {
            rowCount = query.Select<T>().Count();
        });

        return rowCount;
    }

    public virtual int RowCount(Expression<func<bool>> where)
    {
        if (where == null)
            return RowCount();

        int rowCount = -1;

        ExecuteQuery(query =>
        {
            rowCount = query.Select<T>().Where<T>(where).Count();
        });

        return rowCount;
    }

    public virtual void Update(T entity)
    {
        ExecuteQuery(query =>
        {
            query.Update<T>(entity);
            query.SaveChanges();
        });
    }

    #endregion
}


The key to this class is the BaseRepositoryType abstract property. We will implement this property in concrete child classes. We will set the BaseRepository type to something like:

typeof(DbContextRepository<OrderDbContext>);

When called by the Activator.CreateInstance() method in getter of the Repository property, we will get a DbContextRepository object of Type OrderDbContext. Setting the BaseRepositoryType property is what allows us to switch Repository providers, and the specific contexts they may use.

We can then leverage our BaseRepository object in all of our business logic by encapsulating it in the ExecuteQuery() method. This method takes an Action delegate as a parameter and allows us to use that BaseRepository object by simply calling:

ExecuteQuery(query =>
{
    //Place repository calls here.
});

The query lambda syntax simplifies the need to declare and register a delegate, and ultimately leads to clean, readable code.

The final piece to this equation is the concrete implementation to our repository. But, this is also the great part about this model. After all of this coding, we can create repository objects for any Type and any DbContext that we may have defined in our data layer by creating a simple class that inherits from the GenericEntityRepository class, and implements one property.

public partial class OrderRepository : GenericEntityRepository<Order>
{
    public override Type BaseRepositoryType
    {
        get
        {
            return typeof(DbContextRepository<OrderDbContext>);
        }
    }
}


In this example, we have a DbContext type defined in our data layer named OrderDbContext, and a Type named Order. After implementing this class, we will have a OrderRepository object that we can use to Select, Insert, Update, and Delete Order objects from the OrderDbContext.

With this pattern in place, we can save ourselves some time and some typing by placing our concrete class implementation into a T4 template. When pointed at an Entity Framework EDMX file, the template will auto generate these concrete repository classes for all of the tables defined in the EDMX file.

I hope that you have found this pattern helpful, and I encourage you to send me feedback if you any ideas or improvements on this topic. Thanks!

No comments:

Post a Comment