We Do Tech, stuff.

Small business web and system solutions.

Free programs, software reviews, and tech, stuff.

Repository Pattern With Entity Framework and Generics

by: Dan Orlovsky
Posted On: 10/12/2017 6:36:18 AM

Here is a small example of how to create a reusable repository pattern using generics.


Some concepts have been harder for me to understand than others, and looking at code may help you understand what is being done, but not necessarily why. But sometimes, the why is clear as day and the what just looks like chaos.  I've been working with nopCommerce for some time, and I was always a fan of how they implemented their repository.  I've finally decided to write about it a little more in-depth for anyone else who may be struggling with the concept.

Repository Pattern

 Described here

As a summary, I would describe the wider impact of the repository pattern. It allows all of your code to use objects without having to know how the objects are persisted. All of the knowledge of persistence, including mapping from tables to objects, is safely contained in the repository.

So, in short, it allows us to keep all data-access logic in one location.  This has always been an attractive approach for me, but I was never able to effectively make it a generic repository that I can just attach any model to and save the changes...  until today!

Using a Base Entity

First, we need to find some commonality between all of our models that will populate the database.  In the simplest of cases, it will be an Id (it can also be some Date Information, and we can keep comparison logic within the base entity as well), so let's design the BaseEntity object:

public partial class BaseEntity
{
    public int Id { get; set; }
}

All of our Database Models will inherit this BaseEntity.  This commonality will allow our Generic Repository to be used by any Object that inherits this one.  So, let's build one of our database models for the example:

    public partial class Home : BaseEntity
    {
        public string BuyerNames { get; set; }

        public string Address { get; set; }

        public int LotNumber { get; set; }

        public string Notes { get; set; }

        public double PurchasePrice { get; set; }

        public double UpgradePrice { get; set; }

        public bool HasApproved { get; set; }

        public virtual List<SelectionCategory> SelectionCategories { get; set; }
    }

I pulled this from a live program I'm working on.  Luckily, the method we'll show you cares little about what Properties are in the model.

Notice the Home object is inheriting BaseEntity: that means the Id and whatever other properties we choose will be inherited.

The next step would be to create a DbSet from our Context:

    public class SuSDbContext : IdentityDbContext<ApplicationUser>
    {
        public SuSDbContext() : base(ProjectSettings.GetSuSConnection())
        { }

        public static SuSDbContext Create()
        {
            return new SuSDbContext();
        }

        public DbSet<Home> Homes { get; set; }
        public DbSet<SelectionCategory> SelectionCategories { get; set; }
        public DbSet<OptionsCategory> OptionsCategories { get; set; }
        public DbSet<Option> Options { get; set; }   
    }

You will want to replace: ProjectSettings.GetSuSConnection() with your own connection string, but other than that, it's as simple as a context as one can hope for.  There's a constructor, a static Create() method, and some DbSet properties.  Also, know all other objects we pass to the DbSet object inherit our BaseEntity object.

This is essentially all we need before we're ready to start throwing some data at the context; so first, let's design an interface that all Objects which perform CRUD operations on our data will follow.

 

Using a Generic Repository Interface

We layout CRUD methods in an interface and any Repository that will have access to our data will inherit this interface.

    public partial interface IRepository<T> where T : BaseEntity
    {
        T GetById(object id);
        
        void Add(T entity);

        void Add(ICollection<T> entities);

        void Update(T entity);

        void Update(ICollection<T> entities);

        void Remove(T entity);

        void Remove(ICollection<T> entities);

        IQueryable<T> DataTable { get; }
    }

First, we create the interface signature to require an object that IS or inherits BaseEntity.  That's the only requirement to use this interface.  And since all of our objects inherit BaseEntity, any of them will be able to access this interface.

But, instead of having each object access this directly - and rewriting these functions over and over again, let's create a generic repository object:

    // Entity Framework Repository Class
    // All Repositories will inherit the functionality of this class
    public partial class Repository<T> : IRepository<T> where T : BaseEntity
    {
        #region Fields
        // Connection to the context
        private readonly SuSDbContext _context;

        // Private table object
        private IDbSet<T> _entities;
        #endregion

        #region Properties
        /// <summary>
        /// Entities Object
        /// </summary>
        protected virtual IDbSet<T> Entities
        {
            get
            {
                if(_entities == null)
                    _entities = _context.Set<T>();

                return _entities;
            }
        }

        /// <summary>
        /// public DataTable
        /// </summary>
        public IQueryable<T> DataTable
        {
            get
            {
                return Entities;
            }
        }
        #endregion

        #region Constructor
        /// <summary>
        /// Repository()
        /// </summary>
        public Repository()
        {
            _context = SuSDbContext.Create();
        }
        #endregion


        #region Methods

        /// <summary>
        /// Builds an error string from the DbEntityValidationException object
        /// </summary>
        /// <param name="dbEx"></param>
        /// <returns></returns>
        protected string GetErrorText(DbEntityValidationException dbEx)
        {
            string errorMsg = string.Empty;
            foreach (var validationErrors in dbEx.EntityValidationErrors)
                foreach (var error in validationErrors.ValidationErrors)
                    errorMsg += String.Format("PropertyName: {0}, Error: {1}{2}", error.PropertyName, error.ErrorMessage, Environment.NewLine);

            return errorMsg;
        }

        /// <summary>
        /// Gets the error from DbEntityValidationException object and ensures nothing was changed in the database.
        /// </summary>
        /// <param name="dbEx"></param>
        /// <returns></returns>
        protected string GetErrorAndRollBackChanges(DbEntityValidationException dbEx)
        {
            var fullErrorText = GetErrorText(dbEx);
            foreach(var entry in dbEx.EntityValidationErrors.Select(error => error.Entry))
            {
                if (entry == null)
                    continue;
                entry.State = EntityState.Unchanged;
            }

            _context.SaveChanges();

            return fullErrorText;
        }

        /// <summary>
        /// Gets a single entity
        /// </summary>
        /// <param name="id"></param>
        /// <returns>T</returns>
        public virtual T GetById(object id)
        {
            // Convert to int to use SingleOrDefault - 100X faster than .Find()
            int intId = Convert.ToInt32(id);
            return Entities.SingleOrDefault(x => x.Id == intId);
        }

        /// <summary>
        /// Adds a single entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Add(T entity)
        {
            try
            {
                if (entity == null)
                    throw new ArgumentNullException(nameof(entity));

                Entities.Add(entity);
                _context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {
                throw new Exception(GetErrorAndRollBackChanges(dbEx), dbEx);
            }
        }

        /// <summary>
        /// Adds a collection of entities
        /// </summary>
        /// <param name="entities"></param>
        public virtual void Add(ICollection<T> entities)
        {
            try
            {
                if (entities == null)
                    throw new ArgumentNullException(nameof(entities));

                foreach(T entity in entities)
                    Entities.Add(entity);
                
                _context.SaveChanges();
            }
            catch(DbEntityValidationException dbEx)
            {
                throw new Exception(GetErrorAndRollBackChanges(dbEx), dbEx);
            }
        }

        /// <summary>
        /// Removes a single entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Remove(T entity)
        {
            try
            {
                if (entity == null)
                    throw new ArgumentNullException(nameof(entity));
                Entities.Remove(entity);
                _context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {
                throw new Exception(GetErrorAndRollBackChanges(dbEx), dbEx);
            }
        }

        /// <summary>
        /// Removes a collection of entities
        /// </summary>
        /// <param name="entities"></param>
        public virtual void Remove(ICollection<T> entities)
        {
            try
            {
                if (entities == null)
                    throw new ArgumentNullException(nameof(entities));
                foreach (T entity in entities)
                    Entities.Remove(entity);
                _context.SaveChanges();
            }
            catch(DbEntityValidationException dbEx)
            {
                throw new Exception(GetErrorAndRollBackChanges(dbEx), dbEx);
            }
        }

        /// <summary>
        /// Updates a single entity
        /// </summary>
        /// <param name="entity"></param>
        public virtual void Update(T entity)
        {
            try
            {
                if (entity == null)
                    throw new ArgumentNullException(nameof(entity));


                _context.Entry(entity).State = EntityState.Modified;
                _context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {
                throw new Exception(GetErrorAndRollBackChanges(dbEx), dbEx);
            }

        }

        /// <summary>
        /// Updates a collection of entities
        /// </summary>
        /// <param name="entities"></param>
        public virtual void Update(ICollection<T> entities)
        {
            try
            {
                if (entities == null)
                    throw new ArgumentNullException(nameof(entities));
                
                foreach (T entity in entities)
                    _context.Entry(entity).State = EntityState.Modified;

                _context.SaveChanges();
            }
            catch (DbEntityValidationException dbEx)
            {
                throw new Exception(GetErrorAndRollBackChanges(dbEx), dbEx);
            }
        }
        #endregion
    }

 

Notice, we're essentially passing the Generic one more step down the chain.  By reading through this code, you can tell we're performing basic CRUD operations on a generic entity.  Amazing!  

Creating the Repository

Technically, we can perform most operations with this Generic repository.  It would look something like:

Repository<Home> homeRepository = new Repository<Home>();
homeRepository.Add(model);

However, I find myself often needing to override some functionality, or needing to add model-specific items to the repository, so I usually create a specific repository for each model:

    public class HomeRepository : Repository<Home>
    {
        public override void Update(Home entity)
        {
            //.... DO ADDITIONAL LOGIC
            base.Update(entity);
        }
    }

Now, our HomeRepository has access to our CRUD methods, and we have the ability to create additional functionality specific to each model.

Wonderful!

 


Dan Orlovsky

Self-taught full-stack developer and Visual Studio junkie specializing in C#, ASP.NET (WebForms and MVC), HTML5, and CSS3.  I design custom content management solutions for small-businesses looking to take control of their website.  Each project is built with the technical aptitude of the user in mind.

Currently a junior Software Design Engineer for a data company with a focus in Angular and C#.

 


Comments

Ad Space