I know having a Unit Of Work is having an abstraction on top of an abstraction (DbContext) and surely that is an anti-pattern, or at least is not necessary.
I have the following problem:
I have a generic IRepository like so:
public interface IGenericRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "");
TEntity GetByID(object id);
void Insert(TEntity entity);
void Delete(object id);
void Delete(TEntity entityToDelete);
void Update(TEntity entityToUpdate);
}
and this is the implementation of this interface:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal GymHelperContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(GymHelperContext context)
{
this.context = context;
dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> 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 TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
I have a proyect called Facade that instantiates a Mapper and a UnitOfWork, like so:
public class MuscleFacade
{
private readonly UnitOfWork _unitOfWork = new UnitOfWork();
private readonly MuscleMapping _muscleMapping = new MuscleMapping();
public MuscleFacade()
{
}
public IEnumerable<MuscleViewModel> GetAllMuscles()
{
var source = _unitOfWork.MuscleRepository
.Get()
.ToList();
var result = source.Select(x => _muscleMapping.MuscleToModel(x));
return result;
}
public GymViewModel GetGymViewModel()
{
GymViewModel gymViewModel = new GymViewModel
{
ListOfMuscles = GetAllMuscles().ToList()
};
return gymViewModel;
}
}
The MuscleFacade class it's what I inject on my controller with Autofac, I inject an IMuscleFacade in its constructor.
Now the thing is, my MuscleTypeViewModel have a list of MuscleViewModel these models are mapped with their Domain classes counterparts, and in this particular case a MuscleType have many Muscle (Eg: Arm have bicep, tricep, etc) so I put navigational properties on each of them, like so:
public class MuscleType : IEntity
{
public int Id { get; set; }
[StringLength(100)]
public string MuscleTypeName { get; set; }
public ICollection<Muscle> Muscles { get; set; }
}
public class Muscle : IEntity
{
public int Id { get; set; }
[StringLength(100)]
public string MuscleName { get; set; }
public int MuscleTypeId { get; set; }
public MuscleType MuscleType { get; set; }
}
Now let's look at GetAllMuscles method in the Facade again:
public IEnumerable<MuscleViewModel> GetAllMuscles()
{
var source = _unitOfWork.MuscleRepository
.Get()
.ToList();
var result = source.Select(x => _muscleMapping.MuscleToModel(x));
return result;
}
What if I want to Eager-Load MuscleType, how can I change the Get() in order to receive and Expression of Func instead of a string?
You can define a helper class that contains your include definitions:
abstract class IncludeDefinition<TEntity>
{
public abstract IQueryable<TEntity> Include(IQueryable<TEntity> entities);
}
class IncludeDefinition<TEntity, TProperty> : IncludeDefinition<TEntity>
{
public IncludeDefinition(Expression<Func<TEntity, TProperty>> includeEx)
{
_includeEx = includeEx;
}
private readonly Expression<Func<TEntity, TProperty>> _includeEx;
public override IQueryable<TEntity> Include(IQueryable<TEntity> entities)
{
return entities.Include(_includeEx);
}
}
Then use the IncludeDefinition in your Get method
public IEnumerable<Muscle> Get(params IncludeDefinition<Muscle>[] includes)
{
IQueryable<Muscle> muscles = ...;
foreach (var item in includes)
{
muscles = item.Include(muscles);
}
return muscles.ToList();
}
And call the method
_unitOfWork.MuscleRepository
.Get(new IncludeDefinition<Muscle, MuscleType>(m => m.MuscleType));
// Include as many as you wish
_unitOfWork.MuscleRepository
.Get(new IncludeDefinition<Muscle, MuscleType>(m => m.MuscleType),
new IncludeDefinition<Muscle, SomeOtherRelatedEntity>(m => m.SomeOtherProperty));
Edit here comes some way to "just include" instead of writing complicated syntax.
Create a new interface IQueryRepository that supports Get without explicit includes and Include, derive IGenericRepository from this interface:
public interface IQueryRepository<TEntity>
where TEntity : class
{
IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null);
IQueryRepository<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> referenceExpression);
}
public interface IGenericRepository<TEntity> : IQueryRepository<TEntity>
where TEntity : class
{
IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
params IncludeDefinition<TEntity>[] include);
// other methods like GetByID, Add, Update...
}
Update the GenericRepository definition - it uses the approach with IncludeDefinition that I initially described and it returns a GenericQueryRepositoryHelper when Include is called.
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
internal DbSet<TEntity> dbSet;
public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null)
{
return Get(filter, orderBy, new IncludeDefinition<TEntity>[0]);
}
public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, params IncludeDefinition<TEntity>[] includes)
{
IQueryable<TEntity> query = dbSet;
foreach (var item in includes)
{
query = item.Include(query);
}
if (filter != null)
{
query = query.Where(filter);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public IQueryRepository<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> referenceExpression)
{
return new GenericQueryRepositoryHelper<TEntity>(this, new IncludeDefinition<TEntity, TProperty>(referenceExpression));
}
// other methods like GetByID, Add, Update...
}
Implement the GenericQueryRepositoryHelper to store includes and apply them when Get is called
public class GenericQueryRepositoryHelper<TEntity> : IQueryRepository<TEntity>
where TEntity : class
{
private readonly IList<IncludeDefinition<TEntity>> _includeDefinitions;
private readonly IGenericRepository<TEntity> _repository;
internal GenericQueryRepositoryHelper(IGenericRepository<TEntity> repository, IncludeDefinition<TEntity> includeDefinition)
{
_repository = repository;
_includeDefinitions = new List<IncludeDefinition<TEntity>> { includeDefinition };
}
public IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null)
{
return _repository.Get(filter, orderBy, _includeDefinitions.ToArray());
}
public IQueryRepository<TEntity> Include<TProperty>(Expression<Func<TEntity, TProperty>> referenceExpression)
{
_includeDefinitions.Add(new IncludeDefinition<TEntity, TProperty>(referenceExpression));
return this;
}
}
Happy querying with includes:
var repo = new GenericRepository<Muscle>(...);
repo.Include(x => x.MuscleType)
.Include(x => x.MuscleType.Muscles)
.Get(x => x.MuscleName == "Test", x => x.OrderBy(m => m.MuscleName));
Related
I want to be able to use a generic service class of type T that will allow me to query the database dynamically. For example. Normally i would do something like this to delete a record
public void Delete(Post post)
{
this._context.Posts.Remove(post);
}
I want to be able to do this
public void Delete(T post)
{
this._context<T>.Remove(post);
}
I found an article here that sort of brushes over it, but if doesnt seem like a clean way to implement it. https://blog.magnusmontin.net/2013/05/30/generic-dal-using-entity-framework/
You need DbContext.Set
https://msdn.microsoft.com/en-us/library/gg679544(v=vs.113).aspx
Returns a non-generic DbSet instance for access to entities of the given type in the context and the underlying store
public void Delete<T>(T post)
where T : class
{
this._context.Set<T>.Remove(post);
}
For later on, you can also query based on:
this._context.Set<T>.AsQueryable().Where(predicate);
In this instance predicate would be a Expression<Func<T, bool>>
So you can have a generic query method:
public IEnumerable<T> Query<T>(Expression<Func<T, bool>> predicate)
where T : class
{
return this._context.Set<T>().AsQueryable().Where(predicate).ToList();
}
... but I'm digressing from the question slightly now!
You can use Generic repository pattern
public class Repository<TEntity> where TEntity : class
{
private dynamic _context;
private DbSet<TEntity> _dbSet;
protected DbContext Context
{
get
{
if (_context == null)
{
_context = DataContextFactory.GetDataContext();
}
return _context;
}
}
protected DbSet<TEntity> DBSet
{
get
{
if (_dbSet == null)
{
_dbSet = this.Context.Set<TEntity>();
}
return _dbSet;
}
}
public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
return this.GetQuery(predicate, orderExpression).AsEnumerable();
}
public virtual IQueryable<TEntity> GetQuery(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderExpression = null)
{
IQueryable<TEntity> qry = this.DBSet;
if (predicate != null)
qry = qry.Where(predicate);
if (orderExpression != null)
return orderExpression(qry);
return qry;
}
public virtual void Insert<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
dbSet.Add(entity);
}
public virtual void Insert(TEntity entity)
{
this.DBSet.Add(entity);
}
public virtual void Update<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
dbSet.Attach(entity);
this.Context.Entry(entity).State = EntityState.Modified;
}
public virtual void Update(TEntity entity)
{
this.Attach(entity);
this.Context.Entry(entity).State = EntityState.Modified;
}
public virtual void Delete<T>(T entity) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
if (this.Context.Entry(entity).State == EntityState.Detached)
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(TEntity entity)
{
if (this.Context.Entry(entity).State == EntityState.Detached)
this.Attach(entity);
this.DBSet.Remove(entity);
}
public virtual void Delete<T>(object[] id) where T : class
{
DbSet<T> dbSet = this.Context.Set<T>();
T entity = dbSet.Find(id);
dbSet.Attach(entity);
dbSet.Remove(entity);
}
public virtual void Delete(object id)
{
TEntity entity = this.DBSet.Find(id);
this.Delete(entity);
}
public virtual void Attach(TEntity entity)
{
if (this.Context.Entry(entity).State == EntityState.Detached)
this.DBSet.Attach(entity);
}
public virtual void SaveChanges()
{
this.Context.SaveChanges();
}
}
Usage:
public class Sample
{
public void AddNewPerson(Person newPerson)
{
var personRepo = new Repository<Person>();
personRepo.Insert(newPerson);
personRepo.SaveChanges();
}
public void DeletePerson(int personId)
{
var personRepo = new Repository<Person>();
Person person= personRepo.Find(p => p.Id == personId).SingleOrDefault();
personRepo.Delete(person);
}
}
I am building out unit tests for a small app we need to build.
I have implemented the Repository / Unit Of Work pattern. My manager classes implement the unit of work pattern.
For a given interface:
public interface IUserManager
{
List<ApplicationUser> GetUsers(Expression<Func<ApplicationUser, bool>> filter = null);
ApplicationUser GetUser(Expression<Func<ApplicationUser, bool>> filter);
ApplicationUser AddUser(string username, List<string> environmentIds, bool isAdmin = false);
void DeleteUser(string username);
ApplicationUser UpdateUser(string id, List<string> environmentIds, bool isAdmin = false);
IList<string> GetUserRoles(string id);
}
I have implemented
public class UserManager : IUserManager
{
#region private fields
private readonly IRepository<ApplicationUser> _userRepository;
private readonly IRepository<Application> _applicationRepository;
private readonly IRepository<Role> _roleRepository;
private readonly IActiveDirectoryManager _activeDirectoryManager;
#endregion
#region ctor
public UserManager(AppDbContext context, IActiveDirectoryManager activeDirectoryManager)
{
_activeDirectoryManager = activeDirectoryManager;
_userRepository = new Repository<ApplicationUser>(context);
_applicationRepository = new Repository<Application>(context);
_roleRepository = new Repository<Role>(context);
}
#endregion
#region IUserManager
public ApplicationUser AddUser(string username, List<string> applicationIds, bool isAdmin = false)
{
//Get the environments in the list of environmentIds
var applications = _applicationRepository.Get(e => applicationIds.Contains(e.Id)).ToList();
//Get the user from AD
var user = _activeDirectoryManager.GetUser(username);
//set the Id
user.Id = Guid.NewGuid().ToString();
//add the environments to the user
applications.ForEach(x =>
{
user.Applications.Add(x);
});
//if the user is an admin - retrieve the role and add it to the user
if (isAdmin)
{
var role = _roleRepository.Get(r => r.Name == "admin").FirstOrDefault();
if (role != null)
{
user.Roles.Add(role);
}
}
//insert and save
_userRepository.Insert(user);
_userRepository.Save();
//return the user
return user;
}
//removed for brevity
}
My unit test class:
[TestClass]
public class UserManagerUnitTest
{
private readonly Mock<IActiveDirectoryManager> _adManager;
private readonly IUserManager _userManager;
private readonly Mock<IRepository<Application>> _applicationRepository;
private readonly Mock<IRepository<ApplicationUser>> _userRepository;
private readonly Mock<IRepository<Role>> _roleRepository;
public UserManagerUnitTest()
{
var context = new Mock<AppDbContext>();
_adManager = new Mock<IActiveDirectoryManager>();
_applicationRepository = new Mock<IRepository<Application>>();
_userRepository = new Mock<IRepository<ApplicationUser>>();
_roleRepository = new Mock<IRepository<Role>>();
_userManager = new UserManager(context.Object, _adManager.Object);
}
[TestMethod]
[TestCategory("AddUser"), TestCategory("Unit")]
public void AddUser_ValidNonAdmin_UserIsAdded()
{
#region Arrange
string username = "testUser";
List<string> applicationIds = new List<string>() {"1", "2", "3"};
_applicationRepository.Setup(x => x.Get(It.IsAny<Expression<Func<Application, bool>>>(),
It.IsAny<Func<IQueryable<Application>, IOrderedQueryable<Application>>>(), It.IsAny<string>()))
.Returns(new List<Application>());
_adManager.Setup(x => x.GetUser(It.IsAny<string>())).Returns(new ApplicationUser());
#endregion
#region Act
var result = _userManager.AddUser(username, applicationIds, false);
#endregion
#region Assert
Assert.IsNotNull(result);
Assert.IsFalse(result.IsAdmin);
#endregion
}
}
And finally the repository interface:
public interface IRepository<TEntity> where TEntity : class
{
IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity> , IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "");
TEntity GetById(object id);
void Insert(TEntity entity);
void Delete(object id);
void Delete(TEntity entityToDelete);
void Update(TEntity entityToUpdate);
void Save();
}
And implementation:
public class Repository<TEntity> : IRepository<TEntity> where TEntity : class
{
private readonly AppDbContext _context;
internal DbSet<TEntity> DbSet;
public Repository(AppDbContext context)
{
_context = context;
DbSet = _context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = DbSet;
if (filter != null)
query = query.Where(filter);
foreach (var prop in includeProperties.Split(new char[] {','}, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(prop);
}
if (orderBy != null)
{
return orderBy(query).ToList();
}
else
{
return query.ToList();
}
}
public virtual TEntity GetById(object id)
{
return DbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
DbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = DbSet.Find(id);
Delete(entityToDelete);
}
public void Get(Expression<Func<Application, bool>> expression, Func<IQueryable<Application>> func, IOrderedQueryable<Application> orderedQueryable)
{
throw new NotImplementedException();
}
public virtual void Delete(TEntity entityToDelete)
{
if (_context.Entry(entityToDelete).State == EntityState.Detached)
{
DbSet.Attach(entityToDelete);
}
DbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
DbSet.Attach(entityToUpdate);
_context.Entry(entityToUpdate).State = EntityState.Modified;
}
public void Save()
{
_context.SaveChanges();
}
}
my problem is in the mock IRepository<Application>
_applicationRepository.Setup(x => x.Get(It.IsAny<Expression<Func<Application, bool>>>(),
It.IsAny<Func<IQueryable<Application>, IOrderedQueryable<Application>>>(), It.IsAny<string>()))
.Returns(new List<Application>());
For some reason - actual method is being used versus the overridden proxy from Moq. When the test executes - I get a null reference on the Get method of the repository - specifically on the query = DbSet:
public Repository(AppDbContext context)
{
_context = context;
DbSet = _context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = DbSet; **//null here because db should be** mocked
if (filter != null)
query = query.Where(filter);
I am trying to test just the UserManager implementation - not the repository implementation.
What would be the correct way to set this test up?
The issue is you are passing the AppDbContext in the constructor of UserManager, which makes it dependent on it. The class in turn is creating internal instances of the repositories, thus always using the concrete classes:
public UserManager(AppDbContext context, IActiveDirectoryManager activeDirectoryManager)
{
_activeDirectoryManager = activeDirectoryManager;
_userRepository = new Repository<ApplicationUser>(context);
_applicationRepository = new Repository<Application>(context);
_roleRepository = new Repository<Role>(context);
}
You should instead abstract out the creation of the repositories and modify the constructor so that it takes an instance based on the interfaces:
public UserManager(IRepository<ApplicationUser> userRepository, IRepository<Application> applicationRepository, IRepository<Role> roleRepository, IActiveDirectoryManager activeDirectoryManager)
{
_activeDirectoryManager = activeDirectoryManager;
_userRepository = userRepository;
_applicationRepository = applicationRepository;
_roleRepository = roleRepository;
}
This way you are able to abstract out the repositories so your mocks are used instead of the real classes.
I want to write a one class generic for all Repository
I have a class Entity (Product,price,.... )
and the Interface IRepository
public interface IRepository<T>
{
Dictionary<int, T> myProduct { get; set; }
IEnumerable<T> List { get; }
void Add(T entity);
void Delete(T entity,int key);
void Update(T entity,int key);
T FindById(int Id);
}
I can write my Repository for each business object like ProudductRepository, priceReporsitory,.....
but I need to implements as a generic something like this
public class Repository<E, C> : IRepository<E>
and I use this for all my business object not one foreach entity
You can do that as shown below.This is just an example.
Create a Generic Repository
namespace ContosoUniversity.DAL
{
public class GenericRepository<TEntity> where TEntity : class
{
internal SchoolContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(SchoolContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> 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 TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
}
Please read more about it on Implement a Generic Repository and a Unit of Work Class article.
More Articles for you :
Generic Repository and UnitofWork patterns in MVC
Generic Repository and Unit of Work Pattern
Instead of an interface, I would like my Repositories to inherit from an abstract class (Base Repository). I decided I wanted to use an abstract class because I won't be unit testing the repositories (I will unit test service layer instead) and i didn't want to copy over the same implementation to every repository class that inherits from this base repository.
For example:
public abstract class BaseRepository<T> where T : class
{
protected DbSet<T> dbSet;
public BaseRepository(DbContext dataContext)
{
dbSet = dataContext.Set<T>();
}
public void Insert(T entity)
{
dbSet.Add(entity);
}
public IEnumerable<T> SearchFor(Expression<Func<T, bool>> predicate)
{
return dbSet.Where(predicate);
}
public IEnumerable<T> GetAll()
{
return dbSet;
}
public T GetById(int id)
{
return dbSet.Find(id);
}
}
When I tried to create other repositories that inherit from this base repository, I get problems with the constructor. How do I pass the datacontext into these derived classes so that they can be used?
You need to pass the context in the constructor:
public class Repository<TEntity> where TEntity : class
{
internal MyEntities context;
internal DbSet<TEntity> dbSet;
public Repository(MyEntities context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
public virtual IEnumerable<TEntity> Get(
Expression<Func<TEntity, bool>> filter = null,
Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null,
string includeProperties = "")
{
IQueryable<TEntity> 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 TEntity GetByID(object id)
{
return dbSet.Find(id);
}
public virtual void Insert(TEntity entity)
{
dbSet.Add(entity);
}
public virtual void Delete(object id)
{
TEntity entityToDelete = dbSet.Find(id);
Delete(entityToDelete);
}
public virtual void Delete(TEntity entityToDelete)
{
if (context.Entry(entityToDelete).State == EntityState.Detached)
{
dbSet.Attach(entityToDelete);
}
dbSet.Remove(entityToDelete);
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
context.Entry(entityToUpdate).State = EntityState.Modified;
}
}
Use:
var repo = new Repository<MyClass>(context);
In a project, we use generic repository and generic manager so we don't need to rewrite every update / delete etc method in every repository / manager.
Here is how they looks :
public interface IBaseRep<T> : IDisposable where T : class, PrivateObject
{
DbSet<T> DatabaseSet { get; set; }
DbContext Dal { get; set; }
T Find(int? id);
T Find(Expression<Func<T, bool>> predicate);
ICollection<T> Select(Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "");
T Create(T obj);
T Update(T obj);
bool Delete(T obj);
bool Delete(int id);
bool Delete(Expression<Func<T, bool>> predicate);
IQueryable<T> SelectAsQuery(
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "");
}
public class BaseRep<T> : IBaseRep<T> where T : class, PrivateObject
{
public DbSet<T> DatabaseSet { get; set; }
public DbContext Dal { get; set; }
public EORTCBaseRep(DbContext dal)
{
this.Dal = dal;
this.DatabaseSet = Dal.Set<T>();
}
public virtual T Find(int? id)
{
return this.DatabaseSet.Find(id);
}
public virtual T Find(Expression<Func<T, bool>> predicate)
{
return Select(predicate).FirstOrDefault();
}
public virtual ICollection<T> Select(
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "")
{
return SelectAsQuery(predicate, orderBy, includeProperties).ToList();
}
public virtual IQueryable<T> SelectAsQuery(
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "")
{
IQueryable<T> query = this.DatabaseSet;
if (predicate != null)
query = query.Where(predicate);
foreach (var includeProperty in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
query = query.Include(includeProperty);
if (orderBy != null)
query = query.OrderBy(orderBy);
return query;
}
public virtual T Create(T obj)
{
this.Dal.Entry<T>(obj).State = EntityState.Added;
this.Dal.SaveChanges();
return obj;
}
public virtual T Update(T obj)
{
this.Dal.Entry<T>(obj).State = EntityState.Modified;
this.Dal.SaveChanges();
return obj;
}
public virtual bool Delete(T obj)
{
if (obj is ILogicallyDeletable)
{
this.Dal.Entry<T>(obj).State = EntityState.Modified;
(obj as ILogicallyDeletable).IsDeleted = true;
}
else
{
this.Dal.Entry<T>(obj).State = EntityState.Deleted;
}
return this.Dal.SaveChanges() == 1;
}
public virtual bool Delete(int id)
{
T obj = Find(id);
return Delete(obj);
}
public virtual bool Delete(Expression<Func<T, bool>> predicate)
{
foreach (T item in Select(predicate))
{
Delete(item);
}
return this.Dal.SaveChanges() == 1;
}
public virtual void Dispose()
{
this.Dal.Dispose();
}
}
Our managers looks like this :
public interface IBaseManager<T> : IDisposable where T : class, PrivateObject
{
T Find(int? id);
T Find(Expression<Func<T, bool>> predicate);
ICollection<T> Select(Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "");
T Create(T obj);
T Update(T obj);
bool Delete(T obj);
bool Delete(int id);
bool Delete(Expression<Func<T, bool>> predicate);
IQueryable<T> SelectAsQuery(
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "");
}
public class BaseManager<T> : IBaseManager<T> where T : class, PrivateObject
{
protected IBaseRep<T> Repository;
public virtual T Find(int? id)
{
return this.Repository.Find(id);
}
public virtual T Find(Expression<Func<T, bool>> predicate)
{
return this.Repository.Find(predicate);
}
public virtual ICollection<T> Select(
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "")
{
return this.Repository.Select(predicate, orderBy, includeProperties);
}
public virtual IQueryable<T> SelectAsQuery(
Expression<Func<T, bool>> predicate = null,
Expression<Func<T, string>> orderBy = null,
string includeProperties = "")
{
return this.Repository.SelectAsQuery(predicate, orderBy, includeProperties);
}
public virtual T Create(T obj)
{
return this.Repository.Create(obj);
}
public virtual T Update(T obj)
{
return this.Repository.Update(obj);
}
public virtual bool Delete(T obj)
{
return this.Repository.Delete(obj);
}
public virtual bool Delete(int id)
{
return this.Repository.Delete(id);
}
public virtual bool Delete(Expression<Func<T, bool>> predicate)
{
return this.Repository.Delete(predicate);
}
public virtual void Dispose()
{
if (this.Repository != null)
this.Repository.Dispose();
}
}
This works well.
But, we now need to use the same DB table for multiple entity type :
public abstract class AbstractSite : PrivateObject, IActivable, ILogicallyDeletable
{
public int Id { get; set; }
}
public class EthicCommittee : AbstractSite
{
public int Number { get; set; }
}
public class Site : AbstractSite
{
public string Name { get; set; }
}
This is how we use the generic managers :
public class AbstractSiteManager : BaseManager<AbstractSite>
{
public AbstractSiteManager (PrismaDAL prismaDAL = null)
{
this.Repository = new AbstractSiteRep(prismaDAL);
}
}
and how we use the generic repositories :
public class AbstractSiteRep : PrismaBaseRep<AbstractSite>
{
public AbstractSiteRep (PrismaDAL prismaDAL = null)
: base(prismaDAL)
{}
}
public class PrismaBaseRep<T> : BaseRep<T> where T : class, PrivateObject
{
public PrismaBaseRep(PrismaDAL prismaDAL = null) : base((prismaDAL == null) ? new PrismaDAL() : prismaDAL)
{ }
}
But now, we would like to use the concrete types and not the abstract type anymore (AbstractSite = abstract; Site = concrete, RecruitingInstitution = concrete...) without touching the generic repository / manager. So we would have X generic repository (where X : number of concrete types). All of them pointing to the same DB table.
This would allow us to avoid a cast and allow us to restrict which type we can manipulate using one manager / repository.
Do you, guys, have any idea how i could accomplish this ?
Resolved
My mistake.
This works fine as #Mike C said.
I just didn't know that EF was able to find the correct table if i was referencing a concrete object type instead of the abstract (in TPH).