I have written a few blog posts about my experience on applying the Generic Repository pattern to Entity Framework and I even made a NuGet package for my naïve implementation. Even if that looked OK at the time for me, I had troubles about my implementation for couple of reasons:
With the new release of the GenericRepository.EntityFramework package, the all of the above problems have their solutions. The source code for this release is available under the master branch of the repository and you can also see the ongoing work for the final release under the v0.3.0 branch. The NuGet package is available as pre-release for now. So, you need to use the –pre switch to install it.
PM> Install-Package GenericRepository.EntityFramework -Pre
The old GenericRepository.EF package is still around and I will update it, too but it’s now unlisted and only thing it does is to install the GenericRepository.EntityFramework package.
I also included a sample application which shows the usage briefly. I will complete the sample and extend it further for a better view. Definitely check this out!
Let’s dive right in and see what is new and cool.
I introduced two new interfaces: IEntity and IEntity<TId> and each of your entity classes needs to implement one of these. As you can see from the implementation, IEntity just implements the IEntity<int> and you can use IEntity if you are using integer based Ids. The reason why I added these is make the GetSingle method work.
Instead of deriving your context class from DbContext, you now need to take the EntitiesContext as the base class for your context. If you have an existing context class based on DbContext, changing it to use EntitiesContext should not break it. The EntitiesContext class has all the same constructors as DbContext. So, you can also use those. Here is the sample:
public class AccommodationEntities : EntitiesContext { // NOTE: You have the same constructors as the DbContext here. E.g: // public AccommodationEntities() : base("nameOrConnectionString") { } public IDbSet<Country> Countries { get; set; } public IDbSet<Resort> Resorts { get; set; } public IDbSet<Hotel> Hotels { get; set; } }
Then, through your IoC container, you can register your context as a new instance for IEntitiesContext per a particular scope. The below example uses Autofac to do that for an ASP.NET Web API application:
private static void RegisterDependencies(HttpConfiguration config) { var builder = new ContainerBuilder(); builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); // Register IEntitiesContext builder.Register(_ => new AccommodationEntities()) .As<IEntitiesContext>().InstancePerApiRequest(); // TODO: Register repositories here config.DependencyResolver = new AutofacWebApiDependencyResolver(builder.Build()); }
Here is the real meat of the package: IEntityRepository and EntityRepository. Same as the IEntity and IEntity<TId>, we have two different IEntityRepository generic interfaces: IEntityRepository<TEntity> and IEntityRepository<TEntity, TId>. They have their implementations under the same generic signature: EntityRepository<TEntity> and EntityRepository<TEntity, TId>. The big improvement now is that EntityRepository generic repository implementation accepts an IEntitiesContext implementation through its constructor. This, for example, enables you to use the same DbContext (IEntitiesContext implementation in our case, which is EntitiesContext by default) instance per-request for your ASP.NET MVC, ASP.NET Web API application and share that across your repositories. Note: don’t ever use singleton DbContext instance throughout your AppDomain. DbContext is not thread safe.
As we have registered our EntitiesContext instance per request above, we can now register the repositories as well. As our repositories accepts an IEntitiesContext implementation through their constructor, our IoC container will use our previous registration for that automatically. Autofac has this ability as nearly all IoC containers do.
private static void RegisterDependencies(HttpConfiguration config) { var builder = new ContainerBuilder(); builder.RegisterApiControllers(Assembly.GetExecutingAssembly()); // Register IEntitiesContext builder.Register(_ => new AccommodationEntities()) .As<IEntitiesContext>().InstancePerApiRequest(); // TODO: Register repositories here builder.RegisterType<EntityRepository<Country>>() .As<IEntityRepository<Country>>().InstancePerApiRequest(); builder.RegisterType<EntityRepository<Resort>>() .As<IEntityRepository<Resort>>().InstancePerApiRequest(); builder.RegisterType<EntityRepository<Hotel>>() .As<IEntityRepository<Hotel>>().InstancePerApiRequest(); config.DependencyResolver = new AutofacWebApiDependencyResolver(builder.Build()); }
Best feature with this release is out of the box pagination support with generic repository instances. It doesn’t perform the pagination in-memory; it queries the database accordingly and gets only the parts which are needed which is the whole point Here is an ASP.NET Web API controller which uses the pagination support comes with the EntityRepository:
public class CountriesController : ApiController { private readonly IEntityRepository<Country> _countryRepository; private readonly IMappingEngine _mapper; public CountriesController( IEntityRepository<Country> countryRepository, IMappingEngine mapper) { _countryRepository = countryRepository; _mapper = mapper; } // GET api/countries?pageindex=1&pagesize=5 public PaginatedDto<CountryDto> GetCountries(int pageIndex, int pageSize) { PaginatedList<Country> countries = _countryRepository.Paginate(pageIndex, pageSize); PaginatedDto<CountryDto> countryPaginatedDto = _mapper.Map<PaginatedList<Country>, PaginatedDto<CountryDto>>(countries); return countryPaginatedDto; } } public interface IPaginatedDto<out TDto> where TDto : IDto { int PageIndex { get; set; } int PageSize { get; set; } int TotalCount { get; set; } int TotalPageCount { get; set; } bool HasNextPage { get; set; } bool HasPreviousPage { get; set; } IEnumerable<TDto> Items { get; } } public class PaginatedDto<TDto> : IPaginatedDto<TDto> where TDto : IDto { public int PageIndex { get; set; } public int PageSize { get; set; } public int TotalCount { get; set; } public int TotalPageCount { get; set; } public bool HasNextPage { get; set; } public bool HasPreviousPage { get; set; } public IEnumerable<TDto> Items { get; set; } }
Paginate method will return us the PaginatedList<TEntity> object back and we can project that into our own Dto object as you can see above. I used AutoMapper for that. If I send a request to this API endpoint and ask for response in JSON format, I get back the below result:
{ "PageIndex":1, "PageSize":2, "TotalCount":6, "TotalPageCount":3, "HasNextPage":true, "HasPreviousPage":false, "Items":[ { "Id":1, "Name":"Turkey", "ISOCode":"TR", "CreatedOn":"2013-01-08T21:12:26.5854461+02:00" }, { "Id":2, "Name":"United Kingdom", "ISOCode":"UK", "CreatedOn":"2013-01-08T21:12:26.5864465+02:00" } ] }
Isn’t this perfect There are other pagination method inside the EntityRepository implementation which supports including child or parent entities and sorting. You also have the ToPaginatedList extension method and you can build your query and call ToPaginatedList on that query to get PaginatedList<TEntity> object back.
In my previous blog posts, I kind of sucked at extending the generic repository. So, I wanted to show here the better approach that I have been taking for a while now. This is not a feature of my generic repository, this is the feature of .NET itself: extension methods! If you need extra methods for your specific repository, you can always extend the IEntityRepository<TEntity, TId> which gives you a better way to extend your repositories. Here is an example:
public static class HotelRepositoryExtensions { public static IQueryable<Hotel> GetAllByResortId( this IEntityRepository<Hotel, int> hotelRepository, int resortId) { return hotelRepository.FindBy(x => x.ResortId == resortId); } }
My first intention is finish writing all the tests for the whole project, fix bugs and inconsistencies for the v0.3.0 release. After that release, I will work on EF6 version for my generic repository implementation which will have sweet asynchronous support. I also plan to release a generic repository implementation for MongoDB.
Stay tuned, install the package, play with it and give feedback