3
\$\begingroup\$

This weekend I made an attempt at a generic repository pattern for entity framework in .NET after reading this blog post. I was wondering how it could be better. I specifically have a couple questions.

  • Can I be more "in line" with the typical repository pattern? I feel like I lost the typical pattern at some point and would love input from everyone.
  • Can anyone think of a way to do away with IEntity? Probably not, but it would be nice.

First, the Entities. Pretty basic, IEntity interface with just an Id and the models generated from EF.

namespace RepositoryPattern.Models
{
    public interface IEntity
    {
        int Id { get; }
    }
}

namespace RepositoryPattern.Models
{
    public partial class User : IEntity
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string Email { get; set; }
    }
}

Next is this power house, BaseRepository. This is a list of methods that will interact with the database. They're as generic as possible and make use of lambda expression predicates to query for specific entities. I can not stress enough that the empty try/catches do in fact log the information, but I didn't want to clutter this post with my error handling. But if you do use this, please handle your errors.

namespace RepositoryPattern.Repositories
{
    public abstract class BaseRepository<T> where T : class, IEntity
    {
        protected BaseRepository(DBContext repo)
        {
            Repo = repo;
        }
        protected DBContext Repo { get; set; }

        public virtual void Delete(params T[] items)
        {
            try
            {
                var dest = Repo.Set<T>();
                foreach (var item in items)
                {
                    var current = (from x in dest where x.Id == item.Id select x).FirstOrDefault();
                    if (current != null)
                    {
                        dest.Remove(current);
                    }
                }
                var rtnVal = Repo.SaveChanges();
            }
            catch (Exception e)
            {
                //append errors to log 
            }
        }

        public virtual IEnumerable<T> Read()
        {
            try
            {
                if (Repo == null) Repo = new DBContext();

                var context = Repo.Set<T>();

                return context.ToList();
            }
            catch (Exception e)
            {
                return new List<T>();
            }
        }

        public virtual IEnumerable<T> Read(Func<T, bool> @where)
        {
            try
            {
                if (Repo == null) Repo = new DBContext();

                var context = Repo.Set<T>();

                return context.Where(@where).ToList();
            }
            catch (Exception e)
            {
                return new List<T>();
            }
        }

        public virtual T ReadOne(Expression<Func<T, bool>> func)
        {
            try
            {
                if (Repo == null)
                    Repo = new DBContext();
                var context = Repo.Set<T>();
                return context.FirstOrDefault(func);
            }
            catch (Exception e)
            {
                return null;
            }
        }

        public virtual void Create(params T[] items)
        {
            try
            {
                var dest = Repo.Set<T>();
                dest.AddRange(items);
                var rtnVal = Repo.SaveChanges();
            }
            catch (Exception e)
            {
                //append errors to log 
            }
        }


        public virtual void Update(params T[] items)
        {
            try
            {
                var dest = Repo.Set<T>();
                foreach (var item in items)
                {
                    var current = (from x in dest where x.Id == item.Id select x).FirstOrDefault();
                    if (current == null)
                        dest.Add(item);
                    else
                        Repo.Entry(current).CurrentValues.SetValues(item);
                }
                var rtnVal = Repo.SaveChanges();
            }
            catch (Exception e)
            {
                //append errors to log 
            }
        }
    }
}

Next, the repository for the Entity itself. You can include custom business logic here that you can reuse elsewhere.

namespace RepositoryPattern.Repositories
{
    public class UserRepository : BaseRepository<User>
    {

        public UserRepository(DBContext repo) : base(repo)
        {
        }

        //Custom business logic here, can call from anywhere
        public bool UserNameExists(string name)
        {
            return Repo.User.Any(c => c.Name == name);
        }

    }
}

And finally a class that encapsulates all repositories that makes accessing them easier:

namespace RepositoryPattern.Repositories
{
    public class RepositorySet
    {
        public DBContext Repo;

        public RepositorySet()
        {
            Repo = new DBContext();
            InitializeRepositories();
        }

        public RepositorySet(DBContext repo)
        {
            Repo = repo;
            InitializeRepositories();
        }

        private void InitializeRepositories()
        {
            User = new UserRepository(Repo);
        }

        public UserRepository User { get; private set; }


    }
}

You use it as follows:

public class UserController : Controller
{
    private RepositorySet repo = new RepositorySet();

    public ActionResult Index()
    {
        var users = repo.Users.Read();
        var user = repo.Users.ReadOne(u => u.Email = "email@email.com");
        //etc
        return View();
    }
}
\$\endgroup\$
  • 3
    \$\begingroup\$ Ouch! So many new DBContext() without disposing. \$\endgroup\$ – t3chb0t Dec 12 '16 at 15:51
  • 4
    \$\begingroup\$ 99.999% of attempts of abstracting DBContext in a generic way are useless. It would be nice to know why people keep doing it. \$\endgroup\$ – Bruno Costa Dec 12 '16 at 15:53
  • 3
    \$\begingroup\$ I always wonder what's the advantage of wrapping a repository pattern which EF already is with another repository? Why don't you simply create a class that just provides you the necessary queries? You're done in few minutes. \$\endgroup\$ – t3chb0t Dec 12 '16 at 15:54
  • \$\begingroup\$ @BrunoCosta I guess because 99.999% of blogs and articles are wrong about the repository pattern. I've never seen any implementation of it not using EF. There's simply no one who knows how to implement it correctly and the Unit of Work. \$\endgroup\$ – t3chb0t Dec 12 '16 at 15:57
  • 3
    \$\begingroup\$ Usually I have a method for every query that i need to make. If the code of those methods happens to be exactly the same in some circumstances the code pays in DRYness and I don't really care about that. I do it this way from experience. I also used to have a generic CRUD interface that caused more problems than the issues it sovled \$\endgroup\$ – Bruno Costa Dec 12 '16 at 16:04

Your Answer

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

Browse other questions tagged or ask your own question.