Archive for October 15th, 2010
Generic Entity Framework Repository with Eager Loading
In this post am going to explain how we can write Generic repository for Entity framework. The implementation is based on the Repository pattern. Inspired by one of the blog by hibernatingrhinos
Below code explains the Interface I used for creating the repository
using System; using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; namespace Generic.Repository.Repository { public interface IRepository { IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class; IList<TEntity> GetAll<TEntity>() where TEntity : class; IList<TEntity> Find<TEntity>(Expression<Func<TEntity, bool>> criteria) where TEntity : class; void Add<TEntity>(TEntity entity) where TEntity : class; void Attach<TEntity>(TEntity entity) where TEntity : class; void SaveChanges(); bool EnableEagerLoading { get; set; } } }
I only included only limited functionality to this Interface. You can extend the interface to add more functionality. Let’s see the implementation
using System; using System.Collections.Generic; using System.Data.Objects; using System.Linq; using System.Reflection; namespace Generic.Repository.Repository { public class GenericRepository:IRepository { string _connectionString = string.Empty; public GenericRepository(string connection) { _connectionString = connection; } #region Private methods private GenericContext _context = null; private GenericContext Context { get { if (_context == null) { _context = GenericContext.GetContext(_connectionString); _context.ContextOptions.LazyLoadingEnabled = false; } return _context; } } private IList<TEntity> LoadNavigationFields<TEntity>(IList<TEntity> entities) where TEntity : class { foreach (TEntity entity in entities) { PerformEagerLoading<TEntity>(entity, this.Context); } return entities; } private TEntity LoadNavigationFields<TEntity>(TEntity entity) where TEntity : class { PerformEagerLoading<TEntity>(entity, this.Context); return entity; } private void PerformEagerLoading<TEntity>(TEntity entity, ObjectContext context) where TEntity : class { PropertyInfo[] properties = typeof(TEntity).GetProperties(); foreach (PropertyInfo property in properties) { object[] keys = property.GetCustomAttributes(typeof(NavigationFieldAttribute), true); if (keys.Length > 0) { context.LoadProperty(entity, property.Name); } } } #endregion #region Generic Repository methods public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class { return this.Context.CreateGenericObjectSet<TEntity>(); } public IList<TEntity> GetAll<TEntity>() where TEntity : class { IList<TEntity> entities = this.GetQuery<TEntity>().AsEnumerable<TEntity>().ToList(); if (this._enableEagerLoading == false) { return entities; } else { return this.LoadNavigationFields<TEntity>(entities); } } public IList<TEntity> Find<TEntity>(System.Linq.Expressions.Expression<Func<TEntity, bool>> criteria) where TEntity : class { IList<TEntity> entities = this.GetQuery<TEntity>().Where(criteria).ToList(); if (this._enableEagerLoading == false) { return entities; } else { return this.LoadNavigationFields<TEntity>(entities); } } public void Add<TEntity>(TEntity entity) where TEntity : class { this.Context.CreateGenericObjectSet<TEntity>().AddObject(entity); } public void Attach<TEntity>(TEntity entity) where TEntity : class { this.Context.CreateGenericObjectSet<TEntity>().Attach(entity); } public void SaveChanges() { this.Context.SaveChanges(); } bool _enableEagerLoading = false; public bool EnableEagerLoading { get { return _enableEagerLoading; } set { _enableEagerLoading = value; } } #endregion } }
The above implementation can be used in most of the CRUD operations. If you have any specific logic for your repository then you can extend it by implementing the IRepository interface. Most of the functionality doesn’t required much explanation except PerformEagerLoading().
Normally we load related entities using LoadProperty in the context or use Include in the Linq query. Both approach wont work here in generic approach as we do not know the properties to do this. I searched a lot to achieve eager loading in a generic mode. But nothing worked then thought of implementing one myself. I haven’t verified the performance of this approach but worked well for my scenario.
The approach I come with is, decorate the related entities with an attribute and use the reflection and iterate through the entity and load it using LoadProperty of context object. Below code will explain my approach.
private void PerformEagerLoading<TEntity>(TEntity entity, ObjectContext context) where TEntity : class { PropertyInfo[] properties = typeof(TEntity).GetProperties(); foreach (PropertyInfo property in properties) { object[] keys = property.GetCustomAttributes(typeof(NavigationFieldAttribute), true); if (keys.Length > 0) { context.LoadProperty(entity, property.Name); } } }
NavigationFieldAttribute is a new attribute inherited from System.Attribute. You can see the implementation in my source code. For the implementation I used the same db model explained in my previous post.
Let’s write a test to verify our GenericRepository
string connectionString = "Data Source=PROLAPE00700\\SQLserver;Initial Catalog=ImagePublisher;User ID=sony;PWD=sony;MultipleActiveResultSets=True;"; [TestMethod()] public void GetAllTest() { IRepository genericRepository = new GenericRepository(connectionString); genericRepository.EnableEagerLoading = true; IList<User> users = genericRepository.GetAll<User>(); Assert.AreNotEqual(0, users.Count); } [TestMethod] public void FindTest() { IRepository genericRepository = new GenericRepository(connectionString); genericRepository.EnableEagerLoading = true; IList<User> users = genericRepository.Find<User>(u => u.UserID == 1); User user = users[0]; Assert.AreNotEqual(0, user.UserRoles.Count); }
You can understand the EagerLoading functionality by setting false to EnableEagerLoading in the FindTest above. If we do so the test will fail as it wont load User.UserRoles.
The advantage of this generic approach is we don’t want to create Repository object for each entity if you want to fetch a different entity in the same function. For e.g
IList<User> users=genericRepository.GetAll<Users>();
IList<Role> roles=genericRepository.GetAll<Role>();
Download the source code