Introduction
In this session, we will learn about:
- Creating Generic Repository with Entity Framework.
- Taking advantage of .NET generics
- Creating ASP.NET Core custom API controller CRUD operations with AJAX calls.
- Consuming Entity Framework Core 2.0, in-memory database.
- Interface
- Class
- Generic
- async/await
- DataContext
- Overriding onModelCreating
- Overriding SaveChanges
- Overriding Update
- Overriding UpdateAsync
- Overriding SaveChangesAsync
- Creating audit structure
- Dispose
- Creating Abstract class
- Getting WindowsIdentity Current User
- Creating a new insert item
- Updating an Item
- Deleting an Item
- Writing HttpPost Create API method.
- Writing HttpPut Update API method.
- Writing HttpPost Delete API method.
- Route table
- Setting start controller Route to Home Index method.
- jQuery/HTML pass table row value to a JavaScript function
- jQuery Ajax HttpGet
- jQuery Ajax HttpPut
- jQuery Ajax HttpPost
- Editable HTML table
Advantage of generic repository is that you can inherit from it, pass an entity type, and you will have CRUD operations. It will save you a lot of coding time when your domain objects are likely to grow over 50+. Another advantage is a change history functionality; you can inherit your entities from change history interface and you have the functionality.
The repository pattern creates an abstract layer between the Data Access Layer and the Service Layer of an application.
There is more than one way to implement the unit of work and repository patterns. You need to pick which one works best for your project specific needs.
There is more than one way to implement the unit of work and repository patterns. You need to pick which one works best for your project specific needs.
Entity Framework is an object-relational mapper(ORM), which enables the developer to work with relational data using domain-specific object and allows the use of LINQ or Lambda expressions to search or filer data in the database.
NOTE
Our generic repository is not bullet proof with all the functionalities but rather flexible enough that you can easily extend per your own needs.
Our generic repository is not bullet proof with all the functionalities but rather flexible enough that you can easily extend per your own needs.
Pre-Requirements
To be able to run the example from the download or build it from scratch, you need to have the following tools,
- Visual Studio 2017 or above
- .NET Core 2.0 or above
Generic repository Interface and Generic repository abstract class
GenericRepository<T> : IGenericRepository<T> where T : class
We start with EF action methods and put them in a generic repository interface IGenericRepository.
GenericRepository<T> : IGenericRepository<T> where T : class
We start with EF action methods and put them in a generic repository interface IGenericRepository.
- //Copyright 2017 (c) SmartIT. All rights reserved. By John Kocer
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Threading.Tasks;
- namespace EfCoreGenericRepository.DataAccess
- {
- public interface IGenericRepository<T> where T : class
- {
- T Add(T t);
- Task<T> AddAsyn(T t);
- int Count();
- Task<int> CountAsync();
- void Delete(T entity);
- Task<int> DeleteAsyn(T entity);
- void Dispose();
- T Find(Expression<Func<T, bool>> match);
- ICollection<T> FindAll(Expression<Func<T, bool>> match);
- Task<ICollection<T>> FindAllAsync(Expression<Func<T, bool>> match);
- Task<T> FindAsync(Expression<Func<T, bool>> match);
- IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
- Task<ICollection<T>> FindByAsyn(Expression<Func<T, bool>> predicate);
- T Get(int id);
- IQueryable<T> GetAll();
- Task<ICollection<T>> GetAllAsyn();
- IQueryable<T> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties);
- Task<T> GetAsync(int id);
- void Save();
- Task<int> SaveAsync();
- T Update(T t, object key);
- Task<T> UpdateAsyn(T t, object key);
- }
- }
We implement GenericRepository with Generic T. You can add your future methods here. We added dispose method which can be called on the Controller’s override of the dispose method.
- //Copyright 2017 (c) SmartIT. All rights reserved. By John Kocer
- using System;
- using System.Collections.Generic;
- using System.Linq;
- using System.Linq.Expressions;
- using System.Threading.Tasks;
- using Microsoft.EntityFrameworkCore;
- namespace EfCoreGenericRepository.DataAccess
- {
- public abstract class GenericRepository<T> : IGenericRepository<T> where T : class
- {
- protected DataContext _context;
- public GenericRepository(DataContext context)
- {
- _context = context;
- }
- public IQueryable<T> GetAll()
- {
- return _context.Set<T>();
- }
- public virtual async Task<ICollection<T>> GetAllAsyn()
- {
- return await _context.Set<T>().ToListAsync();
- }
- public virtual T Get(int id)
- {
- return _context.Set<T>().Find(id);
- }
- public virtual async Task<T> GetAsync(int id)
- {
- return await _context.Set<T>().FindAsync(id);
- }
- public virtual T Add(T t)
- {
- _context.Set<T>().Add(t);
- _context.SaveChanges();
- return t;
- }
- public virtual async Task<T> AddAsyn(T t)
- {
- _context.Set<T>().Add(t);
- await _context.SaveChangesAsync();
- return t;
- }
- public virtual T Find(Expression<Func<T, bool>> match)
- {
- return _context.Set<T>().SingleOrDefault(match);
- }
- public virtual async Task<T> FindAsync(Expression<Func<T, bool>> match)
- {
- return await _context.Set<T>().SingleOrDefaultAsync(match);
- }
- public ICollection<T> FindAll(Expression<Func<T, bool>> match)
- {
- return _context.Set<T>().Where(match).ToList();
- }
- public async Task<ICollection<T>> FindAllAsync(Expression<Func<T, bool>> match)
- {
- return await _context.Set<T>().Where(match).ToListAsync();
- }
- public virtual void Delete(T entity)
- {
- _context.Set<T>().Remove(entity);
- _context.SaveChanges();
- }
- public virtual async Task<int> DeleteAsyn(T entity)
- {
- _context.Set<T>().Remove(entity);
- return await _context.SaveChangesAsync();
- }
- public virtual T Update(T t, object key)
- {
- if (t == null)
- return null;
- T exist = _context.Set<T>().Find(key);
- if (exist != null) {
- _context.Entry(exist).CurrentValues.SetValues(t);
- _context.SaveChanges();
- }
- return exist;
- }
- public virtual async Task<T> UpdateAsyn(T t, object key)
- {
- if (t == null)
- return null;
- T exist =await _context.Set<T>().FindAsync(key);
- if (exist != null)
- {
- _context.Entry(exist).CurrentValues.SetValues(t);
- await _context.SaveChangesAsync();
- }
- return exist;
- }
- public int Count()
- {
- return _context.Set<T>().Count();
- }
- public async Task<int> CountAsync()
- {
- return await _context.Set<T>().CountAsync();
- }
- public virtual void Save()
- {
- _context.SaveChanges();
- }
- public async virtual Task<int> SaveAsync()
- {
- return await _context.SaveChangesAsync();
- }
- public virtual IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
- {
- IQueryable<T> query = _context.Set<T>().Where(predicate);
- return query;
- }
- public virtual async Task<ICollection<T>> FindByAsyn(Expression<Func<T, bool>> predicate)
- {
- return await _context.Set<T>().Where(predicate).ToListAsync();
- }
- public IQueryable<T> GetAllIncluding(params Expression<Func<T, object>>[] includeProperties)
- {
- IQueryable<T> queryable = GetAll();
- foreach (Expression<Func<T, object>> includeProperty in includeProperties)
- {
- queryable = queryable.Include<T, object>(includeProperty);
- }
- return queryable;
- }
- private bool disposed = false;
- protected virtual void Dispose(bool disposing)
- {
- if (!this.disposed)
- {
- if (disposing)
- {
- _context.Dispose();
- }
- this.disposed = true;
- }
- }
- public void Dispose()
- {
- Dispose(true);
- GC.SuppressFinalize(this);
- }
- }
- }
Custom entity interface inherits from generic repository interface
IBlogRepository : IGenericRepository<Blog>
If you need any specific action for an entity such as id type we can put it here.
- using EfCoreGenericRepository.Models;
- namespace EfCoreGenericRepository.DataAccess
- {
- public interface IBlogRepository : IGenericRepository<Blog>
- {
- // If you need to customize your entity actions you can put here
- Blog Get(int blogId);
- }
- }
GenericRepository<Blog>, IBlogRepository
BlogRepository inherits from abstract class GenericRepository and Interface IBlogRepository. We override Update and UpdateAsync methods because CreateBy and CreatedOn values do not need to be changed after initial creation.
- //Copyright 2017 (c) SmartIT. All rights reserved. By John Kocer
- using System.Linq;
- using System.Threading.Tasks;
- using EfCoreGenericRepository.Models;
- namespace EfCoreGenericRepository.DataAccess
- {
- public class BlogRepository : GenericRepository<Blog>, IBlogRepository
- {
- public BlogRepository(DataContext context) : base(context)
- {
- }
- public Blog Get(int blogId)
- {
- var query = GetAll().FirstOrDefault(b => b.BlogId == blogId);
- return query;
- }
- public async Task<Blog> GetSingleAsyn(int blogId)
- {
- return await _context.Set<Blog>().FindAsync(blogId);
- }
- public override Blog Update(Blog t, object key)
- {
- Blog exist = _context.Set<Blog>().Find(key);
- if (exist != null)
- {
- t.CreatedBy = exist.CreatedBy;
- t.CreatedOn = exist.CreatedOn;
- }
- return base.Update(t, key);
- }
- public async override Task<Blog> UpdateAsyn(Blog t, object key)
- {
- Blog exist =await _context.Set<Blog>().FindAsync(key);
- if (exist != null)
- {
- t.CreatedBy = exist.CreatedBy;
- t.CreatedOn = exist.CreatedOn;
- }
- return await base.UpdateAsyn(t, key);
- }
- }
- }
IAuditable
Change tracking for audit purposes can be accomplished using IAudible. Any entity that needs to be change tracked inherits from IAudible interface. We override DataContext class SaveChanges and SaveChangesAsync methods to get CreatedBy, CreatedOn, UpdatedBy and UpdatedOn values.
- using System;
- namespace EfCoreGenericRepository.Models
- {
- public interface IAuditable
- {
- string CreatedBy { get; set; }
- DateTime? CreatedOn { get; set; }
- string UpdatedBy { get; set; }
- DateTime? UpdatedOn { get; set; }
- }
- }
Blog entity inherits from IAudiable to have change tracking member implemented.
- //Copyright 2017 (c) SmartIT. All rights reserved. By John Kocer
- using System;
- using System.Collections.Generic;
- using System.ComponentModel.DataAnnotations;
- namespace EfCoreGenericRepository.Models
- {
- public class Blog:IAuditable
- {
- [Key]
- public int BlogId { get; set; }
- public string Title { get; set; }
- public ICollection<Post> Posts { get; set; }=new HashSet<Post>();
- public string CreatedBy { get; set; }
- public DateTime? CreatedOn { get; set; }
- public string UpdatedBy { get; set; }
- public DateTime? UpdatedOn { get; set; }
- }
- }
DataContect class inherits from EF DbCotext class as we can see from below inheritance diagram.
DataContect class has DataSet, overridden SaveChanges and SaveChangesAsync method, and UserProvider property to get current user in windows system, and TimeStampProvider to add Created and updated times. You can convert time to Universal Time (UTC) if you need to.
- //Copyright 2017 (c) SmartIT. All rights reserved. By John Kocer
- using System;
- using System.Linq;
- using System.Security.Principal;
- using System.Threading;
- using System.Threading.Tasks;
- using Microsoft.EntityFrameworkCore;
- using EfCoreGenericRepository.Models;
- namespace EfCoreGenericRepository.DataAccess
- {
- public class DataContext : DbContext
- {
- public DataContext(DbContextOptions<DataContext> options) : base(options){}
- protected override void OnModelCreating(ModelBuilder builder)
- {
- builder.Entity<Post>()
- .HasOne(b => b.Blog)
- .WithMany(p => p.Posts)
- .IsRequired();
- base.OnModelCreating(builder);
- }
- public virtual void Save()
- {
- base.SaveChanges();
- }
- public string UserProvider
- {
- get
- {
- if (!string.IsNullOrEmpty(WindowsIdentity.GetCurrent().Name))
- return WindowsIdentity.GetCurrent().Name.Split('\\')[1];
- return string.Empty;
- }
- }
- public Func<DateTime> TimestampProvider { get; set; } = ()
- => DateTime.UtcNow;
- public override int SaveChanges()
- {
- TrackChanges();
- return base.SaveChanges();
- }
- public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
- {
- TrackChanges();
- return await base.SaveChangesAsync(cancellationToken);
- }
- private void TrackChanges()
- {
- foreach (var entry in this.ChangeTracker.Entries().Where(e => e.State == EntityState.Added || e.State == EntityState.Modified))
- {
- if (entry.Entity is IAuditable)
- {
- var auditable = entry.Entity as IAuditable;
- if (entry.State == EntityState.Added)
- {
- auditable.CreatedBy = UserProvider;//
- auditable.CreatedOn = TimestampProvider();
- auditable.UpdatedOn = TimestampProvider();
- }
- else
- {
- auditable.UpdatedBy = UserProvider;
- auditable.UpdatedOn = TimestampProvider();
- }
- }
- }
- }
- public DbSet<Blog> Blog { get; set; }
- public DbSet<BlogDetail> BlogDetail { get; set; }
- public DbSet<Post> Post { get; set; }
- }
- }
API Controller, MVC Controller or Service layer.
On WebAPI controller we have repository specific Database calls through our generic repository. It is a good idea to have service layer between repository and API controller.
We dispose the Context object here calling overridden dispose method.
- //Copyright 2017 (c) SmartIT. All rights reserved. By John Kocer
- using Microsoft.AspNetCore.Mvc;
- using System.Collections.Generic;
- using System.Threading.Tasks;
- using EfCoreGenericRepository.DataAccess;
- namespace Blog.Ui.Controllers
- {
- [Produces("application/json")]
- [Route("api/Blog")]
- public class BlogsApiController : Controller
- {
- private readonly IBlogRepository _blogRepository;
- public BlogsApiController(IBlogRepository blogRepository)
- {
- _blogRepository = blogRepository;
- }
- [Route("~/api/GetBlogs")]
- [HttpGet]
- public async Task<IEnumerable<EfCoreGenericRepository.Models.Blog>> Index()
- {
- return await _blogRepository.GetAllAsyn();
- }
- [Route("~/api/AddBlog")]
- [HttpPost]
- public async Task<EfCoreGenericRepository.Models.Blog> AddBlog([FromBody]EfCoreGenericRepository.Models.Blog blog)
- {
- await _blogRepository.AddAsyn(blog);
- await _blogRepository.SaveAsync();
- return blog;
- }
- [Route("~/api/UpdateBlog")]
- [HttpPut]
- //public Blog UpdateBlog([FromBody] Blog blog)
- //{
- // var updated = _blogRepository.Update(blog, blog.BlogId);
- // return updated;
- //}
- public async Task<EfCoreGenericRepository.Models.Blog> UpdateBlog([FromBody]EfCoreGenericRepository.Models.Blog blog)
- {
- var updated =await _blogRepository.UpdateAsyn(blog, blog.BlogId);
- return updated;
- }
- [Route("~/api/DeleteBlog/{id}")]
- [HttpDelete]
- public string Delete(int id)
- {
- _blogRepository.Delete(_blogRepository.Get(id));
- return "Employee deleted successfully!";
- }
- protected override void Dispose(bool disposing)
- {
- _blogRepository.Dispose();
- base.Dispose(disposing);
- }
- }
- }
We add generic repository dependency injection in the Startup configure services method.
- //in the Startup.cs
- public void ConfigureServices(IServiceCollection services)
- {
- services.AddMvc();
- services.AddDbContext<DataContext>(options =>
- options.UseInMemoryDatabase());
- services.AddTransient<IBlogRepository, BlogRepository>();
- }
When we run our demo application, we will see the below page which does Blog CRUD operations using our generic repository.
Summary
In this article, we have learned how to write generic repository pattern. In Part 2, we will learn repository and Unit of work pattern implementation.
Tasks leaned from this article,
- Creating Generic Repository with Entity Framework.
- Taking advantage of .NET generics
- Creating ASP.NET Core custom API controller CRUD operations with AJAX calls.
- Consuming Entity Framework Core 2.0, in-memory database.
- Interface
- Class
- Generic
- async/await
- DataContext
- Overriding onModelCreating
- Overriding SaveChanges
- Overriding Update
- Overriding UpdateAsync
- Overriding SaveChangesAsync
- Creating audit structure
- Dispose
- Creating Abstract class
- Getting WindowsIdentity Current User
- Creating a new insert item
- Updating an Item
- Deleting an Item
- Writing HttpPost Create API method.
- Writing HttpPut Update API method.
- Writing HttpPost Delete API method.
- Route table
- Setting start controller Route to Home Index method.
- jQuery/HTML pass table row value to a JavaScript function
- jQuery Ajax HttpGet
- jQuery Ajax HttpPut
- jQuery Ajax HttpPost
- Editable HTML table
Download source code from GitHub.
No comments:
Post a Comment