360建设网站免费下载,做死活题网站,用墨刀做网站后台原型,asp.net做学校网站首页阅读本文你的收获
了解仓储模式及泛型仓储的优点学会封装泛型仓储的一般设计思路学习在ASP.NET Core WebAPI项目中使用EntityFrameworkCore.Data.Repository
本文中的案例是微软EntityFrameworkCore的一个仓储模式实现#xff0c;这个仓储库不是我自己写的#xff0c;而是使…阅读本文你的收获
了解仓储模式及泛型仓储的优点学会封装泛型仓储的一般设计思路学习在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包主要有
EntityFrameworkCore.Data.Repository – 一个老外封装好的开源EfCore仓储实现EntityFrameworkCore.Data.UnitOfWork-- 跟以上配套的工作单元Pomelo.EntityFrameworkCore.MySql – MySQL数据库提供程序
简单剖析一下 EntityFrameworkCore.Data.Repository
可以看到作者定义了泛型仓储接口如下
public interface IRepositoryT : IRepository, IDisposable, ISyncRepositoryT, ISyncRepository, IQueryFactoryT, IAsyncRepositoryT, IAsyncRepository where T : class
{
}IRepository T 接口分别集继承了 ISyncRepository T 和 IAsyncRepository T 这两个接口分别是同步仓储方法接口和异步仓储方法接口。
//同步仓储方法接口
public interface ISyncRepositoryT : ISyncRepository, IRepository, IDisposable, IQueryFactoryT where T : class
{IListT Search(IQueryT query);IListTResult SearchTResult(IQueryT, TResult query);T SingleOrDefault(IQueryT query);TResult SingleOrDefaultTResult(IQueryT, TResult query);T FirstOrDefault(IQueryT query);TResult FirstOrDefaultTResult(IQueryT, TResult query);T LastOrDefault(IQueryT query);TResult LastOrDefaultTResult(IQueryT, TResult query);bool Any(ExpressionFuncT, bool predicate null);int Count(ExpressionFuncT, bool predicate null);long LongCount(ExpressionFuncT, bool predicate null);TResult MaxTResult(ExpressionFuncT, TResult selector, ExpressionFuncT, bool predicate null);TResult MinTResult(ExpressionFuncT, TResult selector, ExpressionFuncT, bool predicate null);decimal Average(ExpressionFuncT, decimal selector, ExpressionFuncT, bool predicate null);decimal Sum(ExpressionFuncT, decimal selector, ExpressionFuncT, bool predicate null);T Attach(T entity);void AttachRange(IEnumerableT entities);T Add(T entity);void AddRange(IEnumerableT entities);T Update(T entity, params ExpressionFuncT, object[] properties);int Update(ExpressionFuncT, bool predicate, ExpressionFuncT, T expression);void UpdateRange(IEnumerableT entities, params ExpressionFuncT, object[] properties);T Remove(T entity);int Remove(ExpressionFuncT, bool predicate);void RemoveRange(IEnumerableT entities);int ExecuteSqlCommand(string sql, params object[] parameters);IListT 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, ActionEntityEntryGraphNode callback);void TrackGraphTState(T rootEntity, TState state, FuncEntityEntryGraphNodeTState, bool callback);IQueryableT ToQueryable(IQueryT query);IQueryableTResult ToQueryableTResult(IQueryT, TResult query);
}//异步仓储方法接口
public interface IAsyncRepositoryT : IAsyncRepository, IRepository, IDisposable, IQueryFactoryT where T : class
{TaskIListT SearchAsync(IQueryT query, CancellationToken cancellationToken default(CancellationToken));TaskIListTResult SearchAsyncTResult(IQueryT, TResult query, CancellationToken cancellationToken default(CancellationToken));TaskT SingleOrDefaultAsync(IQueryT query, CancellationToken cancellationToken default(CancellationToken));TaskTResult SingleOrDefaultAsyncTResult(IQueryT, TResult query, CancellationToken cancellationToken default(CancellationToken));TaskT FirstOrDefaultAsync(IQueryT query, CancellationToken cancellationToken default(CancellationToken));TaskTResult FirstOrDefaultAsyncTResult(IQueryT, TResult query, CancellationToken cancellationToken default(CancellationToken));TaskT LastOrDefaultAsync(IQueryT query, CancellationToken cancellationToken default(CancellationToken));TaskTResult LastOrDefaultAsyncTResult(IQueryT, TResult query, CancellationToken cancellationToken default(CancellationToken));Taskbool AnyAsync(ExpressionFuncT, bool predicate null, CancellationToken cancellationToken default(CancellationToken));Taskint CountAsync(ExpressionFuncT, bool predicate null, CancellationToken cancellationToken default(CancellationToken));Tasklong LongCountAsync(ExpressionFuncT, bool predicate null, CancellationToken cancellationToken default(CancellationToken));TaskTResult MaxAsyncTResult(ExpressionFuncT, TResult selector, ExpressionFuncT, bool predicate null, CancellationToken cancellationToken default(CancellationToken));TaskTResult MinAsyncTResult(ExpressionFuncT, TResult selector, ExpressionFuncT, bool predicate null, CancellationToken cancellationToken default(CancellationToken));Taskdecimal AverageAsync(ExpressionFuncT, decimal selector, ExpressionFuncT, bool predicate null, CancellationToken cancellationToken default(CancellationToken));Taskdecimal SumAsync(ExpressionFuncT, decimal selector, ExpressionFuncT, bool predicate null, CancellationToken cancellationToken default(CancellationToken));TaskT AddAsync(T entity, CancellationToken cancellationToken default(CancellationToken));Task AddRangeAsync(IEnumerableT entities, CancellationToken cancellationToken default(CancellationToken));Taskint UpdateAsync(ExpressionFuncT, bool predicate, ExpressionFuncT, T expression, CancellationToken cancellationToken default(CancellationToken));Taskint RemoveAsync(ExpressionFuncT, bool predicate, CancellationToken cancellationToken default(CancellationToken));TaskIListT FromSqlAsync(string sql, IEnumerableobject parameters null, CancellationToken cancellationToken default(CancellationToken));Taskint ExecuteSqlCommandAsync(string sql, IEnumerableobject parameters null, CancellationToken cancellationToken default(CancellationToken));Task ReloadAsync(T entity, CancellationToken cancellationToken default(CancellationToken));
}//泛型仓储的实现列举一部分实现感兴趣的可以自己查看源码
public class RepositoryT : IRepositoryT, IRepository, IDisposable, ISyncRepositoryT, ISyncRepository, IQueryFactoryT, IAsyncRepositoryT, IAsyncRepository where T : class
{private bool _disposed;protected DbContext DbContext { get; }protected DbSetT DbSet { get; }public Repository(DbContext dbContext){DbContext dbContext ?? throw new ArgumentNullException(dbContext, dbContext cannot be null.);DbSet dbContext.SetT();}public virtual ISingleResultQueryT SingleResultQuery(){return EntityFrameworkCore.QueryBuilder.SingleResultQueryT.New();}public virtual IMultipleResultQueryT MultipleResultQuery(){return EntityFrameworkCore.QueryBuilder.MultipleResultQueryT.New();}public virtual ISingleResultQueryT, TResult SingleResultQueryTResult(){return SingleResultQueryT, TResult.New();}public virtual IMultipleResultQueryT, TResult MultipleResultQueryTResult(){return MultipleResultQueryT, TResult.New();}public virtual IListT Search(IQueryT 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(IEnumerableT 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 ExpressionFuncT, object[] properties){if (entity null){throw new ArgumentNullException(entity, entity cannot be null.);}if (properties ! null properties.Any()){EntityEntryT entityEntry DbContext.Entry(entity);foreach (ExpressionFuncT, 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(ExpressionFuncT, bool predicate, ExpressionFuncT, 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(ExpressionFuncT, bool predicate){if (predicate null){throw new ArgumentNullException(predicate, predicate cannot be null.);}return Queryable.Where(DbSet, predicate).Delete();}public virtual void RemoveRange(IEnumerableT entities){if (entities null){throw new ArgumentNullException(entities, entities cannot be null.);}if (entities.Any()){DbSet.RemoveRange(entities);}}四、 在WebApi项目中使用EntityFrameworkCore.Data.Repository
在appsettings.json中配置连接字符串
//配置连接字符串ConnectionStrings: {default: Serverlocalhost;Database20240114WebApplication;userroot;password12345;port3306},在Program.cs中注册相关服务
//注册DbContext服务
string connectionString builder.Configuration.GetConnectionString(default);
builder.Services.AddDbContextMyDbContext(option option.UseMySql(connectionString, ServerVersion.AutoDetect(connectionString)
));
builder.Services.AddScopedDbContext, MyDbContext();// 注册工作单元
builder.Services.AddUnitOfWork();
//builder.Services.AddUnitOfWorkMyDbContext(); // 多数据库支持
//注册泛型仓储服务
builder.Services.AddScoped(typeof(Repository));
设计实体类本例图书管理为例 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 IListBook Books { get; set; }
}实现图书管理API接口
1依赖注入泛型仓储和工作单元对象
[Route(api/[controller])]
[ApiController]
public class BooksController : ControllerBase
{//泛型仓储private readonly RepositoryBook _bookRepository;private readonly RepositoryCategory _categoryRepository;//工作单元private readonly IUnitOfWork _unitOfWork;//构造方法public BooksController(RepositoryBook bookRepository,RepositoryCategory categoryRepository,IUnitOfWork unitOfWork){_bookRepository bookRepository;_categoryRepository categoryRepository;_unitOfWork unitOfWork;}
}2实现分页显示 //分页查询使用Include加载导航属性)[HttpGet(GetPageList)]public async TaskActionResultIPagedListBookOutput GetPageList([FromQuery] BookPageRequestInput input){//创建查询对象-MultipleResultQuery表示多结果集查询var query _bookRepository.MultipleResultQueryBookOutput().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 IMultipleResultQueryBook, 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 TaskActionResultPagedListBookOutput 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);PagedListBookOutput result new PagedListBookOutput();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 TaskActionResultint 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 TaskActionResultint 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 TaskActionResultint 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 TaskActionResultBookOutput Get(long id){//创建查询对象SingleResultQuery表示单条结果查询var query _bookRepository.SingleResultQueryBookOutput().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这个开源仓储实现类。如果本文对你有帮助的话请点赞评论关注或者转发给需要的朋友。