Reputation: 3813
In my ASP.NET MVC 4 project I have carefully followed the design principles in this example of implementing Repository and Unit Of Work.
I am particularly interested in this remark on the article, written after showing the Generic Repository:
This generic repository will handle typical CRUD requirements. When a particular entity type has special requirements, such as more complex filtering or ordering, you can create a derived class that has additional methods for that type.
Since my app meets that particular case, I tried to do this. I made a GenericRepository just like the one in the article, and a SharedContext class (SharedContext is exactly the UnitOfWork class in the article, but this name makes more sense to me)
GenericRepository:
using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Entity;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
namespace BusinessLogicLayer.Repositories
{
public class GenericRepository<T> where T: class
{
internal DbSet<T> _dbSet;
internal DBConnection _context;
#region constructors
public GenericRepository(SharedContext ctx)
{
_context = ctx.Context;
}
public GenericRepository(DBConnection context)
{
_context = context;
_dbSet = context.Set<T>();
}
#endregion
}
}
SharedContext (Unit of work) class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace BusinessLogicLayer.Repositories
{
public class SharedContext : IDisposable
{
#region repositories
private GenericRepository<Entities.Customer> _customerRepository;
/// <summary>
/// access the Customer entity
/// </summary>
public GenericRepository<Entities.Customer> CustomerRepository
{
get
{
if (_customerRepository == null)
_customerRepository = new GenericRepository<Entities.Customer>(_context);
return _customerRepository;
}
}
#endregion
#region context management
private Entities.DBConnection _context = new Entities.DBConnection();
internal Entities.DBConnection Context { get { return _context; } }
//other methods - save, delete
#endregion
}
}
Now here is the problem: note how I exposed the Context property above - I have doubts that this is actually intended, I feel like I'm breaking the pattern by doing it. I like a lot the idea that everything is controlled through the repository in the same context and all, but I need some further methods which are not offered by the generic repository - so I made a separate CustomerRepository:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
namespace BusinessLogicLayer.Repositories
{
public class CustomerRepository: GenericRepository<Entities.Customer>
{
public CustomerRepository(SharedContext ctx)
: base(ctx)
{ }
public decimal GetCustomerBonusByFrequency()
{
//
}
}
}
...which in my controller class, I use like this:
private SharedContext ctx = new SharedContext();
public PartialViewResult CustomerBonus()
{
CustomerRepository cRepo = new CustomerRepository(ctx);
var bonus = cRepo.GetCustomerBonusByFrequency();
return PartialView(bonus);
}
So my 2 questions are:
List item
is this the intended way of adding extra functionality, that was mentioned in the article? Im referring to the class CustomRepository, which feels like a breaking of pattern to me, since there already was a way to get the Customer through GenericRepository(that I am not using anymore), plus I'm exposing the context.
If this is bad, then how should I do it correctly? all i need is to have the extra methods for my Customer entity, but in a way that respects the pattern.
Thx,
edit:
I needed to expose the Context property in SharedContext, because if I delete that constructor in GenericRepository (that takes a SharedContext) and the :base(ctx) in CustomerRepository class, I get this:
BusinessLogicLayer.DBModel.Customer does not contain a constructor that takes 0 arguments
Upvotes: 1
Views: 842
Reputation: 983
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Data.Entity;
using System.Collections.Generic;
using System.Data;
namespace Demo.DAL
{
public class Repository<TObject>
where TObject : class
{
protected DB Context;
protected DB Context = null;
private bool shareContext = false;
public Repository()
{
Context = new DB();
}
public Repository(DB context)
{
Context = context;
shareContext = true;
}
protected DbSet<TObject> DbSet
{
get
{
return Context.Set<TObject>();
}
}
public void Dispose()
{
if (shareContext && (Context != null))
Context.Dispose();
}
public virtual IQueryable<TObject> All()
{
return DbSet.AsQueryable();
}
public virtual IQueryable<TObject>
Filter(Expression<Func<TObject, bool>> predicate)
{
return DbSet.Where(predicate).AsQueryable<TObject>();
}
public virtual IQueryable<TObject> Filter(Expression<Func<TObject, bool>> filter,
out int total, int index = 0, int size = 50)
{
int skipCount = index * size;
var _resetSet = filter != null ? DbSet.Where(filter).AsQueryable() :
DbSet.AsQueryable();
_resetSet = skipCount == 0 ? _resetSet.Take(size) :
_resetSet.Skip(skipCount).Take(size);
total = _resetSet.Count();
return _resetSet.AsQueryable();
}
public bool Contains(Expression<Func<TObject, bool>> predicate)
{
return DbSet.Count(predicate) > 0;
}
public virtual TObject Find(params object[] keys)
{
return DbSet.Find(keys);
}
public virtual TObject Find(Expression<Func<TObject, bool>> predicate)
{
return DbSet.FirstOrDefault(predicate);
}
public virtual TObject Create(TObject TObject)
{
var newEntry = DbSet.Add(TObject);
if (!shareContext)
Context.SaveChanges();
return newEntry;
}
public virtual int Count
{
get
{
return DbSet.Count();
}
}
public virtual int Delete(TObject TObject)
{
DbSet.Remove(TObject);
if (!shareContext)
return Context.SaveChanges();
return 0;
}
public virtual int Update(TObject TObject)
{
var entry = Context.Entry(TObject);
DbSet.Attach(TObject);
entry.State = EntityState.Modified;
if (!shareContext)
return Context.SaveChanges();
return 0;
}
public virtual int Delete(Expression<Func<TObject, bool>> predicate)
{
var objects = Filter(predicate);
foreach (var obj in objects)
DbSet.Remove(obj);
if (!shareContext)
return Context.SaveChanges();
return 0;
}
}
}
Upvotes: 1
Reputation: 12596
Creating a specialized customer repository is the correct thing to do. I would suggest that your SharedContext
class should be updated such that the CustomerRepository
property returns an instance of the CustomerRepository
class, and not of GenericRepository<Cutomer>
.
This way, when you access the CustomerRepository
property from the SharedContext
, you will have the methods of the generic repository available as well as the specialized methods of your CustomerRepository
class.
Upvotes: 1