ASP.NET MVC Data Layer: Access your Data Layer Through Unique Requests
Accessing your data layer is kind of a big deal while coding. This particular technique makes your data available anywhere you can access a controller context.
After the last post of maximizing your controllers, I'm sure that some of you are asking about that pesky repository line that I had in my code.
How can I call my data layer from inside that ViewModelBuilder? What if I have a number of repositories to call?
Fair enough! Let's address that issue!
Repository pattern
When you build enterprise class application, it's all about the layers.
First, we need to talk about our most basic pattern for data access: The repository pattern.
If you've worked with ASP.NET MVC for a while, you probably have used the repository pattern numerous times (Skip ahead if you already know about the repository pattern).
In the Patterns of Enterprise Application Architecture (aff link) by Martin Fowler, the repository pattern is defined as follows:
The Repository pattern is a mediator between the domain and data mapping layers using a collection-like interface for access domain objects.
For every table in my database, I have a repository class to grab the specific data using domain-related methods in the repository. I cheat and use Entity Framework for my ORM (Object-Relational Mapping). So if you swap out your SQL Server database for Oracle, you would rewrite the repository classes responsible for retrieving the data from SQL Server and adjust the class to work with Oracle. Remember DRY!
Here is an example of a simple Faq repository based on my previous post.
IRepository.cs
using System; using System.Linq; using System.Linq.Expressions; namespace ThinController.Repository { public interface IRepository<TEntity> where TEntity : class { IQueryable<TEntity> GetAll(); IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate); int Count(Expression<Func<TEntity, bool>> predicate); int Add(TEntity entity); int SaveChanges(); int Delete(TEntity entity); TEntity First(Expression<Func<TEntity, bool>> predicate); void Dispose(); } }
Repository.cs
using System; using System.Data.Entity; using System.Data.Entity.Core.Objects; using System.Data.Entity.Infrastructure; using System.Linq; using System.Linq.Expressions; namespace ThinController.Repository { public class Repository<TEntity> : IRepository<TEntity> where TEntity : class { protected DbContext dbContext; protected ObjectSet<TEntity> _objectSet; public Repository(DbContext context) { dbContext = context; _objectSet = ((IObjectContextAdapter)context).ObjectContext.CreateObjectSet<TEntity>(); } public virtual IQueryable<TEntity> GetAll() { return dbContext.Set<TEntity>(); } public virtual IQueryable<TEntity> Find(Expression<Func<TEntity, bool>> predicate) { return dbContext.Set<TEntity>().AsNoTracking().Where(predicate); } public virtual int Count(Expression<Func<TEntity, bool>> predicate) { return dbContext.Set<TEntity>().Count(predicate); } public int Add(TEntity entity) { if (entity == null) throw new ArgumentNullException("entity"); dbContext.Set<TEntity>().Add(entity); return dbContext.SaveChanges(); } public int SaveChanges() { return dbContext.SaveChanges(); } public int Delete(TEntity entity) { if (entity == null) throw new ArgumentNullException("Entity Issue. It''s null."); dbContext.Entry(entity).State = System.Data.Entity.EntityState.Deleted; return dbContext.SaveChanges(); } public TEntity First(Expression<Func<TEntity, bool>> predicate) { return dbContext.Set<TEntity>().FirstOrDefault(predicate); } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (!disposing) return; if (dbContext == null) return; dbContext.Dispose(); dbContext = null; } } }
FaqRepository.cs
using System.Collections.Generic; using System.Data.Entity; using ThinController.Models; namespace ThinController.Repository { public class FaqRepository: Repository<Faq>, IFaqRepository { // FaqEntities is what I called this DbContext. public FaqRepository() : this(new FaqEntities()) { } public FaqRepository(DbContext context) : base(context) { } public IEnumerable<Faq> GetAll() { return base.GetAll(); } } }
IFaqRepository.cs
using ThinController.Models; namespace ThinController.Repository { public interface IFaqRepository: IRepository<Faq> { } }
This is the first part of your data layer.
Unit of Work pattern
As your repositories classes grow, you need a way to manage all of those repositories in one manager class. This is where the Unit Of Work pattern assists to manage all of those repositories (Skip ahead if you already know about the Unit Of Work pattern).
The Unit of Work pattern is also found in the Patterns of Enterprise Application Architecture (aff. link). Martin Fowler defines the Unit of Work below:
The Unit of Work Pattern maintains a list of objects affected by a business transaction and coordinates the writing out of changes and the resolution of concurrency problems.
If you use Entity Framework without a Unit of Work, you run a huge risk of receiving ObjectState errors because of using different DbContexts with different objects. When you create your Unit Of Work, you pass in a single DbContext and the repositories use that DbContext throughout the life of the Unit Of Work. It's easier to access your repository classes through a unit of work while coding your controllers.
Here is an sample Unit of Work for your repository/ies
using System.Data.Entity; namespace ThinController.Repository { public class UnitOfWork { protected DbContext _context; private static FaqRepository _faqRepository; protected UnitOfWork(DbContext context) { _context = context; } public virtual int Save() { return _context.SaveChanges(); } public FaqRepository FaqRepository { get { return _faqRepository ?? (_faqRepository = new FaqRepository(_context)); } } } }
Are we there yet?
I know I'm covering a lot about design patterns, but these patterns are necessary in the long run to make your application more maintainable, so stay with me, we're almost there.
I just wanted to make sure everyone understood the underlying patterns that make this work.
The last part that makes this work is a simple extension method attached to a Controller Context.
public static T GetUnitOfWork<T>(this ControllerContext webContext) where T: AbstractUnitOfWork { var objectContextKey = String.Format("{0}-{1}", typeof(T), webContext.GetHashCode().ToString("x")); if (webContext.HttpContext.Items.Contains(objectContextKey)) return webContext.HttpContext.Items[objectContextKey] as T; // Old way... // var type = Activator.CreateInstance<T>(); // New DI way. T type; using (IKernel kernel = new StandardKernel()) { type = kernel.Get<T>(); } webContext.HttpContext.Items.Add(objectContextKey, type); return webContext.HttpContext.Items[objectContextKey] as T; }
Cool, huh?
If you are still scratching your head wondering what I just introduced, let me explain.
First, we set up a unique key for this entry in the HttpContext. Here we use the type and the Request HashCode in hex.
var objectContextKey = String.Format("{0}-{1}", typeof(T), webContext.GetHashCode().ToString("x"));
Next, we check to see if that key is in the HttpContext items. If it is, return the Unit of Work.
if (webContext.HttpContext.Items.Contains(objectContextKey)) return webContext.HttpContext.Items[objectContextKey] as T;
Since it wasn't found, we need to create a new instance of the Unit of Work. As you can see, you can use the old way of doing things with an Activator or you can use Ninject (as I did in this example) or your own DI Framework.
// Old way... // var type = Activator.CreateInstance<T>(); // New DI way. T type; using (IKernel kernel = new StandardKernel()) { type = kernel.Get<T>(); }
Finally, we add our newly created Unit Of Work to the HttpContext and return it.
webContext.HttpContext.Items.Add(objectContextKey, type); return webContext.HttpContext.Items[objectContextKey] as T;
It's all about the layers.
Phew! We've covered a lot, but now that we've built a Data Access layer, we can easily access our data anywhere in our application, even our ViewModelBuilders.
using ThinController.Classes; using ThinController.Controllers; using ThinController.Interfaces; using ThinController.Models; using ThinController.Repository; namespace ThinController.ViewModelBuilder { public class FaqViewModelBuilder : IViewModelBuilder<FaqController, FaqViewModel> { public FaqViewModel Build(FaqController controller, FaqViewModel viewModel) { viewModel.PageTitle = "FAQ List"; viewModel.MetaDescription = "This is the Frequently Asked Questions Page."; viewModel.MetaKeywords = "FAQ, New Questions, Old Questions, FAQ List"; // old way... // var repository = new FaqRepository(); // New Way var unitOfWork = controller.ControllerContext.GetUnitOfWork<UnitOfWork>(); viewModel.FaqList = unitOfWork.FaqRepository.GetAll(); return viewModel; } } }
It definitely makes everything a little more accessible to you throughout your application.
Conclusion
It is definitely easier to take your data access layer and whittle it down to use a single-access entry point so it's more "straw-like" (small, one-location call to data) as opposed to "tunnel-like" (multiple calls scattered throughout your application all over the place).
If you make your code more straw-like, it's easier to shift your data source from one location to another. Think of the "shifting of train-tracks."
Does this approach make sense? Post your comments below.