Problem
I am totally confused while implementing Repository pattern with unit of work and Entity framework because I have seen tons of examples. Please suggest whether I am on the right track or not.
public interface IUnitOfWork: IDisposable
{
void Commit();
void Dispose(bool disposing);
}
Here, Medevenet
is EF DBContext
. For each repo I have property in Unit
or Work
classes.
public class UnitOfWork : IUnitOfWork
{
private MedevnetDBEntities context;
private PostRepository postRepository;
public UnitOfWork(string connectionString)
{
context = new MedevnetDBEntities(connectionString);
}
public PostRepository PostRepository
{
get
{
if (this.postRepository == null)
{
this.postRepository = new PostRepository(context);
}
return postRepository;
}
}
public void Commit()
{
context.SaveChanges();
}
private bool disposed = false;
private string p;
public virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
context.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
public interface IRepository<T> where T : class
{
void Insert(T entity);
void Delete(object id);
void Delete(T entityToDelete);
void Update(T entityToUpdate);
}
public class Repository<T> : IRepository<T> where T : class
{
protected MedevnetDBEntities context;
internal DbSet<T> dbSet;
public Repository(MedevnetDBEntities context)
{
this.context = context;
this.dbSet = context.Set<T>();
}
public virtual IEnumerable<T> GetWithRawSql(string query, params object[] parameters)
{
return dbSet.SqlQuery(query, parameters).ToList();
}
public virtual IEnumerable<T> Get(
Expression<Func<T, bool>> filter = null,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = dbSet;
if (filter != null)
{
query = query.Where(filter);
}
foreach (var includeProperty in includeProperties.Split
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(includeProperty);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual T GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(T entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
T entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(T entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(T entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Ninject dependency config:
private static void RegisterServices(IKernel kernel)
{
//kernel.Bind<MedevnetDBEntities>().ToSelf().InRequestScope();
kernel.Bind<System.Data.Entity.DbContext>().To<MedevnetDBEntities>().InRequestScope().WithConstructorArgument("connectionString", ApplicationDomainConfiguration.ConnectionString);
kernel.Bind<IUnitOfWork>().To<UnitOfWork>().WithConstructorArgument("connectionString", ApplicationDomainConfiguration.ConnectionString);
kernel.Bind<IPostRepository>().To<PostRepository>().WithConstructorArgument(new MedevnetDBEntities(ApplicationDomainConfiguration.ConnectionString));
//PostRepository
}
Solution
-
IUnitOfWork
-
as
IDisposable
is only relevant for an instance of a class, it shouldn’t be implemented by theIUnitOfWork
interface. This would only add an unnecessary constraint to the interface. -
Dispose(bool)
shouldn’t be there. This is a method which should only exist in the class and also should beprotected
. By using it in an interface it automatically will be public.
-
-
UnitOfWork
- dead code should be removed (here
private string p;
) - if you use
this
you should be consistent (see coonstructor andPostRepository
property)
- dead code should be removed (here
-
IRepository<T>
- the names of the input parameters aren’t consistent. I suggest changing
entityToDelete
andentityToUpdate
toentity
as the action which should happen is expressed in the method name and therefor shouldn’t be reflected in the parameter name.
- the names of the input parameters aren’t consistent. I suggest changing
-
Repository<T>.Get()
-
you could make your code more readable, by extracting the
Split()
out of the loop definition. -
you can remove the last else, as this is not needed at all.
public virtual IEnumerable<T> Get( Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "") { IQueryable<T> query = dbSet; if (filter != null) { query = query.Where(filter); } foreach (var includeProperty in includeProperties.Split (new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries)) { query = query.Include(includeProperty); } if (orderBy != null) { return orderBy(query).ToList(); } else { return query.ToList(); } }
will become
public virtual IEnumerable<T> Get( Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "") { IQueryable<T> query = dbSet; if (filter != null) { query = query.Where(filter); } var properties = includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (var includeProperty in properties) { query = query.Include(includeProperty); } if (orderBy != null) { return orderBy(query).ToList(); } return query.ToList(); }
-
Normally I do not recommend repository pattern and unit of work pattern with entity Framework. The reason is DBContext
itself is a Unit of Work and DBSet itself is a repository. So there is no point that implementing the same thing again if there isn’t any very specific reason.
Then how do we achieve re-usability?
The answer is you can use extension methods instated repository method. Let’s say you want to filter by FirstName
in several places in you business logic. Then you can add following extension method.
public static IEnumerable<Person> FirstNameEqualTo(this DbSet<Person> person , string firstName)
{
return person.Where(a=>a.FirstName=firstName).ToList();
}
Then can call this method
DbContext.People.FirstNameEqualTo("Milina Udara");
With this concept you can remove all the code you wrote.
It is ok to write code even without extensions for smaller queries like above. you can use extensions only for lager queries and queries that are used in many places. However, most of the time filters like above needed only in one place.
How do we use this for data saving ?
You may receive ViewModel form the request but end of the day you need a model object to save. So converting ViewModel to model or model to ViewModel is a different responsibility. For that you can use auto-mapper, Service layer, or Model/ViewModel extensions. Then save those object using entity framework.
If you have two or many database calls form a method or controller action you can add services layer.
How do we unit test this?
The main advantage was unit testing, but with entity framework 6 onward you can mock DbSet
If you check StudentRepository
every method other than DeleteStudent
has only one line of code.