Skip to main content

Generic Repository with Entity Framework Core

Recently I worked on a project with ASP.NET Core which uses Entity Framework Core.
With them, I used a generic repository pattern in the data layer.
Repository interface was like below:
 using System;  
 using System.Collections.Generic;  
 using System.Linq.Expressions;  
 using OnlineSurvey.Models;  
 namespace OnlineSurvey.Data  
 {  
   public interface IRepository<T> where T : BaseEntity  
   {  
     T GetById(int id, params Expression<Func<T, object>>[] includExpressions);  
     void Add(T entity);  
     void Delete(T entity);  
     void Delete(int id);  
     IEnumerable<T> GetAll(params Expression<Func<T, object>>[] includExpressions);  
     IEnumerable<T> Find(Expression<Func<T, bool>> where);  
     int Count();  
   }  
 }  
And the implementation was like below:
 using System;  
 using System.Data.Entity;  
 using System.Data.Entity.Infrastructure;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Linq.Expressions;  
 using OnlineSurvey.Models;  
 namespace OnlineSurvey.Data  
 {  
   public class Repository<T> : IRepository<T> where T : BaseEntity  
   {  
     protected DbSet<T> DbSet => this.Context.Set<T>();  
     protected DataContext Context { get; set; }  
     public Repository(DataContext context)  
     {  
       this.Context = context ?? throw new ArgumentException("An instance of DbContext is required to use this repository.", nameof(context));  
     }  
     public IEnumerable<T> GetAll(params Expression<Func<T, object>>[] includExpressions)  
     {  
       if (includExpressions.Any())  
       {  
         return  
           includExpressions.Aggregate<Expression<Func<T, object>>, IQueryable<T>>(this.DbSet,  
             (current, expression) => current.Include(expression)).ToList();  
       }  
       return this.DbSet.ToList();  
     }  
     public T GetById(int id, params Expression<Func<T, object>>[] includExpressions)  
     {  
       if (includExpressions.Any())  
       {  
         var set =  
           includExpressions.Aggregate<Expression<Func<T, object>>, IQueryable<T>>(this.DbSet,  
             (current, expression) => current.Include(expression));  
         return set.SingleOrDefault(s => s.Id == id);  
       }  
       return this.DbSet.Find(id);  
     }  
     public void Add(T entity)  
     {  
       DbEntityEntry entry = this.Context.Entry(entity);  
       if (entry.State != EntityState.Detached)  
       {  
         entry.State = EntityState.Added;  
       }  
       else  
       {  
         this.DbSet.Add(entity);  
       }  
     }  
     public void Delete(T entity)  
     {  
       DbEntityEntry entry = this.Context.Entry(entity);  
       if (entry.State != EntityState.Deleted)  
       {  
         entry.State = EntityState.Deleted;  
       }  
       else  
       {  
         this.DbSet.Attach(entity);  
         this.DbSet.Remove(entity);  
       }  
     }  
     public void Delete(int id)  
     {  
       var entity = this.GetById(id);  
       if (entity != null)  
       {  
         this.Delete(entity);  
       }  
     }  
     public IEnumerable<T> Find(Expression<Func<T, bool>> where)  
     {  
       return DbSet.Where(where).ToList();  
     }  
     public int Count()  
     {  
       return DbSet.Count();  
     }  
   }  
 }  

Usage of the above mentioned implementation:
 Question question = this._unitOfWork.Questions.GetById(id, q=>q.Answers);  
Above code has one issue. There is no way load entities inside a list. This problem can be fixed if we could have access to inbuilt function .ThenInclude.

The following change will give us access to the above mentioned function.
 using System;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Linq.Expressions;  
 using System.Threading.Tasks;  
 using OnlineSurvey.Models;  
 using Microsoft.EntityFrameworkCore.Query;
  
 namespace OnlineSurvey.Data  
 {  
   public interface IRepository<T> where T : BaseEntity  
   {  
     Task<T> GetByIdAsync(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null);  
     Task AddAsync(T entity);  
     void Delete(T entity);  
     Task DeleteAsync(int id);  
     Task<IEnumerable<T>> GetAllAsync(Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null);  
     Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate = null,  
                     Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null);  
     int Count();  
     Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicte = null,  
                   Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null);  
     void Update(T entity);  
   }  
 }  
Refer to the below implementation:
 using System;  
 using System.Data.Entity;  
 using System.Collections.Generic;  
 using System.Linq;  
 using System.Linq.Expressions;  
 using OnlineSurvey.Models;  
 using System.Threading.Tasks;  
 namespace OnlineSurvey.Data  
 {  
   public class Repository<T> : IRepository<T> where T : BaseEntity  
   {  
     protected DbSet<T> DbSet => this.Context.Set<T>();  
     protected DbContext Context { get; set; }  
     public Repository(DbContext context)  
     {  
       this.Context = context ?? throw new ArgumentException("An instance of DbContext is required to use this repository.", "context");  
     }  
     public async Task<T> GetByIdAsync(int id, Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null)  
     {  
       IQueryable<T> query = this.DbSet;  
       if(include != null)  
       {  
         query = include(query);  
       }  
       return await query.AsNoTracking().SingleOrDefaultAsync(s => s.Id == id);  
     }  
     public async Task AddAsync(T entity)  
     {  
       EntityEntry entry = this.Context.Entry(entity);  
       if(entity.State != EntityState.Detached)  
       {  
         entry.State = EntityState.Added;  
       }  
       else  
       {  
         await this.DbSet.AddAsync(entity);  
       }  
     }  
     public void Delete(T entity)  
     {  
       EntityEntry entry = this.Context.Entry(entity);  
       if (entity.State != EntityState.Deleted)  
       {  
         entry.State = EntityState.Deleted;  
       }  
       else  
       {  
         this.DbSet.Attach(entity);  
         this.DbSet.Remove(entity);  
       }  
     }  
     public async Task DeleteAsync(int id)  
     {  
       T entity = await this.GetByIdAsync(id);  
       if(entity != null)  
       {  
         this.Delete(entity);  
       }  
     }  
     public async Task<IEnumerable<T>> GetAllAsync(Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null)  
     {  
       IQueryable<T> query = this.DbSet;  
       if (include != null)  
       {  
         query = include(query);  
       }  
       return await query.AsNoTracking().ToListAsync();  
     }  
     public async Task<IEnumerable<T>> FindAsync(Expression<Func<T, bool>> predicate = null, Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null)  
     {  
       IQueryable<T> query = this.DbSet;  
       if (include != null)  
       {  
         query = include(query);  
       }  
       if(predicate != null)  
       {  
         query = query.Where(predicate);  
       }  
       return await query.AsNoTracking().ToListAsync();  
     }  
     public int Count()  
     {  
       return this.DbSet.Count();  
     }  
     public async Task<T> FirstOrDefaultAsync(Expression<Func<T, bool>> predicate = null, Func<IQueryable<T>, IIncludableQueryable<T, object>> include = null)  
     {  
       IQueryable<T> query = this.DbSet;  
       if (include != null)  
       {  
         query = include(query);  
       }  
       if (predicate != null)  
       {  
         query = query.Where(predicate);  
       }  
       return await query.FirstOrDefaultAsync();  
     }  
     public void Update(T entity)  
     {  
       EntityEntry entry = this.Context.Entry(entity);  
       entity.State = EntityState.Modified;  
     }  
   }  
 }  
Usage of the above-mentioned implementation:
 Question question = this._unitOfWork.Questions.GetById(id, q=>q.Include(q => q.Answers).ThenInclude(a => a.Question));  
Hope this helps you in some way.

Comments

Popular posts from this blog

Exit a T-SQL Cursor When Condition is met

Have you ever wanted to exit from a cursor when a condition is met? I wanted to do it. So this is how I did it. DECLARE @Field1 AS INT DECLARE @Field2 AS INT DECLARE CursorName CURSOR READ_ONLY FOR SELECT Field1, Field2 FROM TableName OPEN CursorName FETCH NEXT FROM CursorName INTO @Field1, @Field2 WHILE @@FETCH_STATUS = 0 BEGIN IF @Field1 = 1 BEGIN GOTO ENDCURSOR END FETCH NEXT FROM CursorName INTO @Field1, @Field2 END ENDCURSOR: CLOSE CursorName DEALLOCATE CursorName I have set my fonts to bold where you want to notice. So that's all I hope you will get something out of it and it is true that this is not a big deal. :)

Common Design Principles

There are number of common design principles that, like design patterns, best practice over the years to build maintainable software. I'm up to describe some widely used design principles though out the post. Following common principle are extracted by the same book that I mentioned before ( Professional ASP.Net Design Patterns - Scott Millet ). Principles are as follows: Keep It Simple Stupid (KISS) One common issue in software programming is over-complicating a solution. So main concern of this principle is keep the code simple but not simplistic. Eventually this will avoid unnecessary complexities. Don't Repeat yourself (DRY) Main concern of this principle is to avoid the repetition. In other words this is all about abstracting out the common functionalities into a single place. Ex: If there is a price calculation method in a system. It should lay in a single place there. Tell Don't Ask The Tell, Don’t Ask principle is closely aligned with encapsulation and the assignin...

An error occurred while starting the application in ASP.NET Core on IIS

I got this following error when I hosted my ASP.NET Core 2.2 web API on IIS. An error occurred while starting the application. .NET Core 4.6.27317.07 X64 v4.0.0.0 | Microsoft.AspNetCore.Hosting version 2.2.0-rtm-35687 (Please ignore the version numbers. Just wanted to show the error as it was.) So my first step was to change the  stdoutLogEnabled=true in web.config,  but couldn't find the logs folder. Few mins later I learnt that the  AspNetCoreModule   doesn’t create the logs folder for you by default (Hope they will fixed soon). It logs the error to the Event Viewer which says: Warning: Could not create stdoutLogFile  Path\logs\stdout_xxxxx.log, ErrorCode = -2147024893 . So, I created the the logs folder manually and here I find the real reason why it is failing. Hope this will help you guys.