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 protected]");
//etc
return View();
}
}
new DBContext()
without disposing. \$\endgroup\$DBContext
in a generic way are useless. It would be nice to know why people keep doing it. \$\endgroup\$