本文中的案例是微软EntityFrameworkCore的一个仓储模式实现,这个仓储库不是我自己写的,而是使用了一个老外写的EntityFrameworkCore.Data.Repository,大家可以一起来学习一下,如果你觉得合适,可以直接用在你的项目中,很方便。案例代码下载
仓储(Repository)模式自2004年首次作为领域驱动模型DDD设计的一部分引入,仓储本质上是提供数据的抽象,以便应用程序可以使用具有接口的相似的简单抽象集合。从此集合中CURD是通过一系列直接的方法完成,无需处理连接、命令等问题,使用此种模式可帮助实现松耦合,并保持领域对象的持久性无知。
下图为控制器和仓储协同工作的图例:
仓储(Repository)是存在于工作单元和数据库之间单独分离出来的一层,是对数据访问的封装。其优点是
如果我们采用的ORM框架是EF Core,实现仓储模式的话,那么类图设计一般如下:
通常实现仓储的时候,会使用泛型技术封装增删改查的的通用功能,类型T即为具体要进行增删改查处理的实体类型。
使用泛型仓储(Generic Repository)的好处有以下几点:
开发环境:
操作系统: Windows 10 专业版
平台版本是:.NET 6
开发框架:ASP.NET Core WebApi、Entity Framework Core
开发工具:Visual Studio 2022
数据库: MySQL 5.7+
使用的NuGet包主要有:
可以看到,作者定义了泛型仓储接口如下:
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);
}
}
//配置连接字符串
"ConnectionStrings": {
"default": "Server=localhost;Database=20240114WebApplication;user=root;password=12345;port=3306"
},
//注册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<>));
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)依赖注入泛型仓储和工作单元对象:
[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这个开源仓储实现类。如果本文对你有帮助的话,请点赞+评论+关注,或者转发给需要的朋友。