2019-10-10 09:14:26
Clean Architecture
The architecture should be independent of frameworks. Meaning that a developer should be able to replace an out of date framework with a new framework without having to make changes to the architecture.
Repository Benefits
- Minimises duplicate query logic:
var products = repository.GetTopSellingProducts(category, count)
- Promotes test-ability.
Decouples an application from persistence frameworks, such as entity framework.
This give developers options to replace old persistence frameworks as and when new persistence frameworks are released.
New persistence frameworks are released about every few years:
2002 ADO.NET • 2007 LINQ to SQL • 2007 NHibernate • 2008 Entity Framework v1 • 2010 Entity Framework v4 • 2011 Entity Framework v4.1 • 2012 Entity Framework v5& • 2013 Entity Framework v6 • 2016 Entity Framework Core 1.0 • 2017 Entity Framework Core 2.0 • 2019 Entity Framework Core 3.0.
What should the Repository Look Like?
A repository should consist of at least the following methods:
Add(obj) Remove(obj) Get(id) GetAll() Find(predicate)
Note that there is no update, remove or save. Because the repository acts as a collection of objects in memory. Instead the Unit of Work is used
Unit of Work
Maintains a list of objects affected by a business transaction and coordinates the writing out of changes.
Patterns of Enterprise Application Architecture by Martin Fowler
Simplicity is the ultimate sophistication
Patterns of Enterprise Application Architecture by Martin Fowler
Implementing the Repository Pattern
Unit of Work
Note that Complete()
is the equivalent of Save(), but is semantically a better choice for a "Unit of work".
Implementation In Visual Studio
Note for simplicity the code examples contain only synchronous methods. In the real word
IRepository
Interface
using System; using System.Collections.Generic; using System.Linq.Expressions; namespace Example.Core.Repositories { public interface IRepository<TEntity> where TEntity : class { #region Getter TEntity Get(int id); IEnumerable<TEntity> GetAll(); IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate); TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate); #endregion #region Adder void Add(TEntity entity); void AddRange(IEnumerable<TEntity> entities); #endregion #region Removers void Remove(TEntity entity); void RemoveRange(IEnumerable<TEntity> entities); #endregion } }
Implementing IRepository
using Example.Core.Repositories; using System; using System.Collections.Generic; using System.Data.Entity; using System.Linq; using System.Linq.Expressions; namespace Example.Persistence.Repositories { public class Repository<TEntity> : IRepository<TEntity> where TEntity : class { private DbSet<TEntity> _entities; protected readonly DbContext Context; public Repository(DbContext context) { Context = context;// Here we are working with a DbContext, // not MyAppContext. So we don't have DbSets // such as Courses or Authors, // and we need to use the generic Set() method to access them. _entities = Context.Set<TEntity>(); } public TEntity Get(int id) { return _entities.Find(id); } public IEnumerable<TEntity> GetAll() { return _entities.ToList(); } public IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> predicate) { return _entities.Where(predicate); } public TEntity SingleOrDefault(Expression<Func<TEntity, bool>> predicate) { return _entities.SingleOrDefault(predicate); } public void Add(TEntity entity) { _entities.Add(entity); } public void AddRange(IEnumerable<TEntity> entities) { _entities.AddRange(entities); } public void Remove(TEntity entity) { _entities.Remove(entity); } public void RemoveRange(IEnumerable<TEntity> entities) { _entities.RemoveRange(entities); } } }
No Track Queries and DbContext in EF Core
var product = _context.Products.AsNoTracking() .FirstOrDefault();
To make this universal add this line to the contractor of the class that inherits from DbContext
:
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
Creating a Custom Repository
For this example lets imagine a Course
class.
First create and interface
using Example.Core.Domain; using System.Collections.Generic; namespace Example.Core.Repositories { public interface ICourseRepository : IRepository<Course> { IEnumerable<Course> GetTopSellingCourses(int count); IEnumerable<Course> GetCoursesWithAuthors(int pageIndex, int pageSize); } }
Implement the interface
using Example.Core.Domain; using Example.Core.Repositories; using System.Collections.Generic; using System.Data.Entity; using System.Linq; namespace Example.Persistence.Repositories { public class CourseRepository : Repository<Course>, ICourseRepository { public CourseRepository(MyDbContext context) : base(context) { } public IEnumerable<Course> GetTopSellingCourses(int count) { return MyDbContext.Courses.OrderByDescending(c => c.FullPrice).Take(count).ToList(); } public IEnumerable<Course> GetCoursesWithAuthors(int pageIndex, int pageSize = 10) { return MyDbContext.Courses .Include(c => c.Author) .OrderBy(c => c.Name) .Skip((pageIndex - 1) * pageSize) .Take(pageSize) .ToList(); } public MyDbContext MyDbContext { get { return Context as MyDbContext; } } } }
Code for Unit of Work
Create the interface
using Example.Core.Repositories; using System; namespace Example.Core { public interface IUnitOfWork : IDisposable { ICourseRepository Courses { get; } IAuthorRepository Authors { get; } int Complete(); } }
Implementation
using Example.Core; using Example.Core.Repositories; using Example.Persistence.Repositories; namespace Example.Persistence { public class UnitOfWork : IUnitOfWork { private readonly MyDbContext _context; public UnitOfWork(MyDbContext context) { _context = context; Courses = new CourseRepository(_context); Authors = new AuthorRepository(_context); } public ICourseRepository Courses { get; private set; } public IAuthorRepository Authors { get; private set; } public int Complete() { return _context.SaveChanges(); } public void Dispose() { _context.Dispose(); } } }
Using the Code
An Example
using Example.Persistence; namespace Example { class Program { static void Main(string[] args) { using (var unitOfWork = new UnitOfWork(new MyDbContext())) {// Example1 var course = unitOfWork.Courses.Get(1);// Example2 var courses = unitOfWork.Courses.GetCoursesWithAuthors(1, 4);// Example3 var author = unitOfWork.Authors.GetAuthorWithCourses(1); unitOfWork.Courses.RemoveRange(author.Courses); unitOfWork.Authors.Remove(author); unitOfWork.Complete(); } } } }