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
[…] used the same generic repository pattern explained in one of my previous post. When I wrote this app I spend most of the time in investigating about different methods of […]
Twitter Public Timeline reader for WP7 using Caliburn Micro | Sony Arouje Blog
October 28, 2010 at 3:31 pm
[…] similar to the one I wrote for EF. You can find the generic repository for EF in one of my post. Below is the Generic repository class for […]
Photo Album using Silverlight, Caliburn Micro and Mongo DB | Sony Arouje Blog
November 16, 2010 at 1:18 am
[…] I used the Generic repository model I explained earlier in my blog. The above method is a synchronous call and the caller should wait till the db calls complete. Am […]
Using await in real world asynchronous programming–Part 2 | Sony Arouje Blog
December 9, 2010 at 11:21 pm
[…] created a generic repository sort of class around db4o, the same way I did one for Entity Framework. This generic repository reduced lot of my work and act as the entry point to persist my entities […]
db4o A light weight Object Oriented Database | Sony Arouje Blog
January 5, 2011 at 4:26 pm
[…] created a generic repository for Hibernate just like the one I created for Entity Framework. Let’s go through the hibernate […]
Persist Domain Model using NHibernate | Sony Arouje Blog
June 24, 2011 at 8:35 pm
Hi… Can please help me to call StoredProcedure with help of genericrepository. I am able to call directly means with the help of context object but not finding any way to call with the help of GenericRepository. What i found that when SP imported its create complexType instead of Entity.
Thanks in advanced.
Anonymous
June 25, 2012 at 2:42 pm
Hi,
I am sorry I haven’t connected an ORM with any StoredProc, so not sure how to deal with it in the Generic Repository.
Regards,
Sony Arouje
Sony Arouje
June 25, 2012 at 3:08 pm