ASP.NET Core 3.1系列(22)——封装EFCore中通用的CRUD操作

32 篇文章 43 订阅

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本身就实现了仓储和工作单元,我们只需要对一些常规操作提取出来进行轻量级的封装即可。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值