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\$DBContextin a generic way are useless. It would be nice to know why people keep doing it. \$\endgroup\$