Implementing Unit of Work Repository Pattern and Entity Framework Database First Design

Without getting into the discussion of the advantages/disadvantages of this implementation, I would like to straight away get into the implementation pattern that I have learned from working in a company in Kathmandu.
If you are interested in the discussion whether this is a better approach of implementation as compared to some other “XYZ” approach, feel free to surf the web. I use this implementation in almost all of my projects and am quite comfortable with it.
To begin with, first have your database ready. Have your tables sorted out as per your requirement. To demonstrate a basic sample, here I have one table called “SurveyBuilderHelper” which I am using for my next big project (:D for real). So this is how my table looks:
Fig 1: Sample Table
 
Now, in our visual studio, create a new project class library project. In this project, add a new item. This item is going to be “ADO.NET Entity Data Model”, which will serve as our database entities context. So, create a new .edmx file entity model which you will be generating by providing proper server credentials and pointing to the database that you have just designed. Also, provide a proper name for the entity connection string to be stored in your app.config file. In my case, I have named it as “SurveyBuilderHelperEntities”.
 
 
Now, add a new class to this project and call it “DB.cs”. This class will derive from your entities. It will look something like this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Helper.DataLayer
{
    public class DB : SurveyBuilderHelperEntities
    {      
        public DB() : base() { }      
    }
}
 
Next, we will implement the repository pattern. Add the following interface and class to your project: IRepository and Repository.
 
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace Helper.DataLayer
{
    public interface IRepository : IDisposable
        where T : class
    {
        ///
        /// Gets all objects from database
        ///
        ///
        IQueryable All();
        ///
        /// Gets objects from database by filter.
        ///
        /// Specified a filter
        ///
        IQueryable Filter(Expression<Func<T, bool>> predicate);
        ///
        /// Gets objects from database with filting and paging.
        ///
        ///
        /// Specified a filter
        /// Returns the total records count of the filter.
        /// Specified the page index.
        /// Specified the page size
        ///
        IQueryable Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50);
        ///
        /// Gets the object(s) is exists in database by specified filter.
        ///
        /// Specified the filter expression
        ///
        bool Contains(Expression<Func<T, bool>> predicate);
        ///
        /// Find object by keys.
        ///
        /// Specified the search keys.
        ///
        T Find(params object[] keys);
        ///
        /// Find object by specified expression.
        ///
        ///
        ///
        T Find(Expression<Func<T, bool>> predicate);
        ///
        /// Create a new object to database.
        ///
        /// Specified a new object to create.
        ///
        T Create(T t);
        ///
        /// Delete the object from database.
        ///
        /// Specified a existing object to delete.
        int Delete(T t);
        ///
        /// Delete objects from database by specified filter expression.
        ///
        ///
        ///
        int Delete(Expression<Func<T, bool>> predicate);
        ///
        /// Update object changes and save to database.
        ///
        /// Specified the object to save.
        ///
        int Update(T t);
        void Refresh(T entity);
        ///
        /// Get the total objects count.
        ///
        int Count { get; }
    }
}
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading.Tasks;
namespace Helper.DataLayer
{
    public class Repository : IRepository
        where TObject : class
    {
        private bool shareContext = false;
        public Repository()
        {
            Context = new DB();
        }
        public Repository(DB context)
        {
            Context = context;
            shareContext = true;
        }
        protected DB Context = null;
        protected DbSet DbSet
        {
            get
            {
                return Context.Set();
            }
        }
        public void Dispose()
        {
            if (shareContext && (Context != null))
                Context.Dispose();
        }
        public virtual IQueryable All()
        {
            return DbSet.AsQueryable();
        }
        public virtual IQueryable Filter(Expression<Func<TObject, bool>> predicate)
        {
            return DbSet.Where(predicate).AsQueryable();
        }
        public virtual IQueryable Filter(Expression<Func<TObject, bool>> filter, out int total, int index = 0, int size = 50)
        {
            int skipCount = index * size;
            var _resetSet = filter != null ? DbSet.Where(filter).AsQueryable() : DbSet.AsQueryable();
            _resetSet = skipCount == 0 ? _resetSet.Take(size) : _resetSet.Skip(skipCount).Take(size);
            total = _resetSet.Count();
            return _resetSet.AsQueryable();
        }
        public bool Contains(Expression<Func<TObject, bool>> predicate)
        {
            return DbSet.Count(predicate) > 0;
        }
        public virtual TObject Find(params object[] keys)
        {
            return DbSet.Find(keys);
        }
        public virtual TObject Find(Expression<Func<TObject, bool>> predicate)
        {
            return DbSet.FirstOrDefault(predicate);
        }
        public virtual TObject Create(TObject TObject)
        {
            var newEntry = DbSet.Add(TObject);
            if (!shareContext)
                Context.SaveChanges();
            return newEntry;
        }
        public virtual int Count
        {
            get
            {
                return DbSet.Count();
            }
        }
        public virtual int Delete(TObject TObject)
        {
            DbSet.Remove(TObject);
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }
        public virtual int Update(TObject TObject)
        {
            var entry = Context.Entry(TObject);
            DbSet.Attach(TObject);
            entry.State = EntityState.Modified;
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }
        public virtual int Delete(Expression<Func<TObject, bool>> predicate)
        {
            var objects = Filter(predicate);
            foreach (var obj in objects)
                DbSet.Remove(obj);
            if (!shareContext)
                return Context.SaveChanges();
            return 0;
        }
        public void Refresh(TObject entity)
        {
            Context.Entry(entity).Reload();
        }
    }
}
 
Basically what this repository pattern does is that it exposes all the methods that will be used to access our repository.  Now, for every table objects that we have in our database, we have to create a separate repository class inheriting from the above repository. Since, I have only one table right now, I will add a repository for that table by creating the following class file:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Helper.DataLayer
{
    public interface IQuestionEntryRepository: IRepository<QuestionEntry>
    {
    }
    public class QuestionEntryRepository : Repository<QuestionEntry>, IQuestionEntryRepository
    {
        public QuestionEntryRepository(DB context)
            : base(context)
        { }
    }
}
For other tables, you would have similar repositories with their own s.
Next, we implement the unit of work pattern. For this, we will create two interfaces: IUnitOfWork and IManageUnitOfWork.  The IUnitOfWork will inherit from IDisposable and the IManageUnitOfWork will inherit from IUnitOfWork.
 
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Helper.DataLayer
{
    public interface IUnitOfWork : IDisposable
    {
        int SaveChanges();
    }
    public interface IManageUnitOfWork : IUnitOfWork
    {
       
    }
}
Now, for every repositories we have, we will include a property of that type in our IManageUnitOfWork interface. So, since I have only one table right now, it looks like this for me:
public interface IManageUnitOfWork : IUnitOfWork
    {
        IQuestionEntryRepository QuestionEntries { get;}       
    }
 
 
But when I have my database ready with all other tables, it could look like this:
public interface IManageUnitOfWork : IUnitOfWork
    {
        IParticipantInfoRepository Participants { get; }
        IQuestionRepository Questions { get; }
        IQuestionEntryRepository QuestionEntries { get; }
        ISurveyHeaderRepository SureveyHeaders { get; }
        ISurveySectionRepository SurveySections { get; }
        IAnswerRepository Answers { get; }
        IInputTyperepository InputTypes { get; }
        ISessionTableRepository SessionTables { get; }
        IErrorLogRepository ErrorLogs { get; }
        ISkippedQuestionRepository SkippedQuestions { get; }
        IHouseholdPhotoRepository HouseholdPhotos { get; }
        IMultiTextAnswerRepository MultiTextAnswers { get; }
        IMultiSelectAnswerRepository MultiSelectAnswers { get; }
    }
 
 
What we are doing here is creating a centralized access to all our repositories. All of these repositories can be accessed easily from an implementation of our IManageUnitOfWork interface. Finally now, we will create this implementation and call it ManageUnitOfWork.cs.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Helper.DataLayer
{
    public class ManageUnitOfWork: IManageUnitOfWork
    {
        #regionFields
        private DB dbContext;
        private IQuestionEntryRepository questionEntries;
       
        #endregion
        #regionConstructor
        public ManageUnitOfWork(DB context)
        {
            dbContext = context;
        }
        #endregion
        #regionProperty
        public IQuestionEntryRepository QuestionEntries
        {
            get
            {
                if (questionEntries == null)
                    questionEntries = new QuestionEntryRepository(dbContext);
                return questionEntries;
            }
        }
        #endregion
        #regionUtility
        public int SaveChanges()
        {
            return dbContext.SaveChanges();
        }
        public void Dispose()
        {
            //if (categories != null)
            //    categories.Dispose();       
            if (dbContext != null)
                dbContext.Dispose();
            GC.SuppressFinalize(this);
        }
        #endregion
    }
}
Again, for every repositories, you will have a field and property value set up similar to this one.
This ends the setting up of the Unit of Work, Entity Framework repository pattern. This repository can now be accessed from your other projects in the following way:
using Helper.DataLayer;
using Helper.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Helper.Services.QuestionEntryService
{
    public class QuestionEntryService: IQuestionEntryService
    {
        ManageUnitOfWork context;
        DB db;
        public QuestionEntryService(DB _db)
        {
            db = _db;
            context = new ManageUnitOfWork(db);           
        }
        public int Create(QuestionEntryModel model)
        {
            QuestionEntry questionEntry = new QuestionEntry
            {
                EntryID = model.EntryID,
                SectionHeader = model.SectionHeader,
                SectionNumber = model.SectionNumber,
                QuestionText = model.QuestionText,
                OptionGroup = model.QuestionText,
                InputType = model.InputType,
                HasBranches = model.HasBranches,
                FurtherDesc = model.FurtherDesc
            };
            context.QuestionEntries.Create(questionEntry);
            context.SaveChanges();
            return questionEntry.EntryID;
        }
    }
}
That’s all!!
Thank you for taking time to read through all of this implementation tutorial. I hope it was helpful.
Tags: