Skip to main content
Code Review

Return to Question

replaced http://codereview.stackexchange.com/ with https://codereview.stackexchange.com/
Source Link

I am about to >explode< :) due to amount of reading on this subject... My head hurts, and I need some honest opinions... There is a similar question/review similar question/review that I noticed, but I believe my approach is bit different so I wanted to ask.

I am about to >explode< :) due to amount of reading on this subject... My head hurts, and I need some honest opinions... There is a similar question/review that I noticed, but I believe my approach is bit different so I wanted to ask.

I am about to >explode< :) due to amount of reading on this subject... My head hurts, and I need some honest opinions... There is a similar question/review that I noticed, but I believe my approach is bit different so I wanted to ask.

Question Protected by syb0rg
Tweeted twitter.com/#!/StackCodeReview/status/202718913002344449
Source Link
zam6ak
  • 811
  • 1
  • 8
  • 6

EF Code First with Repository, UnitOfWork and DbContextFactory

I am about to >explode< :) due to amount of reading on this subject... My head hurts, and I need some honest opinions... There is a similar question/review that I noticed, but I believe my approach is bit different so I wanted to ask.

We are embarking on a really big project that will use ASP.NET MVC 4 with Web API and would like to use Entity Framework Code First. Before I post code for review, please consider following

  • My project will be big (possibly 100+ domain entities)
  • There needs to SOLID approach to architecture
  • By now, I understand that Entity Framework DbContext is (entirely or to some extent) implementing Unit of Work and Repository patterns, so I could skip all this ;)
  • I am really concerned using EF injected directly into Controller constructors (testability, separation of concerns, etc, etc)
  • I am also aware that there is more than one way to implement UoW and Repository patterns.
  • I am not worried or wanting to build abstractions that will allow me to "swap" ORM (e.g. swap Entity Framework for NHiberante or such).

My Approach

  • Repositories are generic and have a base class that implement much of the standard logic.
  • Repositories need DbContext via constructor (which is provided by UnitOfWork)
  • UnitOfWork is responsible for managing access to all repositories and insuring context is shared among them.
  • UnitOfWork is disposable, Repositories are not...
  • In order to "hide" DbContext, UnitOfWork is constructed using IDbContextFactory.

Questions

  • This seems to work for me, and the advantage I see is that every Controller just needs UoW injected which is nice. Some Controllers need 2-3 repositories in addition to domain services so this makes things nice...I think...
  • Over time, UoW will grow with repositories (there could be 65+ aggregate roots each having a repo). Any ides on how to better manage this? Should I somehow inject Repositories instead od new()-ing them up in the UnitOfWork? I'd love to be able to create a IoC module (Autofac is my poison) to wire up all repos (somehow)
  • Is use of IDbContextFactory an overkill or should I just inject DbContext to constructor of UnitOfWork instead? Right now, my web app has no direct dependency on Entity Framework it only depends n DAL (which in turn depends on EF). On the other hand DbContextFactory new()es up MyAppDbContext and is not handled by IoC
  • Does anyone notices any other "code smell" ?
  • Some of the questions are in code NOTEs to make them more relevant...

Ok here is the code with 2 repositories and sample use (all namespaces are omitted for brevity sake)

IDbContextFactory and DbContextFactory

/// <summary>
/// Creates instance of specific DbContext
/// </summary>
public interface IDbContextFactory //: IDisposable //NOTE: Since UnitOfWork is disposable I am not sure if context factory has to be also...
{
 DbContext GetDbContext();
}
public class DbContextFactory : IDbContextFactory
{
 private readonly DbContext _context;
 public DbContextFactory()
 {
 // the context is new()ed up instead of being injected to avoid direct dependency on EF
 // not sure if this is good approach...but it removes direct dependency on EF from web tier
 _context = new MyAppDbContext(); 
 }
 public DbContext GetDbContext()
 {
 return _context;
 }
 // see comment in IDbContextFactory inteface...
 //public void Dispose()
 //{
 // if (_context != null)
 // {
 // _context.Dispose();
 // GC.SuppressFinalize(this);
 // }
 //}
}

IRepository, Repository, and 2 specific Repositories wiht extra interfaces (Vehicle and Inventory)

public interface IRepository<T> where T : class
{
 /// <summary>
 /// Get the total objects count.
 /// </summary>
 int Count { get; }
 /// <summary>
 /// Gets all objects from database
 /// </summary>
 IQueryable<T> All();
 /// <summary>
 /// Gets object by primary key.
 /// </summary>
 /// <param name="id"> primary key </param>
 /// <returns> </returns>
 T GetById(object id);
 /// <summary>
 /// Gets objects via optional filter, sort order, and includes
 /// </summary>
 /// <param name="filter"> </param>
 /// <param name="orderBy"> </param>
 /// <param name="includeProperties"> </param>
 /// <returns> </returns>
 IQueryable<T> Get(Expression<Func<T, bool>> filter = null, Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null, string includeProperties = "");
 /// <summary>
 /// Gets objects from database by filter.
 /// </summary>
 /// <param name="predicate"> Specified a filter </param>
 IQueryable<T> Filter(Expression<Func<T, bool>> predicate);
 /// <summary>
 /// Gets objects from database with filting and paging.
 /// </summary>
 /// <param name="filter"> Specified a filter </param>
 /// <param name="total"> Returns the total records count of the filter. </param>
 /// <param name="index"> Specified the page index. </param>
 /// <param name="size"> Specified the page size </param>
 IQueryable<T> Filter(Expression<Func<T, bool>> filter, out int total, int index = 0, int size = 50);
 /// <summary>
 /// Gets the object(s) is exists in database by specified filter.
 /// </summary>
 /// <param name="predicate"> Specified the filter expression </param>
 bool Contains(Expression<Func<T, bool>> predicate);
 /// <summary>
 /// Find object by keys.
 /// </summary>
 /// <param name="keys"> Specified the search keys. </param>
 T Find(params object[] keys);
 /// <summary>
 /// Find object by specified expression.
 /// </summary>
 /// <param name="predicate"> </param>
 T Find(Expression<Func<T, bool>> predicate);
 /// <summary>
 /// Create a new object to database.
 /// </summary>
 /// <param name="entity"> Specified a new object to create. </param>
 T Create(T entity);
 /// <summary>
 /// Deletes the object by primary key
 /// </summary>
 /// <param name="id"> </param>
 void Delete(object id);
 /// <summary>
 /// Delete the object from database.
 /// </summary>
 /// <param name="entity"> Specified a existing object to delete. </param>
 void Delete(T entity);
 /// <summary>
 /// Delete objects from database by specified filter expression.
 /// </summary>
 /// <param name="predicate"> </param>
 void Delete(Expression<Func<T, bool>> predicate);
 /// <summary>
 /// Update object changes and save to database.
 /// </summary>
 /// <param name="entity"> Specified the object to save. </param>
 void Update(T entity);
}
public class Repository<T> : IRepository<T> where T : class
{
 protected readonly DbContext _dbContext;
 protected readonly DbSet<T> _dbSet;
 public Repository(DbContext dbContext)
 {
 _dbContext = dbContext;
 _dbSet = _dbContext.Set<T>();
 }
 public virtual int Count
 {
 get { return _dbSet.Count(); }
 }
 public virtual IQueryable<T> All()
 {
 return _dbSet.AsQueryable();
 }
 public virtual T GetById(object id)
 {
 return _dbSet.Find(id);
 }
 public virtual IQueryable<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);
 }
 if (!String.IsNullOrWhiteSpace(includeProperties))
 {
 foreach (var includeProperty in includeProperties.Split(new[] {','}, StringSplitOptions.RemoveEmptyEntries))
 {
 query = query.Include(includeProperty);
 }
 }
 if (orderBy != null)
 {
 return orderBy(query).AsQueryable();
 }
 else
 {
 return query.AsQueryable();
 }
 }
 public virtual IQueryable<T> Filter(Expression<Func<T, bool>> predicate)
 {
 return _dbSet.Where(predicate).AsQueryable();
 }
 public virtual IQueryable<T> Filter(Expression<Func<T, 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<T, bool>> predicate)
 {
 return _dbSet.Count(predicate) > 0;
 }
 public virtual T Find(params object[] keys)
 {
 return _dbSet.Find(keys);
 }
 public virtual T Find(Expression<Func<T, bool>> predicate)
 {
 return _dbSet.FirstOrDefault(predicate);
 }
 public virtual T Create(T entity)
 {
 var newEntry = _dbSet.Add(entity);
 return newEntry;
 }
 public virtual void Delete(object id)
 {
 var entityToDelete = _dbSet.Find(id);
 Delete(entityToDelete);
 }
 public virtual void Delete(T entity)
 {
 if (_dbContext.Entry(entity).State == EntityState.Detached)
 {
 _dbSet.Attach(entity);
 }
 _dbSet.Remove(entity);
 }
 public virtual void Delete(Expression<Func<T, bool>> predicate)
 {
 var entitiesToDelete = Filter(predicate);
 foreach (var entity in entitiesToDelete)
 {
 if (_dbContext.Entry(entity).State == EntityState.Detached)
 {
 _dbSet.Attach(entity);
 }
 _dbSet.Remove(entity);
 }
 }
 public virtual void Update(T entity)
 {
 var entry = _dbContext.Entry(entity);
 _dbSet.Attach(entity);
 entry.State = EntityState.Modified;
 }
} 
public class VehicleRepository : Repository<Vehicle>, IVehicleRepository
{
 public VehicleRepository(DbContext dbContext) : base(dbContext)
 {
 }
}
public interface IVehicleRepository : IRepository<Vehicle>
{
 //RFU
} 
public interface IInventoryRepository : IRepository<InventoryItem>
{
 IList<InventoryItem> GetByVehicleId(string vehicleId); // NOTE: InventoryItem.VehicleId != InventoryItem.Id
}
public class InventoryItemRepository : Repository<InventoryItem>, IInventoryItemRepository
{
 public InventoryItemRepository(DbContext dbContext) : base(dbContext)
 {
 }
 public IList<InventoryItem> GetByVehicleId(string vehicleId)
 {
 return Filter(vii => vii.Vehicle.Id == vehicleId).ToList();
 }
} 

IUnitOfWork, UnitOfWork

public interface IUnitOfWork : IDisposable
{
 InventoryItemRepository InventoryItemRepository { get; }
 VehicleRepository VehicleRepository { get; }
 void Save();
}
public class UnitOfWork : IUnitOfWork
{
 private readonly DbContext _dbContext;
 private bool _disposed;
 private InventoryItemRepository _inventoryItemRepository;
 private VehicleRepository _vehicleRepository;
 /// <summary>
 /// NOTE: repository getters instantiate repositories as needed (lazily)...
 /// i wish I knew of IoC "way" of wiring up repository getters...
 /// </summary>
 /// <param name="dbContextFactory"></param>
 public UnitOfWork(IDbContextFactory dbContextFactory)
 {
 _dbContext = dbContextFactory.GetDbContext();
 }
 public void Save()
 {
 if (_dbContext.GetValidationErrors().Any())
 {
 // TODO: move validation errors into domain level exception and then throw it instead of EF related one
 }
 _dbContext.SaveChanges();
 }
 public InventoryItemRepository InventoryItemRepository
 {
 get { return _inventoryItemRepository ?? (_inventoryItemRepository = new InventoryItemRepository(_dbContext)); }
 }
 public VehicleRepository VehicleRepository
 {
 get { return _vehicleRepository ?? (_vehicleRepository = new VehicleRepository(_dbContext)); }
 }
 public void Dispose()
 {
 Dispose(true);
 GC.SuppressFinalize(this);
 }
 protected virtual void Dispose(bool disposing)
 {
 if (!_disposed)
 {
 if (disposing)
 {
 _dbContext.Dispose();
 }
 }
 _disposed = true;
 }
}

Sample Usage in ASP.NET MVC4 + Web API

Global.asax.cs (Application_Start)

// relevant registration
 builder.RegisterType<UnitOfWork>().As<IUnitOfWork>()
 .WithParameter("dbContextFactory", new DbContextFactory())
 .InstancePerHttpRequest()
 .InstancePerApiRequest();

InventoryController

public class InventoryController : ApiController
{
 private readonly InventoryItemMapper _mapper; // NOTE: maps viewModel to domain entities and vice versa using ValueInjector
 private readonly IUnitOfWork _unitOfWork;
 public InventoryController(IUnitOfWork unitOfWork)
 {
 _unitOfWork = unitOfWork; // UoW (with all repos) is injected and ready for use...
 _mapper = new VehicleInventoryItemMapper(); //TODO: this will be injected also...
 }
 public IEnumerable<InventoryViewModel> Get()
 {
 var inventoryItems = _unitOfWork.InventoryItemRepository.All().ToList();
 var inventory = _mapper.MapToModel(inventoryItems);
 return inventory;
 
 }
}
lang-cs

AltStyle によって変換されたページ (->オリジナル) /