【EFCore仓储模式】介绍一个EFCore的Repository实现

2024-01-21 14:44

本文主要是介绍【EFCore仓储模式】介绍一个EFCore的Repository实现,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

阅读本文你的收获

  1. 了解仓储模式及泛型仓储的优点
  2. 学会封装泛型仓储的一般设计思路
  3. 学习在ASP.NET Core WebAPI项目中使用EntityFrameworkCore.Data.Repository

本文中的案例是微软EntityFrameworkCore的一个仓储模式实现,这个仓储库不是我自己写的,而是使用了一个老外写的EntityFrameworkCore.Data.Repository,大家可以一起来学习一下,如果你觉得合适,可以直接用在你的项目中,很方便。案例代码下载

一、 什么是仓储模式

仓储(Repository)模式自2004年首次作为领域驱动模型DDD设计的一部分引入,仓储本质上是提供数据的抽象,以便应用程序可以使用具有接口的相似的简单抽象集合。从此集合中CURD是通过一系列直接的方法完成,无需处理连接、命令等问题,使用此种模式可帮助实现松耦合,并保持领域对象的持久性无知。

  • 仓储模式是为了在程序的数据访问层和业务逻辑层之间创建的一个抽象层
  • 仓储模式是一种数据访问模式,提供一种更松散耦合的数据访问方法
  • 将创建数据访问的逻辑写在单独的类中即仓储
  • 仓储负责和业务层进行持久化通信

下图为控制器和仓储协同工作的图例:
仓储示意图

二、泛型仓储

仓储(Repository)是存在于工作单元和数据库之间单独分离出来的一层,是对数据访问的封装。其优点是

  • 业务层无需知道具体实现,达到分离关注点;
  • 提高对数据库访问的维护,对于仓储的改变,并不改变业务的逻辑;

如果我们采用的ORM框架是EF Core,实现仓储模式的话,那么类图设计一般如下:
仓储模式类图通常实现仓储的时候,会使用泛型技术封装增删改查的的通用功能,类型T即为具体要进行增删改查处理的实体类型。

使用泛型仓储(Generic Repository)的好处有以下几点:

  • 更好的可重用性:泛型仓储可以在多个实体类型之间共享和重用,减少了重复的代码编写和维护工作。
  • 更好的类型安全性:使用泛型仓储可以在编译时就约束数据的类型,减少了运行时类型错误的可能性。
  • 更好的简洁性:泛型仓储可以通过使用通用的方法和接口来简化数据访问的逻辑,提供统一的CRUD(增删改查)操作接口。
  • 更好的可测试性:泛型仓储使得数据访问逻辑可以更容易地进行单元测试,因为可以使用模拟或者假数据来代替实际的数据存储。
  • 更好的扩展性:泛型仓储可以通过继承或者接口实现来扩展其功能,例如添加自定义的查询方法或者过滤器。

三、基于EF Core实现的泛型仓储案例

开发环境:

操作系统: Windows 10 专业版
平台版本是:.NET 6
开发框架:ASP.NET Core WebApi、Entity Framework Core
开发工具:Visual Studio 2022
数据库: MySQL 5.7+

安装NuGet包

使用的NuGet包主要有:

  1. EntityFrameworkCore.Data.Repository – 一个老外封装好的开源EfCore仓储实现
  2. EntityFrameworkCore.Data.UnitOfWork-- 跟以上配套的工作单元
  3. Pomelo.EntityFrameworkCore.MySql – MySQL数据库提供程序

简单剖析一下 EntityFrameworkCore.Data.Repository

可以看到,作者定义了泛型仓储接口如下:

public interface IRepository<T> : IRepository, IDisposable, ISyncRepository<T>, ISyncRepository, IQueryFactory<T>, IAsyncRepository<T>, IAsyncRepository where T : class
{
}

IRepository< T >接口分别集继承了 ISyncRepository< T > 和 IAsyncRepository< T >这两个接口,分别是同步仓储方法接口和异步仓储方法接口。

//同步仓储方法接口
public interface ISyncRepository<T> : ISyncRepository, IRepository, IDisposable, IQueryFactory<T> where T : class
{IList<T> Search(IQuery<T> query);IList<TResult> Search<TResult>(IQuery<T, TResult> query);T SingleOrDefault(IQuery<T> query);TResult SingleOrDefault<TResult>(IQuery<T, TResult> query);T FirstOrDefault(IQuery<T> query);TResult FirstOrDefault<TResult>(IQuery<T, TResult> query);T LastOrDefault(IQuery<T> query);TResult LastOrDefault<TResult>(IQuery<T, TResult> query);bool Any(Expression<Func<T, bool>> predicate = null);int Count(Expression<Func<T, bool>> predicate = null);long LongCount(Expression<Func<T, bool>> predicate = null);TResult Max<TResult>(Expression<Func<T, TResult>> selector, Expression<Func<T, bool>> predicate = null);TResult Min<TResult>(Expression<Func<T, TResult>> selector, Expression<Func<T, bool>> predicate = null);decimal Average(Expression<Func<T, decimal>> selector, Expression<Func<T, bool>> predicate = null);decimal Sum(Expression<Func<T, decimal>> selector, Expression<Func<T, bool>> predicate = null);T Attach(T entity);void AttachRange(IEnumerable<T> entities);T Add(T entity);void AddRange(IEnumerable<T> entities);T Update(T entity, params Expression<Func<T, object>>[] properties);int Update(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> expression);void UpdateRange(IEnumerable<T> entities, params Expression<Func<T, object>>[] properties);T Remove(T entity);int Remove(Expression<Func<T, bool>> predicate);void RemoveRange(IEnumerable<T> entities);int ExecuteSqlCommand(string sql, params object[] parameters);IList<T> FromSql(string sql, params object[] parameters);void ChangeTable(string table);void ChangeState(T entity, EntityState state);EntityState GetState(T entity);void Reload(T entity);void TrackGraph(T rootEntity, Action<EntityEntryGraphNode> callback);void TrackGraph<TState>(T rootEntity, TState state, Func<EntityEntryGraphNode<TState>, bool> callback);IQueryable<T> ToQueryable(IQuery<T> query);IQueryable<TResult> ToQueryable<TResult>(IQuery<T, TResult> query);
}
//异步仓储方法接口
public interface IAsyncRepository<T> : IAsyncRepository, IRepository, IDisposable, IQueryFactory<T> where T : class
{Task<IList<T>> SearchAsync(IQuery<T> query, CancellationToken cancellationToken = default(CancellationToken));Task<IList<TResult>> SearchAsync<TResult>(IQuery<T, TResult> query, CancellationToken cancellationToken = default(CancellationToken));Task<T> SingleOrDefaultAsync(IQuery<T> query, CancellationToken cancellationToken = default(CancellationToken));Task<TResult> SingleOrDefaultAsync<TResult>(IQuery<T, TResult> query, CancellationToken cancellationToken = default(CancellationToken));Task<T> FirstOrDefaultAsync(IQuery<T> query, CancellationToken cancellationToken = default(CancellationToken));Task<TResult> FirstOrDefaultAsync<TResult>(IQuery<T, TResult> query, CancellationToken cancellationToken = default(CancellationToken));Task<T> LastOrDefaultAsync(IQuery<T> query, CancellationToken cancellationToken = default(CancellationToken));Task<TResult> LastOrDefaultAsync<TResult>(IQuery<T, TResult> query, CancellationToken cancellationToken = default(CancellationToken));Task<bool> AnyAsync(Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default(CancellationToken));Task<int> CountAsync(Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default(CancellationToken));Task<long> LongCountAsync(Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default(CancellationToken));Task<TResult> MaxAsync<TResult>(Expression<Func<T, TResult>> selector, Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default(CancellationToken));Task<TResult> MinAsync<TResult>(Expression<Func<T, TResult>> selector, Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default(CancellationToken));Task<decimal> AverageAsync(Expression<Func<T, decimal>> selector, Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default(CancellationToken));Task<decimal> SumAsync(Expression<Func<T, decimal>> selector, Expression<Func<T, bool>> predicate = null, CancellationToken cancellationToken = default(CancellationToken));Task<T> AddAsync(T entity, CancellationToken cancellationToken = default(CancellationToken));Task AddRangeAsync(IEnumerable<T> entities, CancellationToken cancellationToken = default(CancellationToken));Task<int> UpdateAsync(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> expression, CancellationToken cancellationToken = default(CancellationToken));Task<int> RemoveAsync(Expression<Func<T, bool>> predicate, CancellationToken cancellationToken = default(CancellationToken));Task<IList<T>> FromSqlAsync(string sql, IEnumerable<object> parameters = null, CancellationToken cancellationToken = default(CancellationToken));Task<int> ExecuteSqlCommandAsync(string sql, IEnumerable<object> parameters = null, CancellationToken cancellationToken = default(CancellationToken));Task ReloadAsync(T entity, CancellationToken cancellationToken = default(CancellationToken));
}
//泛型仓储的实现(列举一部分实现,感兴趣的可以自己查看源码)
public class Repository<T> : IRepository<T>, IRepository, IDisposable, ISyncRepository<T>, ISyncRepository, IQueryFactory<T>, IAsyncRepository<T>, IAsyncRepository where T : class
{private bool _disposed;protected DbContext DbContext { get; }protected DbSet<T> DbSet { get; }public Repository(DbContext dbContext){DbContext = dbContext ?? throw new ArgumentNullException("dbContext", "dbContext cannot be null.");DbSet = dbContext.Set<T>();}public virtual ISingleResultQuery<T> SingleResultQuery(){return EntityFrameworkCore.QueryBuilder.SingleResultQuery<T>.New();}public virtual IMultipleResultQuery<T> MultipleResultQuery(){return EntityFrameworkCore.QueryBuilder.MultipleResultQuery<T>.New();}public virtual ISingleResultQuery<T, TResult> SingleResultQuery<TResult>(){return SingleResultQuery<T, TResult>.New();}public virtual IMultipleResultQuery<T, TResult> MultipleResultQuery<TResult>(){return MultipleResultQuery<T, TResult>.New();}public virtual IList<T> Search(IQuery<T> query){if (query == null){throw new ArgumentNullException("query", "query cannot be null.");}return ToQueryable(query).ToList();}public virtual T Add(T entity){if (entity == null){throw new ArgumentNullException("entity", "entity cannot be null.");}DbSet.Add(entity);return entity;}public virtual void AddRange(IEnumerable<T> entities){if (entities == null){throw new ArgumentNullException("entities", "entities cannot be null.");}if (entities.Any()){DbSet.AddRange(entities);}}public virtual T Update(T entity, params Expression<Func<T, object>>[] properties){if (entity == null){throw new ArgumentNullException("entity", "entity cannot be null.");}if (properties != null && properties.Any()){EntityEntry<T> entityEntry = DbContext.Entry(entity);foreach (Expression<Func<T, object>> propertyExpression in properties){PropertyEntry propertyEntry;try{propertyEntry = entityEntry.Property(propertyExpression);}catch{propertyEntry = null;}if (propertyEntry != null){propertyEntry.IsModified = true;continue;}ReferenceEntry referenceEntry;try{referenceEntry = entityEntry.Reference(propertyExpression);}catch{referenceEntry = null;}if (referenceEntry != null){EntityEntry targetEntry = referenceEntry.TargetEntry;DbContext.Update(targetEntry.Entity);}}}else{DbSet.Update(entity);}return entity;}public virtual int Update(Expression<Func<T, bool>> predicate, Expression<Func<T, T>> expression){if (predicate == null){throw new ArgumentNullException("predicate", "predicate cannot be null.");}if (expression == null){throw new ArgumentNullException("expression", "expression cannot be null.");}return Queryable.Where(DbSet, predicate).Update(expression);}
public virtual T Remove(T entity){if (entity == null){throw new ArgumentNullException("entity", "entity cannot be null.");}DbSet.Remove(entity);return entity;}public virtual int Remove(Expression<Func<T, bool>> predicate){if (predicate == null){throw new ArgumentNullException("predicate", "predicate cannot be null.");}return Queryable.Where(DbSet, predicate).Delete();}public virtual void RemoveRange(IEnumerable<T> entities){if (entities == null){throw new ArgumentNullException("entities", "entities cannot be null.");}if (entities.Any()){DbSet.RemoveRange(entities);}}

四、 在WebApi项目中使用EntityFrameworkCore.Data.Repository

  1. 在appsettings.json中配置连接字符串
//配置连接字符串"ConnectionStrings": {"default": "Server=localhost;Database=20240114WebApplication;user=root;password=12345;port=3306"},
  1. 在Program.cs中注册相关服务
//注册DbContext服务
string connectionString = builder.Configuration.GetConnectionString("default");
builder.Services.AddDbContext<MyDbContext>(option => option.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)
));
builder.Services.AddScoped<DbContext, MyDbContext>();// 注册工作单元
builder.Services.AddUnitOfWork();
//builder.Services.AddUnitOfWork<MyDbContext>(); // 多数据库支持
//注册泛型仓储服务
builder.Services.AddScoped(typeof(Repository<>));
  1. 设计实体类,本例图书管理为例
 public class Book{public Book(){Title = string.Empty;ISBN = string.Empty;}[Key]public long Id { get; set; }[Required][MaxLength(100)]public string Title { get; set; }[Required][MaxLength(20)]public string ISBN { get; set; }public long CategoryId { get; set; }//导航属性[ForeignKey("CategoryId")]public virtual Category Category { get; set; }}public class Category
{public Category(){Name = string.Empty;Code = string.Empty;}[Key]public long Id { get; set; }/// <summary>/// 分类代码/// </summary>[Required][MaxLength(30)]public string Code { get; set; }/// <summary>/// 分类名/// </summary>[Required][MaxLength(30)]public string Name { get; set; }//导航属性public virtual IList<Book> Books { get; set; }
}
  1. 实现图书管理API接口

(1)依赖注入泛型仓储和工作单元对象:

[Route("api/[controller]")]
[ApiController]
public class BooksController : ControllerBase
{//泛型仓储private readonly Repository<Book> _bookRepository;private readonly Repository<Category> _categoryRepository;//工作单元private readonly IUnitOfWork _unitOfWork;//构造方法public BooksController(Repository<Book> bookRepository,Repository<Category> categoryRepository,IUnitOfWork unitOfWork){_bookRepository = bookRepository;_categoryRepository = categoryRepository;_unitOfWork = unitOfWork;}
}

(2)实现分页显示:

  //分页查询(使用Include加载导航属性)[HttpGet("GetPageList")]public async Task<ActionResult<IPagedList<BookOutput>>> GetPageList([FromQuery] BookPageRequestInput input){//创建查询对象-MultipleResultQuery表示多结果集查询var query = _bookRepository.MultipleResultQuery<BookOutput>().Page(input.PageIndex, input.PageSize) //分页.AndFilter(b => string.IsNullOrEmpty(input.Title) || b.Title.StartsWith(input.Title)) //筛选条件.Include(q => q.Include(x => x.Category))  //级联加载.OrderByDescending("Title").ThenBy("ISBN") //排序.Select(b => new BookOutput                //投影{CategoryId = b.CategoryId,CategoryCode = b.Category.Code,CategoryName = b.Category.Name,ISBN = b.ISBN,Title = b.Title,Id = b.Id}) as IMultipleResultQuery<Book, BookOutput>; //转换类型//执行查询var result = (await _bookRepository.SearchAsync(query)).ToPagedList(query.Paging.PageIndex,query.Paging.PageSize,query.Paging.TotalCount);return Ok(result);}
  //分页查询(使用IQueryable.Join方法进行联表查询)[HttpGet("GetBookPage")]public async Task<ActionResult<PagedList<BookOutput>>> GetBookPage([FromQuery] BookPageRequestInput input){//获取可IQueryable可查询对象var books = _bookRepository.ToQueryable(_bookRepository.MultipleResultQuery());var categories = _categoryRepository.ToQueryable(_categoryRepository.MultipleResultQuery());var query = books.Join(categories, b => b.CategoryId, c => c.Id,(b, c) => new BookOutput{CategoryId = b.CategoryId,CategoryCode = b.Category.Code,CategoryName = b.Category.Name,ISBN = b.ISBN,Title = b.Title,Id = b.Id}).Where(b => string.IsNullOrEmpty(input.Title) || b.Title.StartsWith(input.Title)).OrderBy(b => b.Id);PagedList<BookOutput> result = new PagedList<BookOutput>();result.TotalCount = await query.CountAsync();result.Items = await query.Skip((input.PageIndex - 1) * input.PageSize).Take(input.PageSize).ToListAsync();return result;}

(3)添加图书:

  //POST api/Books[HttpPost]public async Task<ActionResult<int>> Add([FromBody] BookAddOrUpdateInput input){Book book = new Book{CategoryId = input.CategoryId,ISBN = input.ISBN,Title = input.Title};await _bookRepository.AddAsync(book);var result = await _unitOfWork.SaveChangesAsync();return result;}

(4)修改图书:

   //PUT api/Books[HttpPut]public async Task<ActionResult<int>> Update([FromBody] BookAddOrUpdateInput input){var numAffected = await _bookRepository.UpdateAsync(b => b.Id == input.Id, b => new Book{CategoryId = input.CategoryId,ISBN = input.ISBN,Title = input.Title});var result = await _unitOfWork.SaveChangesAsync();return result;}

(5)删除图书:

   //DELETE api/Books/{id}[HttpDelete("{id}")]public async Task<ActionResult<int>> Delete(long id){var numAffected = await _bookRepository.RemoveAsync(b => b.Id == id);var result = await _unitOfWork.SaveChangesAsync();return result;}

(6)根据ID获取图书:

  //GET api/Books/{id}[HttpGet("{id}")]public async Task<ActionResult<BookOutput>> Get(long id){//创建查询对象,SingleResultQuery表示单条结果查询var query = _bookRepository.SingleResultQuery<BookOutput>().Include(q => q.Include(x => x.Category)).AndFilter(b => b.Id == id).Select(b => new BookOutput  //投影{CategoryId = b.CategoryId,CategoryCode = b.Category.Code,CategoryName = b.Category.Name,ISBN = b.ISBN,Title = b.Title,Id = b.Id});return await _bookRepository.SingleOrDefaultAsync(query);}

本次演示了在ASP.NET Core中使用泛型仓储模式封装EF Core的CRUD方法,推荐大家可以尝试一下EntityFrameworkCore.Data.Repository这个开源仓储实现类。如果本文对你有帮助的话,请点赞+评论+关注,或者转发给需要的朋友。

这篇关于【EFCore仓储模式】介绍一个EFCore的Repository实现的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/629878

相关文章

使用Python实现IP地址和端口状态检测与监控

《使用Python实现IP地址和端口状态检测与监控》在网络运维和服务器管理中,IP地址和端口的可用性监控是保障业务连续性的基础需求,本文将带你用Python从零打造一个高可用IP监控系统,感兴趣的小伙... 目录概述:为什么需要IP监控系统使用步骤说明1. 环境准备2. 系统部署3. 核心功能配置系统效果展

Python实现微信自动锁定工具

《Python实现微信自动锁定工具》在数字化办公时代,微信已成为职场沟通的重要工具,但临时离开时忘记锁屏可能导致敏感信息泄露,下面我们就来看看如何使用Python打造一个微信自动锁定工具吧... 目录引言:当微信隐私遇到自动化守护效果展示核心功能全景图技术亮点深度解析1. 无操作检测引擎2. 微信路径智能获

Python中pywin32 常用窗口操作的实现

《Python中pywin32常用窗口操作的实现》本文主要介绍了Python中pywin32常用窗口操作的实现,pywin32主要的作用是供Python开发者快速调用WindowsAPI的一个... 目录获取窗口句柄获取最前端窗口句柄获取指定坐标处的窗口根据窗口的完整标题匹配获取句柄根据窗口的类别匹配获取句

在 Spring Boot 中实现异常处理最佳实践

《在SpringBoot中实现异常处理最佳实践》本文介绍如何在SpringBoot中实现异常处理,涵盖核心概念、实现方法、与先前查询的集成、性能分析、常见问题和最佳实践,感兴趣的朋友一起看看吧... 目录一、Spring Boot 异常处理的背景与核心概念1.1 为什么需要异常处理?1.2 Spring B

Python位移操作和位运算的实现示例

《Python位移操作和位运算的实现示例》本文主要介绍了Python位移操作和位运算的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录1. 位移操作1.1 左移操作 (<<)1.2 右移操作 (>>)注意事项:2. 位运算2.1

如何在 Spring Boot 中实现 FreeMarker 模板

《如何在SpringBoot中实现FreeMarker模板》FreeMarker是一种功能强大、轻量级的模板引擎,用于在Java应用中生成动态文本输出(如HTML、XML、邮件内容等),本文... 目录什么是 FreeMarker 模板?在 Spring Boot 中实现 FreeMarker 模板1. 环

Qt实现网络数据解析的方法总结

《Qt实现网络数据解析的方法总结》在Qt中解析网络数据通常涉及接收原始字节流,并将其转换为有意义的应用层数据,这篇文章为大家介绍了详细步骤和示例,感兴趣的小伙伴可以了解下... 目录1. 网络数据接收2. 缓冲区管理(处理粘包/拆包)3. 常见数据格式解析3.1 jsON解析3.2 XML解析3.3 自定义

SpringMVC 通过ajax 前后端数据交互的实现方法

《SpringMVC通过ajax前后端数据交互的实现方法》:本文主要介绍SpringMVC通过ajax前后端数据交互的实现方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价... 在前端的开发过程中,经常在html页面通过AJAX进行前后端数据的交互,SpringMVC的controll

Spring Security自定义身份认证的实现方法

《SpringSecurity自定义身份认证的实现方法》:本文主要介绍SpringSecurity自定义身份认证的实现方法,下面对SpringSecurity的这三种自定义身份认证进行详细讲解,... 目录1.内存身份认证(1)创建配置类(2)验证内存身份认证2.JDBC身份认证(1)数据准备 (2)配置依

利用python实现对excel文件进行加密

《利用python实现对excel文件进行加密》由于文件内容的私密性,需要对Excel文件进行加密,保护文件以免给第三方看到,本文将以Python语言为例,和大家讲讲如何对Excel文件进行加密,感兴... 目录前言方法一:使用pywin32库(仅限Windows)方法二:使用msoffcrypto-too