1、前言
在前面的博客中,我们采用在Controller
的构造函数中直接注入数据库上下文DbContext
的方法实现数据库的一些常规操作,这种方法比较适合规模较小的项目。而一旦项目规模上升、数据库中的表的数量较多时,还是要考虑对EFCore
中的CURD
操作进行一下适度的封装,下面开始介绍。
2、通用泛型接口——IGenericService
EFCore
中每张表的CRUD
操作代码大同小异,唯一的区别就是DbSet
的泛型参数不同。如果要封装这些操作,我们自然会想到使用泛型来实现。首先定义一个IGenericService<TEntity>
,该接口作为通用接口,泛型参数TEntity
对应实体类,接口中包含了一系列的CRUD
操作,代码如下:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace App.Service
{
public interface IGenericService<TEntity> where TEntity : class, new()
{
/// <summary>
/// 检索实体集合
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <param name="order">排序表达式</param>
/// <param name="selection">投影表达式</param>
/// <param name="isTracking">是否跟踪实体</param>
/// <returns>实体集合</returns>
IQueryable<TEntity> Retrieve(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> order = null, Expression<Func<TEntity, TEntity>> selection = null, bool isTracking = true);
/// <summary>
/// 根据主键获取实体
/// </summary>
/// <param name="key">主键</param>
/// <returns>实体</returns>
TEntity Get(object key);
/// <summary>
/// 获取实体数量(int)
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>实体数量</returns>
int GetCount(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// 获取实体数量(long)
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>实体数量</returns>
long GetLongCount(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// 判断实体是否存在
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>是否存在</returns>
bool Exists(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// 创建实体
/// </summary>
/// <param name="entity">实体</param>
/// <returns>受影响的行数</returns>
int Create(TEntity entity);
/// <summary>
/// 批量创建实体
/// </summary>
/// <param name="entities">实体集合</param>
/// <returns>受影响的行数</returns>
int BatchCreate(IEnumerable<TEntity> entities);
/// <summary>
/// 删除实体
/// </summary>
/// <param name="key">主键</param>
/// <returns>受影响的行数</returns>
int Delete(object key);
/// <summary>
/// 删除实体
/// </summary>
/// <param name="entity">实体</param>
/// <returns>受影响的行数</returns>
int Delete(TEntity entity);
/// <summary>
/// 批量删除实体
/// </summary>
/// <param name="entities">实体集合</param>
/// <returns>受影响的行数</returns>
int BatchDelete(IEnumerable<TEntity> entities);
/// <summary>
/// 批量删除实体
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>受影响的行数</returns>
int BatchDelete(Expression<Func<TEntity, bool>> predicate = null);
/// <summary>
/// 更新实体
/// </summary>
/// <param name="entity">实体</param>
/// <param name="properties">需要更新的列</param>
/// <returns>受影响的行数</returns>
int Update(TEntity entity, params string[] properties);
/// <summary>
/// 批量更新实体
/// </summary>
/// <param name="entity">实体</param>
/// <param name="properties">需要更新的列</param>
/// <returns>受影响的行数</returns>
int BatchUpdate(IEnumerable<TEntity> entities, params string[] properties);
}
}
3、通用泛型类——GenericService
定义IGenericService
接口后,需要创建一个通用类GenericService<TDbContext, TEntity>
来实现该接口。这个通用类包含两个泛型参数,TDbContext
为数据库上下文,TEntity
为实体类。该类中包含一个数据库上下文对象,我们就利用它来实现CRUD
操作,代码如下:
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
namespace App.Service
{
public class GenericService<TDbContext, TEntity> : IGenericService<TEntity> where TDbContext : DbContext where TEntity : class, new()
{
protected readonly TDbContext _dbContext;
protected readonly DbSet<TEntity> _dbSet;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="dbContext">数据库上下文</param>
public GenericService(TDbContext dbContext)
{
_dbContext = dbContext;
_dbSet = dbContext.Set<TEntity>();
}
/// <summary>
/// 检索实体集合
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <param name="order">排序表达式</param>
/// <param name="selection">投影表达式</param>
/// <param name="isTracking">是否跟踪实体</param>
/// <returns>实体集合</returns>
public virtual IQueryable<TEntity> Retrieve(Expression<Func<TEntity, bool>> predicate = null, Func<IQueryable<TEntity>, IOrderedQueryable<TEntity>> order = null, Expression<Func<TEntity, TEntity>> selection = null, bool isTracking = true)
{
IQueryable<TEntity> query = isTracking ? _dbSet : _dbSet.AsNoTracking();
if (predicate != null)
{
query = query.Where(predicate);
}
if (order != null)
{
query = order(query);
}
if (selection != null)
{
query = query.Select(selection);
}
return query;
}
/// <summary>
/// 根据主键获取实体
/// </summary>
/// <param name="key">主键</param>
/// <returns>实体</returns>
public virtual TEntity Get(object key)
{
return _dbSet.Find(key);
}
/// <summary>
/// 获取实体数量(int)
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>实体数量</returns>
public virtual int GetCount(Expression<Func<TEntity, bool>> predicate = null)
{
return predicate == null ? _dbSet.Count() : _dbSet.Count(predicate);
}
/// <summary>
/// 获取实体数量(long)
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>实体数量</returns>
public virtual long GetLongCount(Expression<Func<TEntity, bool>> predicate = null)
{
return predicate == null ? _dbSet.LongCount() : _dbSet.LongCount(predicate);
}
/// <summary>
/// 判断实体是否存在
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>是否存在</returns>
public virtual bool Exists(Expression<Func<TEntity, bool>> predicate = null)
{
return predicate == null ? _dbSet.Any() : _dbSet.Any(predicate);
}
/// <summary>
/// 创建实体
/// </summary>
/// <param name="entity">实体</param>
/// <returns>受影响的行数</returns>
public virtual int Create(TEntity entity)
{
_dbSet.Add(entity);
return _dbContext.SaveChanges();
}
/// <summary>
/// 批量创建实体
/// </summary>
/// <param name="entities">实体集合</param>
/// <returns>受影响的行数</returns>
public virtual int BatchCreate(IEnumerable<TEntity> entities)
{
_dbSet.AddRange(entities);
return _dbContext.SaveChanges();
}
/// <summary>
/// 删除实体
/// </summary>
/// <param name="key">主键</param>
/// <returns>受影响的行数</returns>
public virtual int Delete(object key)
{
TEntity entity = _dbSet.Find(key);
if (entity != null)
{
_dbSet.Remove(entity);
}
return _dbContext.SaveChanges();
}
/// <summary>
/// 删除实体
/// </summary>
/// <param name="entity">实体</param>
/// <returns>受影响的行数</returns>
public virtual int Delete(TEntity entity)
{
_dbSet.Remove(entity);
return _dbContext.SaveChanges();
}
/// <summary>
/// 批量删除实体
/// </summary>
/// <param name="entities">实体集合</param>
/// <returns>受影响的行数</returns>
public virtual int BatchDelete(IEnumerable<TEntity> entities)
{
_dbSet.RemoveRange(entities);
return _dbContext.SaveChanges();
}
/// <summary>
/// 批量删除实体
/// </summary>
/// <param name="predicate">条件表达式</param>
/// <returns>受影响的行数</returns>
public virtual int BatchDelete(Expression<Func<TEntity, bool>> predicate = null)
{
IQueryable<TEntity> query = predicate == null ? _dbSet : _dbSet.Where(predicate);
_dbSet.RemoveRange(query);
return _dbContext.SaveChanges();
}
/// <summary>
/// 更新实体
/// </summary>
/// <param name="entity">实体</param>
/// <param name="properties">需要更新的列</param>
/// <returns>受影响的行数</returns>
public virtual int Update(TEntity entity, params string[] properties)
{
if (properties != null && properties.Length > 0)
{
_dbSet.Attach(entity);
foreach (string property in properties)
{
_dbContext.Entry(entity).Property(property).IsModified = true;
}
}
else
{
_dbSet.Update(entity);
}
return _dbContext.SaveChanges();
}
/// <summary>
/// 批量更新实体
/// </summary>
/// <param name="entity">实体</param>
/// <param name="properties">需要更新的列</param>
/// <returns>受影响的行数</returns>
public virtual int BatchUpdate(IEnumerable<TEntity> entities, params string[] properties)
{
if (properties != null && properties.Length > 0)
{
foreach (TEntity entity in entities)
{
_dbSet.Attach(entity);
foreach (string property in properties)
{
_dbContext.Entry(entity).Property(property).IsModified = true;
}
}
}
else
{
_dbSet.UpdateRange(entities);
}
return _dbContext.SaveChanges();
}
}
}
4、扩展接口——IAuthorService
在实现通用泛型接口之后,数据库中各个表的CRUD
操作已经基本实现了。但实际业务往往比较复杂,一些特定的需求无法通过通用泛型接口实现,此时就需要对接口进行功能上的扩展。这里定义一个IAuthorService
,该接口继承IGenericService
,同时定义了一个查询每个Author
对应Book
的方法,代码如下:
using App.Models;
using System.Collections.Generic;
namespace App.Service
{
public interface IAuthorService : IGenericService<Author>
{
/// <summary>
/// 查询每个Author对应的Book
/// </summary>
/// <returns>AuthorBooks</returns>
IEnumerable<AuthorBooks> GetAuthorBooks();
}
public class AuthorBooks
{
/// <summary>
/// 编号
/// </summary>
public int Id { get; set; }
/// <summary>
/// 作者姓名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 书名
/// </summary>
public string Title { get; set; }
}
}
5、扩展类——AuthorService
定义好IAuthorService
之后,需要定义一个AuthorService
类来实现该接口。该类继承通用泛型类GenericService
,同时实现了扩展接口IAuthorService
,代码如下:
using App.Context;
using App.Models;
using System.Collections.Generic;
using System.Linq;
namespace App.Service
{
public class AuthorService : GenericService<DaoDbContext, Author>, IAuthorService
{
/// <summary>
/// 构造函数
/// </summary>
/// <param name="dbContext">数据库上下文</param>
public AuthorService(DaoDbContext dbContext) : base(dbContext)
{
}
/// <summary>
/// 查询每个Author对应的Book
/// </summary>
/// <returns>AuthorBooks</returns>
public IEnumerable<AuthorBooks> GetAuthorBooks()
{
IQueryable<AuthorBooks> query = from a in _dbContext.Set<Author>()
join b in _dbContext.Set<Book>() on a.Id equals b.AuthorId into g
from r in g.DefaultIfEmpty()
select new AuthorBooks
{
Id = a.Id,
Name = a.Name,
Title = r.Title
};
return query.ToList();
}
}
}
6、注册IAuthorService接口
现在IIAuthorService
接口已经实现了,接下来只需要在Startup.cs
中注册即可,代码如下:
using App.Context;
using App.Service;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace App
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
// 添加控制器
services.AddControllers();
// 添加数据库上下文
services.AddDbContext<DaoDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("ConnectionString"));
});
// 添加IAuthorService
services.AddScoped<IAuthorService, AuthorService>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
}
7、Controller注入IAuthorService接口
最后在Controller
的构造函数中注入IAuthorService
接口即可,代码如下:
using App.Models;
using App.Service;
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Linq;
namespace App.Controllers
{
[Route("api/[controller]/[action]")]
[ApiController]
public class AuthorController : ControllerBase
{
protected readonly IAuthorService _service;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="service"></param>
public AuthorController(IAuthorService service)
{
_service = service;
}
/// <summary>
/// 根据性别查询Author集合
/// </summary>
/// <param name="gender">性别</param>
/// <returns>实体集合</returns>
[HttpGet]
public ActionResult<IEnumerable<Author>> GetAuthors(string gender)
{
if (string.IsNullOrEmpty(gender) || string.IsNullOrWhiteSpace(gender))
{
return BadRequest("参数错误");
}
return _service.Retrieve(p => p.Gender == gender, p => p.OrderByDescending(m => m.Id), p => new Author
{
Id = p.Id,
Name = p.Name,
Gender = p.Gender
}).ToList();
}
/// <summary>
/// 根据Id查询Author
/// </summary>
/// <param name="id">主键Id</param>
/// <returns>实体</returns>
[HttpGet]
public ActionResult<Author> GetAuthor(int id)
{
if (id <= 0)
{
return BadRequest("参数错误");
}
return _service.Get(id);
}
/// <summary>
/// 查询每个Author对应的Book
/// </summary>
/// <returns></returns>
[HttpGet]
public ActionResult<IEnumerable<AuthorBooks>> GetAuthorBooks()
{
return _service.GetAuthorBooks().ToList();
}
/// <summary>
/// 创建Author实体
/// </summary>
/// <param name="author"></param>
/// <returns></returns>
[HttpPost]
public ActionResult<string> Create([FromBody] Author author)
{
if (string.IsNullOrEmpty(author.Name) || string.IsNullOrWhiteSpace(author.Name))
{
return BadRequest("姓名不能为空");
}
return _service.Create(author) > 0 ? "创建实体成功" : "创建实体失败";
}
}
}
8、结语
本文主要介绍了如何封装EFCore
中常见的CRUD
操作。在原先.NET Framework
时代,开发者会封装仓储层Repository
和工作单元UnitOfWork
,虽然我之前也会这么做,但后来发现很多情况下其实不需要这两层,EFCore
本身就实现了仓储和工作单元,我们只需要对一些常规操作提取出来进行轻量级的封装即可。