I'm using generic repository pattern with unit of work implementation for my project.
Recently I've come to an issue which I could not solve. When I try to update an entity's collection property (i.e: Add a new associated entity) and call update on my UoW (to delegate it to repository and obviously EF) it does not save to the database.
My generic repository:
public class GenericRepository<TEntity> where TEntity : class
{
internal MyDBContext context;
internal DbSet<TEntity> dbSet;
public GenericRepository(MyDBContext context)
{
this.context = context;
this.dbSet = context.Set<TEntity>();
}
internal virtual IQueryable<TEntity> BuildQuery(Expression<Func<TEntity,bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = dbSet.AsNoTracking();
foreach (var include in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(include);
}
if (filter != null)
query = query.Where(filter);
if (orderBy != null)
return orderBy(query);
return query;
}
public virtual IEnumerable<TEntity> Get(Expression<Func<TEntity, bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = BuildQuery(filter, orderBy, includeProperties);
return query.ToList();
}
public virtual void Update(TEntity entity)
{
dbSet.Attach(entity);
context.Entry<TEntity>(entity).State = EntityState.Modified;
}
}
My Unit of Work implementation
public class UnitOfWork : IDisposable
{
//code removed for clarity
public GenericRepository<CorporateServiceCategory> ServiceCategories
{
get
{
if(this.serviceCategoryRepository == null)
{
serviceCategoryRepository = new GenericRepository<CorporateServiceCategory>(context);
}
return serviceCategoryRepository;
}
}
public void Commit()
{
context.SaveChanges();
}
}
What I'm trying to do is:
using(var unit = new UnitOfwork())
{
//unit.Companies is a generic repository instance for Company entities.
var company = unit.Companies.Get(filter: f => f.Id == 1).SingleOrDefault();
company.ServiceCategories.Add(new ServiceCategory {Name = "Demo"});
unit.Companies.Update(company);
//This is a call to context.SaveChanges();
unit.Commit();
}
I expect this code to create a new Company -> ServiceCategory association and add a record to the database. When I do the same operation without Unit of Work but using DbContext itself, it works.
What am I doing wrong with my UoW & Generic Repository implementation?
Thanks to SOfanatic's comment, the problem has solved now.
I've updated my GenericRepository's BuildQuery method to reflect SOfanatic's suggestion and it worked.
Here is the updated BuildQuery method:
internal virtual IQueryable<TEntity> BuildQuery(Expression<Func<TEntity,bool>> filter = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> orderBy = null, string includeProperties = "")
{
IQueryable<TEntity> query = this.context.IsReadOnly ? dbSet.AsNoTracking() : dbSet;
foreach (var include in includeProperties.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
{
query = query.Include(include);
}
if (filter != null)
query = query.Where(filter);
if (orderBy != null)
return orderBy(query);
return query;
}
DbContext.IsReadOnly is a custom property I added to my DbContext implementation. That way if I want to load entities in a kind of "read-only" mode (only selects) I disable lazy loading, proxy generation and change tracking so it increases EF performance a bit.