第一节:框架全新升级(快速搭建、必备中间件集成、BaseService改造、大数据处理)

一. 搭建基本框架,各层联调成功

1.  项目分层

新建:YpfCore.AdminWeb、YpfCore.Data、YpfCore.DTO、YpfCore.IService、YpfCore.Service、YpfCore.Utils,每层的作用如下:

 A. YpfCore.AdminWeb层:Api层,对外提供接口,供客户端调用。

 B. YpfCore.Data层:数据层,存放数据库实体映射类和相关配置类、EF上下文类。

 C. YpfCore.DTO层:数据传输对象层,存放一些业务逻辑实体,供UI层调用。

 D. YpfCore.IService层:业务接口层。

 E. YpfCore.Service层:业务层。

 F. YpfCore.Utils层:帮助类层

(PS:基本的搭建步骤与之前版本类似,这里重点是升级了版本,改进了一下写法,更详细的搭建过程参考:https://www.cnblogs.com/yaopengfei/p/14226644.html

 

2. 数据层构建

(1). EFCore相关程序集

  【Microsoft.EntityFrameworkCore.SqlServer】:用来连接SQLServer数据库,里面包含【Microsoft.EntityFrameworkCore.Relational】,而它里面又包含:【Microsoft.EntityFrameworkCore】

       【Microsoft.EntityFrameworkCore.Tools】:用来迁移数据库的,里面包含【Microsoft.EntityFrameworkCore.Design】

    【Microsoft.EntityFrameworkCore】【Microsoft.EntityFrameworkCore.Design】  

注:其它层均不需要单独引入程序集,因为已经添加对【YpfCore.Data】层的依赖了

(2). 日志程序集

  【Microsoft.Extensions.Logging】【Microsoft.Extensions.Logging.Debug】【Microsoft.Extensions.Logging.Console】

(3). 映射实体

  先注释掉可空配置代码,个人不喜欢  <Nullable>enable</Nullable>

【Scaffold-DbContext "Server=xxx;Database=Vue3AdminDB;User ID=vue3admin;Password=vue3admin123456;" Microsoft.EntityFrameworkCore.SqlServer -OutputDir Entity -Context CoreFrameDBContext -UseDatabaseNames -DataAnnotations -NoPluralize 】

(4). 配置日志

  optionsBuilder.UseLoggerFactory(LoggerFactory.Create(build =>
  {
                build.AddDebug();
                build.AddConsole();
  }));

 

3. 业务接口层构建

(1). 项目内的引用

 【YpfCore.Data】【YpfCore.DTO】

(2). 依赖程序集

 【Microsoft.EntityFrameworkCore】【System.Data.SqlClient】  (PS:引用的YpfCore.Data层中已经包含EFCore相关的所有程序集了)

(3). 核心代码

   新增IBaseService 和 ISupport接口,IBaseService用于定义EFCore上下文对DB操作的方法约束,ISupport为了标记后续哪些子类Service可以被注入到YpfCore.AdminWeb层。

IBaseService  

using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using YpfCore.DTO;

namespace YpfCore.IService.BaseInterface;

/// <summary>
/// 通用方法接口
/// </summary>
public interface IBaseService
{
    /****************************************下面进行方法的封装(同步)***********************************************/


    #region 01-数据源
    IQueryable<T> Entities<T>() where T : class;

    IQueryable<T> EntitiesNoTrack<T>() where T : class;

    #endregion

    #region 02-批量处理SaveChangesAsync()
    /// <summary>
    /// 02-批量处理SaveChangesAsync()
    /// </summary>
    /// <returns></returns>
    Task<int> SaveChangeAsync();

    #endregion

    #region 03-新增(单体)
    /// <summary>
    ///  03-新增(单体)
    /// </summary>
    /// <param name="model">需要新增的实体</param>
    Task AddAsync<T>(T model) where T : class;

    #endregion

    #region 04-新增(集合)
    /// <summary>
    /// 04-新增(集合)
    /// </summary>
    /// <param name="list">需要新增的集合</param>
    Task AddRangeAsync<T>(List<T> list) where T : class;

    #endregion

    #region 05-删除(单体)-无异步
    /// <summary>
    /// 05-删除(单体)-无异步
    /// </summary>
    /// <param name="model">需要删除的实体</param>
    /// <returns></returns>
    void DelAsync<T>(T model) where T : class;

    #endregion

    #region 06-删除(集合)-无异步
    /// <summary>
    /// 06-删除(集合)-无异步
    /// </summary>
    /// <param name="list">需要删除的集合</param>
    /// <returns></returns>
    void DelRangeAsync<T>(List<T> list) where T : class;

    #endregion

    #region 07-根据条件删除(查询删除一体化)
    /// <summary>
    /// 07-根据条件删除(查询删除一体化)
    /// </summary>
    /// <param name="delWhere">需要删除的条件</param>
    Task DelByAsync<T>(Expression<Func<T, bool>> delWhere) where T : class;

    #endregion

    #region 08-单实体修改【适用于没有状态追踪的实体】-无异步
    /// <summary>
    /// 08-单实体修改【适用于没有状态追踪的实体】
    /// </summary>
    /// <param name="model">需要修改的实体</param>
    /// <returns></returns>
    void Modify<T>(T model) where T : class;

    #endregion

    #region 09-根据条件查询
    /// <summary>
    /// 09-根据条件查询
    /// </summary>
    /// <param name="whereLambda">查询条件(lambda表达式的形式生成表达式目录树)</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    Task<List<T>> GetListByAsync<T>(Expression<Func<T, bool>> whereLambda, bool isTrack = true) where T : class;

    #endregion

    #region 10-根据条件排序和查询
    /// <summary>
    /// 10-根据条件排序和查询
    /// </summary>
    /// <typeparam name="Tkey">排序字段类型</typeparam>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="orderLambda">排序条件</param>
    /// <param name="isAsc">升序or降序</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    Task<List<T>> GetListByAsync<T, Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda,
        bool isAsc = true, bool isTrack = true) where T : class;

    #endregion

    #region 11-分页查询(根据Lambda排序)
    /// <summary>
    /// 11-分页查询(根据Lambda排序)
    /// </summary>
    /// <typeparam name="Tkey">排序字段类型</typeparam>
    /// <param name="pageIndex">页码</param>
    /// <param name="pageSize">页容量</param>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="orderLambda">排序条件</param>
    /// <param name="isAsc">升序or降序</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    Task<List<T>> GetPageListAsync<T, Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda,
          Expression<Func<T, Tkey>> orderLambda, bool isAsc = true, bool isTrack = true) where T : class;

    #endregion

    #region 12-分页查询(根据名称排序)
    /// <summary>
    /// 12-分页查询(根据名称排序)
    /// 需要依赖:YpfCore.Utils/Extensions/SortExtension
    /// </summary>
    /// <param name="pageIndex">页码</param>
    /// <param name="pageSize">每页的条数</param>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="sortName">排序字段名称</param>
    /// <param name="sortDirection">asc 或 desc</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    Task<List<T>> GetPageListByNameAsync<T>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda,
       string sortName, string sortDirection, bool isTrack = true) where T : class;

    #endregion

    #region 13-执行增加,删除,修改SQL操作
    /// <summary>
    /// 执行增加,删除,修改SQL操作
    /// 【自动进行参数化处理,防止SQL注入】
    /// </summary>
    /// <param name="sql">sql脚本</param>
    /// <returns></returns>
    Task<int> ExecuteSqlInterpolatedAsync(FormattableString sql);

    #endregion

    #region 14-执行查询SQL操作【适用于单表】
    /// <summary>
    /// 14-执行查询SQL操作【适用于单表】
    /// </summary>
    /// <typeparam name="T">表实体</typeparam>
    /// <param name="sql">sql脚本</param>
    /// <returns></returns>
    Task<List<T>> FromSqlInterpolated<T>(FormattableString sql) where T : class;

    #endregion

    #region 15-封装开启事务代码
    /// <summary>
    /// 15-封装开启事务代码
    /// </summary>
    /// <param name="Func">业务函数回调,传递参数为db,返回task对象</param>
    /// <returns>
    ///  status="ok":代表事务成功
    ///  status="error":代表事务失败
    /// </returns>
    Task<TransResult> BeginTransactionAsync(Func<DbContext, Task> Func);
    #endregion







    /****************************************下面 EFCore.BulkExtensions封装【异步,仅支持SQLServer】***********************************************/

    #region 01-批量插入
    /// <summary>
    /// 01-批量插入
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="list">实体集合</param>
    /// <returns></returns>
    Task BulkInsertAsync<T>(List<T> list) where T : class;
    #endregion

    #region 02-批量删除
    /// <summary>
    /// 02-批量删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="delWhere">删除条件</param>
    /// <returns></returns>
    Task<int> BatchDeleteAsync<T>(Expression<Func<T, bool>> delWhere) where T : class;
    #endregion

    #region 03-全表truncate删除
    /// <summary>
    /// 03-全表truncate删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <returns></returns>
    Task TruncateAsync<T>() where T : class;
    #endregion

    #region  04-批量修改-写法1(实体模式)
    /// <summary>
    /// 04-批量修改-写法1(实体模式)
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="updateWhere">更新条件</param>
    /// <param name="model">更新实体</param>
    /// <returns></returns>
    Task<int> BatchUpdateAsync<T>(Expression<Func<T, bool>> updateWhere, T model) where T : class;
    #endregion

    #region  05-批量修改-写法2(表达式模式,支持在原值的基础上进行修改)
    /// <summary>
    /// 05-批量修改-写法2(表达式模式,支持在原值的基础上进行修改)
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="updateWhere">更新条件</param>
    /// <param name="updataExpression">更新实体表达式</param>
    /// <returns></returns>
    Task<int> BatchUpdateAsync<T>(Expression<Func<T, bool>> updateWhere, Expression<Func<T, T>> updataExpression) where T : class;
    #endregion


    /****************************************下面Zack.EFCore.Batch封装【异步,支持SQLServer和MySQL】***********************************************/


    #region 01-批量插入
    /// <summary>
    /// 01-批量插入
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="list">实体集合</param>
    /// <returns></returns>
    Task InsertBulkAsync<T>(List<T> list) where T : class;

    #endregion

    #region  02-批量删除
    /// <summary>
    /// 02-批量删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="delWhere">删除条件</param>
    /// <returns></returns>
    Task<int> DeleteRangeAsync<T>(Expression<Func<T, bool>> delWhere) where T : class;
    #endregion







}
View Code

ISupport 

    /// <summary>
    /// 一个标记接口,只有实现该接口的类才进行注入
    /// </summary>
    public interface ISupport
    {
    }

 

4. 业务层构建

(1). 项目内的引用

    【YpfCore.Data】【YpfCore.DTO】【YpfCore.IService】【YpfCore.DTO】【YpfCore.Utils】

(2). 依赖程序集

   【Microsoft.EntityFrameworkCore】【Microsoft.EntityFrameworkCore.SqlServer】 

(3). 将程序集的输出路径改为:..\YpfCore.AdminWeb\bin\,以便后续与YpfCore.AdminWeb层解耦。(此处和旧版本不一样哦)

(4).核心代码

BaseService 

using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using YpfCore.Data.Entity;
using YpfCore.DTO;
using YpfCore.IService.BaseInterface;
using YpfCore.Utils.Extensions;

namespace YpfCore.Service.BaseClass;

/// <summary>
/// 泛型方法,直接注入EF上下文
/// </summary>
public class BaseService : IBaseService, ISupport
{
    public DbContext db;

    /// <summary>
    /// 在使用的时候,自动注入db上下文
    /// </summary>
    /// <param name="db"></param>
    public BaseService(CoreFrameDBContext db)
    {
        this.db = db;

        //关闭全局追踪的代码
        //db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    }

    /****************************************下面EFCore基础方法的封装【异步】***********************************************/
    /*
       PS: 这里所有的方法savechange都隔离出来,不再合并封装
    */


    #region 01-数据源
    public IQueryable<T> Entities<T>() where T : class
    {
        return db.Set<T>();
    }

    public IQueryable<T> EntitiesNoTrack<T>() where T : class
    {
        return db.Set<T>().AsNoTracking();
    }

    #endregion

    #region 02-批量处理SaveChangesAsync()
    /// <summary>
    /// 02-批量处理SaveChangesAsync()
    /// </summary>
    /// <returns></returns>
    public async Task<int> SaveChangeAsync()
    {
        return await db.SaveChangesAsync();
    }
    #endregion

    #region 03-新增(单体)
    /// <summary>
    ///  03-新增(单体)
    /// </summary>
    /// <param name="model">需要新增的实体</param>
    public async Task AddAsync<T>(T model) where T : class
    {
        await db.AddAsync(model);
    }
    #endregion

    #region 04-新增(集合)
    /// <summary>
    /// 04-新增(集合)
    /// </summary>
    /// <param name="list">需要新增的集合</param>
    public async Task AddRangeAsync<T>(List<T> list) where T : class
    {
        await db.AddRangeAsync(list);
    }
    #endregion

    #region 05-删除(单体)-无异步
    /// <summary>
    /// 05-删除(单体)-无异步
    /// </summary>
    /// <param name="model">需要删除的实体</param>
    /// <returns></returns>
    public void DelAsync<T>(T model) where T : class
    {
        db.Remove(model);
    }
    #endregion

    #region 06-删除(集合)-无异步
    /// <summary>
    /// 06-删除(集合)-无异步
    /// </summary>
    /// <param name="list">需要删除的集合</param>
    /// <returns></returns>
    public void DelRangeAsync<T>(List<T> list) where T : class
    {
        db.RemoveRange(list);
    }
    #endregion

    #region 07-根据条件删除(查询删除一体化)
    /// <summary>
    /// 07-根据条件删除(查询删除一体化)
    /// </summary>
    /// <param name="delWhere">需要删除的条件</param>
    public async Task DelByAsync<T>(Expression<Func<T, bool>> delWhere) where T : class
    {
        List<T> listDels = await db.Set<T>().Where(delWhere).ToListAsync();
        listDels.ForEach(model =>
        {
            db.Entry(model).State = EntityState.Deleted;
        });
    }
    #endregion

    #region 08-单实体修改【适用于没有状态追踪的实体】-无异步
    /// <summary>
    /// 08-单实体修改【适用于没有状态追踪的实体】
    /// </summary>
    /// <param name="model">需要修改的实体</param>
    /// <returns></returns>
    public void Modify<T>(T model) where T : class
    {
        db.Entry(model).State = EntityState.Modified;
    }
    #endregion

    #region 09-根据条件查询
    /// <summary>
    /// 09-根据条件查询
    /// </summary>
    /// <param name="whereLambda">查询条件(lambda表达式的形式生成表达式目录树)</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetListByAsync<T>(Expression<Func<T, bool>> whereLambda, bool isTrack = true) where T : class
    {
        if (isTrack)
        {
            return await db.Set<T>().Where(whereLambda).ToListAsync();
        }
        else
        {
            return await db.Set<T>().Where(whereLambda).AsNoTracking().ToListAsync();
        }

    }
    #endregion

    #region 10-根据条件排序和查询
    /// <summary>
    /// 10-根据条件排序和查询
    /// </summary>
    /// <typeparam name="Tkey">排序字段类型</typeparam>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="orderLambda">排序条件</param>
    /// <param name="isAsc">升序or降序</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetListByAsync<T, Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda,
        bool isAsc = true, bool isTrack = true) where T : class
    {
        IQueryable<T> data;
        if (isTrack)
        {
            data = db.Set<T>().Where(whereLambda);
        }
        else
        {
            data = db.Set<T>().Where(whereLambda).AsNoTracking();
        }
        if (isAsc)
        {
            data = data.OrderBy(orderLambda);
        }
        else
        {
            data = data.OrderByDescending(orderLambda);
        }
        return await data.ToListAsync();
    }
    #endregion

    #region 11-分页查询(根据Lambda排序)
    /// <summary>
    /// 11-分页查询(根据Lambda排序)
    /// </summary>
    /// <typeparam name="Tkey">排序字段类型</typeparam>
    /// <param name="pageIndex">页码</param>
    /// <param name="pageSize">页容量</param>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="orderLambda">排序条件</param>
    /// <param name="isAsc">升序or降序</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetPageListAsync<T, Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda,
        Expression<Func<T, Tkey>> orderLambda, bool isAsc = true, bool isTrack = true) where T : class
    {

        IQueryable<T> data = null;
        if (isTrack)
        {
            data = db.Set<T>().Where(whereLambda);
        }
        else
        {
            data = db.Set<T>().Where(whereLambda).AsNoTracking();
        }
        if (isAsc)
        {
            data = data.OrderBy(orderLambda).Skip((pageIndex - 1) * pageSize).Take(pageSize);
        }
        else
        {
            data = data.OrderByDescending(orderLambda).Skip((pageIndex - 1) * pageSize).Take(pageSize);
        }
        return await data.ToListAsync();
    }
    #endregion

    #region 12-分页查询(根据名称排序)
    /// <summary>
    /// 12-分页查询(根据名称排序)
    /// 需要依赖:YpfCore.Utils/Extensions/SortExtension
    /// </summary>
    /// <param name="pageIndex">页码</param>
    /// <param name="pageSize">每页的条数</param>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="sortName">排序字段名称</param>
    /// <param name="sortDirection">asc 或 desc</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetPageListByNameAsync<T>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda,
        string sortName, string sortDirection, bool isTrack = true) where T : class
    {
        List<T> list;
        if (isTrack)
        {
            list = await db.Set<T>().Where(whereLambda).DataSorting(sortName, sortDirection)
             .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
        }
        else
        {
            list = await db.Set<T>().Where(whereLambda).AsNoTracking().DataSorting(sortName, sortDirection)
             .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
        }
        return list;
    }
    #endregion

    #region 13-执行增加,删除,修改SQL操作
    /// <summary>
    /// 执行增加,删除,修改SQL操作
    /// 【自动进行参数化处理,防止SQL注入】
    /// </summary>
    /// <param name="sql">sql脚本</param>
    /// <returns></returns>
    public async Task<int> ExecuteSqlInterpolatedAsync(FormattableString sql)
    {
        return await db.Database.ExecuteSqlInterpolatedAsync(sql);
    }
    #endregion

    #region 14-执行查询SQL操作【适用于单表】
    /// <summary>
    /// 14-执行查询SQL操作【适用于单表】
    /// </summary>
    /// <typeparam name="T">表实体</typeparam>
    /// <param name="sql">sql脚本</param>
    /// <returns></returns>
    public async Task<List<T>> FromSqlInterpolated<T>(FormattableString sql) where T : class
    {
        return await db.Set<T>().FromSqlInterpolated(sql).ToListAsync();
    }
    #endregion

    #region 15-封装开启事务代码
    /// <summary>
    /// 15-封装开启事务代码
    /// </summary>
    /// <param name="Func">业务函数回调,传递参数为db, 返回task</param>
    /// <returns>
    ///  status="ok":代表事务成功
    ///  status="error":代表事务失败
    /// </returns>
    public async Task<TransResult> BeginTransactionAsync(Func<DbContext, Task> Func)
    {
        using (var transaction = await db.Database.BeginTransactionAsync())
        {
            try
            {
                await Func(db); //执行业务

                await transaction.CommitAsync();  //最终事务提交

                return new TransResult() { status = "ok" };
            }
            catch (Exception ex)
            {
                //using包裹不需要手写rollback
                return new TransResult() { status = "error", ex = ex };
            }
        }
    }

    #endregion


    /****************************************下面 EFCore.BulkExtensions封装【异步,仅支持SQLServer】***********************************************/

    #region 01-批量插入
    /// <summary>
    /// 01-批量插入
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="list">实体集合</param>
    /// <returns></returns>
    public async Task BulkInsertAsync<T>(List<T> list) where T : class
    {
        await db.BulkInsertAsync(list);
    }
    #endregion

    #region 02-批量删除
    /// <summary>
    /// 02-批量删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="delWhere">删除条件</param>
    /// <returns></returns>
    public async Task<int> BatchDeleteAsync<T>(Expression<Func<T, bool>> delWhere) where T : class
    {
        int count = await db.Set<T>().Where(delWhere).BatchDeleteAsync();
        return count;
    }
    #endregion

    #region 03-全表truncate删除
    /// <summary>
    /// 03-全表truncate删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <returns></returns>
    public async Task TruncateAsync<T>() where T : class
    {
        await db.TruncateAsync<T>();
    }
    #endregion

    #region 04-批量修改-写法1(实体模式)
    /// <summary>
    /// 04-批量修改-写法1(实体模式)
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="updateWhere">更新条件</param>
    /// <param name="model">更新实体</param>
    /// <returns></returns>
    public async Task<int> BatchUpdateAsync<T>(Expression<Func<T, bool>> updateWhere, T model) where T : class
    {
        int count = await db.Set<T>().Where(updateWhere).BatchUpdateAsync(model);
        return count;
    }
    #endregion

    #region 05-批量修改-写法2(表达式模式,支持在原值的基础上进行修改)
    /// <summary>
    /// 05-批量修改-写法2(表达式模式,支持在原值的基础上进行修改)
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="updateWhere">更新条件</param>
    /// <param name="updataExpression">更新实体表达式</param>
    /// <returns></returns>
    public async Task<int> BatchUpdateAsync<T>(Expression<Func<T, bool>> updateWhere, Expression<Func<T, T>> updataExpression) where T : class
    {
        int count = await db.Set<T>().Where(updateWhere).BatchUpdateAsync(updataExpression);
        return count;
    }
    #endregion



    /****************************************下面Zack.EFCore.Batch封装【异步,支持SQLServer和MySQL】***********************************************/
    /*
        PS: 批量修改:二次封装反而更加繁琐,所以,这里不封装了
     */

    #region  01-批量插入
    /// <summary>
    /// 01-批量插入
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="list">实体集合</param>
    /// <returns></returns>
    public async Task InsertBulkAsync<T>(List<T> list) where T : class
    {
        //await db.BulkInsertAsync(list);  

        //由于BulkInsertAsync和上述【EFCore.BulkExtensions】程序集名称重复,所以这里采用扩展方法来处理
        await MSSQLBulkInsertExtensions.BulkInsertAsync(db, list);
    }
    #endregion

    #region 02-批量删除
    /// <summary>
    /// 02-批量删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="delWhere">删除条件</param>
    /// <returns></returns>
    public async Task<int> DeleteRangeAsync<T>(Expression<Func<T, bool>> delWhere) where T : class
    {
        int count = await db.DeleteRangeAsync<T>(delWhere);
        return count;
    }
    #endregion




}
View Code

 

5. 帮助类层构建

   暂无内容

 

6. DTO层构建

   暂无内容

 

7. Api层构建

(1). 项目内的引用

     【YpfCore.Data】【YpfCore.DTO】【YpfCore.IService】【YpfCore.DTO】【YpfCore.Utils】

(2). 依赖程序集

 【Autofac 6.0.0】【Autofac.Extensions.DependencyInjection 7.0.2】

(3). 核心代码

A. EFCore的注入

builder.Services.AddDbContext<CoreFrameDBContext>(option => option.UseSqlServer(builder.Configuration.GetConnectionString("SQLServerStr")), ServiceLifetime.Scoped);

B. AutoFac的注册

program中注入:

builder.Host.UseServiceProviderFactory(new AutofacServiceProviderFactory());
builder.Host.ConfigureContainer<ContainerBuilder>(builder => builder.RegisterModule(new DefaultModule()));

AutoFac封装类:

/// <summary>
/// 服务于AutoFac
/// </summary>
public class DefaultModule : Module
{
    protected override void Load(ContainerBuilder builder)
    {
        {
            //这里就是AutoFac的注入方式,下面采用常规的方式
            //详见:https://www.cnblogs.com/yaopengfei/p/9479268.html
            //官网:https://autofac.org/

            //特别注意:其中很大的一个变化在于,Autofac 原来的一个生命周期InstancePerRequest,将不再有效。正如我们前面所说的,整个request的生命周期被ASP.NET Core管理了,
            //所以Autofac的这个将不再有效。我们可以使用 InstancePerLifetimeScope ,同样是有用的,对应了我们ASP.NET Core DI 里面的Scoped。

            //关于dll路径的问题:开发环境 需要有 \bin\Debug\netcoreapp3.1\, 而生产环境不需要, 使用AppContext.BaseDirectory来获取根目录恰好符合该要求。

            //在普通类中配置文件的读取麻烦,后面封装(注:appsettings.json要改为始终复制)
            var Configuration = new ConfigurationBuilder().AddJsonFile(AppContext.BaseDirectory + "appsettings.json").Build();
            var dirName = Configuration["IocDll"];
            Assembly asmService = Assembly.LoadFile(AppContext.BaseDirectory + dirName);

            builder.RegisterAssemblyTypes(asmService)
                   .Where(t => !t.IsAbstract && typeof(ISupport).IsAssignableFrom(t))  //只有实现了ISupport接口的类才进行注册
                   .AsImplementedInterfaces()    //把一个类注册给它实现的全部接口
                   .InstancePerLifetimeScope()   //作用域单例(比如Task.Run就是另外一个作用域),而非请求内单例(请求内单例用:InstancePerRequest)
                   .PropertiesAutowired();       //在core里表示在注入类中实现构造函数注入
        }
    }
}

C.  配置文件类 appsettings.json

   该文件需要改为始终复制

{
  "ConnectionStrings": {
    "SQLServerStr": "Server=xxx;Database=Vue3AdminDB;User ID=vue3admin;Password=xxx;TrustServerCertificate=true",
  },
  "IocDll": "YpfCore.Service.dll",
}

 

 8. 测试

    在YpfCore.AdminWeb层的控制器中注入IBaseService,操控哪张表,调用方法的时候传入对应表的实体类即可。各种封装方法详见BaseService。

 public void Test3([FromServices] IBaseService _myBaseService)
 {
   //查询
   var data1 = _myBaseService.Entities<T_SysUser>().Where(u => u.id != "1").ToList();
   var data2 = _myBaseService.GetListBy<T_SysOperLog>(u => u.id != "1");

   //删除
   var count1 = _myBaseService.DelBy<T_SysLoginLog>(u => u.id != "1");
}

 

 

二. 必备中间件集成

1. 配置跨域

  其中 WithExposedHeaders("Content-Disposition")  表示:支持文件流形式Excel的下载,如无该需求,可以不写。

//开启跨域(要在静态文件之后)
app.UseCors(options =>
{
    options.AllowAnyOrigin()
            .AllowAnyMethod()
            .AllowAnyHeader()
            .WithExposedHeaders("Content-Disposition");  //支持文件流形式Excel的下载
});

 

2. 配置区域路由

//默认路由
app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Guid}/{action=Index}/{id?}");

//开启区域路由
app.MapControllerRoute(
    name: "MyArea",
    pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}");

路由中需要添加特性 [Area("Admin_Areas")],用来配置路由名称


[Area("Admin_Areas")]
[Route("api/[area]/[controller]/[action]")]
public class SysUserController : Controller
{
    /// <summary>
    /// 获取用户信息
    /// </summary>
    /// <param name="id">用户编号</param>
    /// <returns></returns>
    [HttpPost]
    public string GetUserMsg(string id)
    {
        return "ypf" + id;
    }
}

 

3. 配置OpenApi

(1). 通过反射开启显示注释。

(2). 开启JWT参数传递。

(3). 根据区域进行分组显示。

csproj代码

	<PropertyGroup>
		<GenerateDocumentationFile>true</GenerateDocumentationFile>
		<NoWarn>$(NoWarn);1591</NoWarn>
	</PropertyGroup>

注册服务代码

builder.Services.AddSwaggerGen(options =>
{
    //1. 通过反射开启注释
    var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
    options.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, xmlFilename));

    //2. 支持jwt传递
    var scheme = new OpenApiSecurityScheme()
    {
        Description = "普通的jwt校验, 即:说白了就是在Header中传递参数的时候多了一个:auth=token",
        Reference = new OpenApiReference
        {
            Type = ReferenceType.SecurityScheme,
            Id = "auth"
        },
        Scheme = "oauth2",
        Name = "auth",
        In = ParameterLocation.Header,
        Type = SecuritySchemeType.ApiKey,
    };
    options.AddSecurityDefinition("auth", scheme);
    var requirement = new OpenApiSecurityRequirement();
    requirement[scheme] = new List<string>();
    options.AddSecurityRequirement(requirement);

    //3.配置根据区域分组
    options.SwaggerDoc("Main", new OpenApiInfo { Version = "V1.0", Title = "主模块", Description = "按区域分组管理,右上角切换", });
    options.SwaggerDoc("AdminApi_Areas", new OpenApiInfo { Version = "V1.0", Title = "AdminApi_Areas模块" });
    options.SwaggerDoc("LogApi_Areas", new OpenApiInfo { Version = "V1.0", Title = "LogApi_Areas模块" });
    options.DocInclusionPredicate((docName, apiDes) =>
    {
        if (!apiDes.TryGetMethodInfo(out MethodInfo method)) return false;
        //使用ApiExplorerSettingsAttribute里面的GroupName进行特性标识
        //(1).获取controller上的特性,注:method.DeclaringType只能获取controller上的特性              
        var controllerGroupNameList = method.DeclaringType.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName).ToList();
        if (docName == "Main" && !controllerGroupNameList.Any()) return true;
        //(2).获取action上的特性
        var actionGroupNameList = method.GetCustomAttributes(true).OfType<ApiExplorerSettingsAttribute>().Select(m => m.GroupName);
        if (actionGroupNameList.Any())
        {
            return actionGroupNameList.Any(u => u == docName);
        }
        return controllerGroupNameList.Any(u => u == docName);
    });


});
View Code

管道代码

if (app.Environment.IsDevelopment())
{
    //开启OpenApi管道,仅开发模式下可以访问
    app.UseSwagger();
    app.UseSwaggerUI(c =>
    {
        c.SwaggerEndpoint("/swagger/Main/swagger.json", "主模块");
        c.SwaggerEndpoint("/swagger/AdminApi_Areas/swagger.json", "AdminApi_Areas");
        c.SwaggerEndpoint("/swagger/LogApi_Areas/swagger.json", "LogApi_Areas");
        //配置文档的展开形式:List列表(默认)  None(折叠)  Full(展开到参数级别)
        c.DocExpansion(Swashbuckle.AspNetCore.SwaggerUI.DocExpansion.List);
    });
}
View Code

特性标记

[ApiExplorerSettings(GroupName = "AdminApi_Areas")]

 

4. 配置日志

(这里删除了旧版本中的Log4net,仅支持SeriLog)

(1). YpfCore.Utils层安装程序集:【Serilog】【Serilog.Sinks.Async】【Serilog.Sinks.File】

(2). Log/SeriLog/LogUtils帮助类

/// <summary>
/// SeriLog帮助类
/// </summary>
public class LogUtils
{
    static readonly string log1Name = "ApiLog";
    static readonly string log2Name = "ErrorApiLog";

    /// <summary>
    /// 初始化日志
    /// </summary>
    public static void InitLog()
    {
        //static string LogFilePath(string FileName) => $@"{AppContext.BaseDirectory}Log\{FileName}\log.log";   //bin目录下

        static string LogFilePath(string FileName) => $@"MyLogs\{FileName}\log_.log";
        string SerilogOutputTemplate = "{NewLine}Date:{Timestamp:yyyy-MM-dd HH:mm:ss.fff}{NewLine}LogLevel:{Level}{NewLine}Message:{Message}{NewLine}{Exception}" + new string('-', 100);
        Serilog.Log.Logger = new LoggerConfiguration()
                    .Enrich.FromLogContext()
                    .MinimumLevel.Debug() // 所有Sink的最小记录级别
                    .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log1Name)).WriteTo.Async(a => a.File(LogFilePath(log1Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)))
                    .WriteTo.Logger(lg => lg.Filter.ByIncludingOnly(Matching.WithProperty<string>("position", p => p == log2Name)).WriteTo.Async(a => a.File(LogFilePath(log2Name), rollingInterval: RollingInterval.Day, outputTemplate: SerilogOutputTemplate)))
                    .CreateLogger();
    }


    /*****************************下面是不同日志级别*********************************************/
    // FATAL(致命错误) > ERROR(一般错误) > Warning(警告) > Information(一般信息) > DEBUG(调试信息)>Verbose(详细模式,即全部)


    /// <summary>
    /// 普通日志
    /// </summary>
    /// <param name="msg">日志内容</param>
    /// <param name="fileName">文件夹名称</param>
    public static void Info(string msg, string fileName = "")
    {
        if (fileName == "" || fileName == log1Name)
        {
            Serilog.Log.Information($"{{position}}:{msg}", log1Name);
        }
    }

    /// <summary>
    /// 异常日志
    /// </summary>
    /// <param name="ex">Exception</param>
    /// <param name="fileName">文件夹名称</param>
    public static void Error(Exception ex, string fileName = "")
    {
        if (fileName == "" || fileName == log2Name)
        {
            Serilog.Log.Error(ex, "{position}:" + ex.Message, log2Name);
        }
    }
}
View Code

(3). 在YpfCore.AdminWeb层中进行初始化

//初始化日志
LogUtils.InitLog();

(4). 测试

    /// <summary>
    /// 04-日志测试
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    public string Test4()
    {
        try
        {
            LogUtils.Info("hello word");
            int.Parse("sdfsdf");
        }
        catch (Exception ex)
        {
            LogUtils.Error(ex);
        }
        return "ok";
    }

 

5. 开启http请求

  封装在YpfCore.Utils层中,新增headers参数传递,Api层中不再需要注入AddHttpClient了直接使用即可。

(1). RequestHelp帮助类封装代码

/// <summary>
/// 基于HttpClientFactory的请求封装
/// 依赖【Microsoft.Extensions.DependencyInjection】和 【Microsoft.Extensions.Http】
///     【System.Text.Json】
/// 其它层可以直接调用,不再需要注入AddHttpClient了,因为下面封装里已Add进去了
/// </summary>
public class RequestHelp
{
    /// <summary>
    /// Get请求
    /// </summary>
    /// <param name="url">请求地址</param>
    /// <param name="headers">headers内容,可以不填</param>
    /// <returns></returns>
    public static string Get(string url, Dictionary<string, string> headers = null)
    {
        var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
        IHttpClientFactory clientFactory = serviceProvider.GetService<IHttpClientFactory>();
        var request = new HttpRequestMessage(HttpMethod.Get, url);
        //添加Headers内容
        foreach (var item in headers)
        {
            request.Headers.Add(item.Key, item.Value);
        }
        var client = clientFactory.CreateClient();
        var response = client.SendAsync(request).Result;
        var myResult = response.Content.ReadAsStringAsync().Result;
        return myResult;
    }


    /// <summary>
    /// Post请求-表单形式
    /// </summary>
    /// <param name="url">请求地址</param>
    /// <param name="content">请求内容,形如:"userName=admin&pwd=123456"</param>
    /// <param name="headers">headers内容,可以不填</param>
    /// <returns></returns>
    public static string Post(string url, string content, Dictionary<string, string> headers = null)
    {
        var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
        IHttpClientFactory clientFactory = serviceProvider.GetService<IHttpClientFactory>();
        var request = new HttpRequestMessage(HttpMethod.Post, url)
        {
            //内容的处理
            Content = new StringContent(content, Encoding.UTF8, "application/x-www-form-urlencoded")
        };
        //添加Headers内容
        foreach (var item in headers)
        {
            request.Headers.Add(item.Key, item.Value);
        }
        var client = clientFactory.CreateClient();
        var response = client.SendAsync(request).Result;
        var myResult = response.Content.ReadAsStringAsync().Result;
        return myResult;
    }



    /// <summary>
    /// Post请求-Json形式
    /// </summary>
    /// <param name="url">请求地址</param>
    /// <param name="content">请求内容, 形如:
    ///   new {userName = "admin", pwd = "123456"}
    /// </param>
    /// <param name="headers">headers内容,可以不填</param>
    /// <returns></returns>
    public static string PostJson(string url, object content, Dictionary<string, string> headers = null)
    {
        var serviceProvider = new ServiceCollection().AddHttpClient().BuildServiceProvider();
        IHttpClientFactory clientFactory = serviceProvider.GetService<IHttpClientFactory>();
        var request = new HttpRequestMessage(HttpMethod.Post, url)
        {
            //内容的处理
            Content = new StringContent(JsonSerializer.Serialize(content), Encoding.UTF8, "application/json")
        };
        //添加Headers内容
        foreach (var item in headers)
        {
            request.Headers.Add(item.Key, item.Value);
        }
        var client = clientFactory.CreateClient();

        var response = client.SendAsync(request).Result;
        var myResult = response.Content.ReadAsStringAsync().Result;
        return myResult;
    }

}
View Code

(2). 测试Api接口

 /// <summary>
    /// 接收Get请求测试
    /// </summary>
    /// <param name="userName"></param>
    /// <param name="userAge"></param>
    /// <returns></returns>
    [HttpGet]
    public string GetInfo1(string userName, string userAge)
    {
        var token = HttpContext.Request.Headers["token"].ToString();
        return $"userName:{userName},userAge:{userAge}";
    }

    /// <summary>
    /// 接收Post请求--表单测试
    /// </summary>
    /// <param name="userName"></param>
    /// <param name="userAge"></param>
    /// <returns></returns>
    [HttpPost]
    public string GetInfo2(string userName, string userAge)
    {
        var token = HttpContext.Request.Headers["token"].ToString();
        return $"userName:{userName},userAge:{userAge}";
    }

    /// <summary>
    ///  接收Post请求--Json测试  http://localhost:5095/
    /// </summary>
    /// <param name="user">user实体</param>
    /// <returns></returns>
    [HttpPost]
    public string GetInfo3([FromBody] UserInfo user)
    {
        var token = HttpContext.Request.Headers["token"].ToString();
        return $"userAccount:{user.userAccount},userSex:{user.userSex}";
    }
View Code

(3). 测试调用代码

    /// <summary>
    /// 05-测试发送各种Http请求
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    public string Test5()
    {
        string baseUrl1 = "http://localhost:5095/api/Demo/GetInfo1?userName=ypf&userAge=18";
        string baseUrl2 = "http://localhost:5095/api/Demo/GetInfo2";
        string baseUrl3 = "http://localhost:5095/api/Demo/GetInfo3";
        Dictionary<string, string> dics = new();
        dics.Add("token", "skdfklsldf");
        //Get请求
        string result1 = RequestHelp.Get(baseUrl1, dics);

        //Post请求-表单
        string result2 = RequestHelp.Post(baseUrl2, "userName=ypf&userAge=18", dics);

        //Post请求-JSON 
        var content = new
        {
            userAccount = "admin",
            userSex = "男"
        };
        string result3 = RequestHelp.PostJson(baseUrl3, content, dics);
        return result3;
    }

 

6. 开启静态资源,自动创建问题

  实际上可以不写 if (!Directory.Exists(downloadRoot)) 这个判断,因为Directory.CreateDirectory(downloadRoot)本身就是自带判断路径不存在的话,自动创建

//1. 启用静态资源
app.UseStaticFiles();
//开启DownLoad文件夹,便于下载相关的请求的进行访问(发布的时候,如果没有,会自动创建)
var downloadFolder = "DownLoad";
var downloadRoot = Path.Combine(Directory.GetCurrentDirectory(), downloadFolder); //创建下载文件夹
if (!Directory.Exists(downloadRoot)) //判断路径是否存在
{
    Directory.CreateDirectory(downloadRoot);//不存在,则创建新路径(可以不写前面的if判断,因为默认支持判断路径是否存在)
}
app.UseStaticFiles(new StaticFileOptions
{
    FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), downloadFolder)),
    RequestPath = $"/{downloadFolder}"   //配置相对路径(建议和前面的名起个一样的,当然也可以起别的,注意前面要有/)
});

 

7.  配置通用全局返回格式

  支持以下功能:

(1). 针对DateTime类型通用格式的处理

(2). 参数格式:可以配置原样输出 或者 首字母统一格式化小写。

(3). 取消Unicode编码

(4). 是否忽略空值,如果某个字段为Null(不包含空字符),可以配置直接忽略该字段,即该字段返回给前端的时候不显示。

(5). 允许额外的符号

(6). 反序列化过程中属性名称是否使用不区分大小写的比较

DatetimeJsonConverter类代码

/// <summary>
/// 全局日期格式转换类
/// 基于【System.Text.Json】程序集
/// </summary>
public class DatetimeJsonConverter : JsonConverter<DateTime>
{
    private readonly string dataFormatString;
    /// <summary>
    /// 构造函数
    /// </summary>
    /// <param name="myStr">默认格式为:yyyy-MM-dd HH:mm:ss</param>
    public DatetimeJsonConverter(string myStr = "yyyy-MM-dd HH:mm:ss")
    {
        dataFormatString = myStr;
    }
    public override DateTime Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
    {
        if (reader.TokenType == JsonTokenType.String)
        {
            if (DateTime.TryParse(reader.GetString(), out DateTime date))
                return date;
        }
        return reader.GetDateTime();
    }
    public override void Write(Utf8JsonWriter writer, DateTime value, JsonSerializerOptions options)
    {
        writer.WriteStringValue(value.ToString(dataFormatString));
    }
}

program中代码配置

//5. 注册全局返回值通用处理
builder.Services.Configure<JsonOptions>(options =>
{
    //日期时间格式
    options.JsonSerializerOptions.Converters.Add(new DatetimeJsonConverter("yyyy-MM-dd HH:mm:ss"));
    //参数格式(null表示:参数原样输出;  JsonNamingPolicy.CamelCase表示首字母格式化成小写)
    options.JsonSerializerOptions.PropertyNamingPolicy = null;
    //取消Unicode编码
    options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
    //是否忽略空值 (Never表示不忽略; WhenWritingDefault表示属性值为null的时候忽略)
    options.JsonSerializerOptions.DefaultIgnoreCondition = JsonIgnoreCondition.Never;
    //允许额外符号
    options.JsonSerializerOptions.AllowTrailingCommas = true;
    //反序列化过程中属性名称是否使用不区分大小写的比较
    options.JsonSerializerOptions.PropertyNameCaseInsensitive = false;
});

测试

  /// <summary>
    /// 06-测试通用全局格式返回
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    public IActionResult Test6()
    {
        //return Json(new
        //{
        //    Name1 = "ypf",
        //    Time1 = DateTime.Now,
        //    Name2 = "",
        //});
        return Json(new T_SysLoginLog()
        {
            id = "",           //空字符串不忽略
            addTime = null,   //开启空值null忽略,该字段会被忽略
            userAccount = "admin"
        });
    }

 

 

三. BaseService改造 和 大数据处理封装

1. BaseSevice升级剖析

(相比之前的旧版本,有以下变化:)

(1). 精简代码,删掉和Savechange合并在一起的方法

(2). 仅保留异步写法,删掉同步方法的封装

(3). 借助委托封装事务:调用的时候,可以直接使用传递过来的db原生写法,也可以使用注入的baseService

代码如下(此处代码包含下面大数据处理代码的封装): 

using EFCore.BulkExtensions;
using Microsoft.EntityFrameworkCore;
using System.Linq.Expressions;
using YpfCore.Data.Entity;
using YpfCore.DTO;
using YpfCore.IService.BaseInterface;
using YpfCore.Utils.Extensions;

namespace YpfCore.Service.BaseClass;

/// <summary>
/// 泛型方法,直接注入EF上下文
/// </summary>
public class BaseService : IBaseService, ISupport
{
    public DbContext db;

    /// <summary>
    /// 在使用的时候,自动注入db上下文
    /// </summary>
    /// <param name="db"></param>
    public BaseService(CoreFrameDBContext db)
    {
        this.db = db;

        //关闭全局追踪的代码
        //db.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    }

    /****************************************下面EFCore基础方法的封装【异步】***********************************************/
    /*
       PS: 这里所有的方法savechange都隔离出来,不再合并封装
    */


    #region 01-数据源
    public IQueryable<T> Entities<T>() where T : class
    {
        return db.Set<T>();
    }

    public IQueryable<T> EntitiesNoTrack<T>() where T : class
    {
        return db.Set<T>().AsNoTracking();
    }

    #endregion

    #region 02-批量处理SaveChangesAsync()
    /// <summary>
    /// 02-批量处理SaveChangesAsync()
    /// </summary>
    /// <returns></returns>
    public async Task<int> SaveChangeAsync()
    {
        return await db.SaveChangesAsync();
    }
    #endregion

    #region 03-新增(单体)
    /// <summary>
    ///  03-新增(单体)
    /// </summary>
    /// <param name="model">需要新增的实体</param>
    public async Task AddAsync<T>(T model) where T : class
    {
        await db.AddAsync(model);
    }
    #endregion

    #region 04-新增(集合)
    /// <summary>
    /// 04-新增(集合)
    /// </summary>
    /// <param name="list">需要新增的集合</param>
    public async Task AddRangeAsync<T>(List<T> list) where T : class
    {
        await db.AddRangeAsync(list);
    }
    #endregion

    #region 05-删除(单体)-无异步
    /// <summary>
    /// 05-删除(单体)-无异步
    /// </summary>
    /// <param name="model">需要删除的实体</param>
    /// <returns></returns>
    public void DelAsync<T>(T model) where T : class
    {
        db.Remove(model);
    }
    #endregion

    #region 06-删除(集合)-无异步
    /// <summary>
    /// 06-删除(集合)-无异步
    /// </summary>
    /// <param name="list">需要删除的集合</param>
    /// <returns></returns>
    public void DelRangeAsync<T>(List<T> list) where T : class
    {
        db.RemoveRange(list);
    }
    #endregion

    #region 07-根据条件删除(查询删除一体化)
    /// <summary>
    /// 07-根据条件删除(查询删除一体化)
    /// </summary>
    /// <param name="delWhere">需要删除的条件</param>
    public async Task DelByAsync<T>(Expression<Func<T, bool>> delWhere) where T : class
    {
        List<T> listDels = await db.Set<T>().Where(delWhere).ToListAsync();
        listDels.ForEach(model =>
        {
            db.Entry(model).State = EntityState.Deleted;
        });
    }
    #endregion

    #region 08-单实体修改【适用于没有状态追踪的实体】-无异步
    /// <summary>
    /// 08-单实体修改【适用于没有状态追踪的实体】
    /// </summary>
    /// <param name="model">需要修改的实体</param>
    /// <returns></returns>
    public void Modify<T>(T model) where T : class
    {
        db.Entry(model).State = EntityState.Modified;
    }
    #endregion

    #region 09-根据条件查询
    /// <summary>
    /// 09-根据条件查询
    /// </summary>
    /// <param name="whereLambda">查询条件(lambda表达式的形式生成表达式目录树)</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetListByAsync<T>(Expression<Func<T, bool>> whereLambda, bool isTrack = true) where T : class
    {
        if (isTrack)
        {
            return await db.Set<T>().Where(whereLambda).ToListAsync();
        }
        else
        {
            return await db.Set<T>().Where(whereLambda).AsNoTracking().ToListAsync();
        }

    }
    #endregion

    #region 10-根据条件排序和查询
    /// <summary>
    /// 10-根据条件排序和查询
    /// </summary>
    /// <typeparam name="Tkey">排序字段类型</typeparam>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="orderLambda">排序条件</param>
    /// <param name="isAsc">升序or降序</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetListByAsync<T, Tkey>(Expression<Func<T, bool>> whereLambda, Expression<Func<T, Tkey>> orderLambda,
        bool isAsc = true, bool isTrack = true) where T : class
    {
        IQueryable<T> data;
        if (isTrack)
        {
            data = db.Set<T>().Where(whereLambda);
        }
        else
        {
            data = db.Set<T>().Where(whereLambda).AsNoTracking();
        }
        if (isAsc)
        {
            data = data.OrderBy(orderLambda);
        }
        else
        {
            data = data.OrderByDescending(orderLambda);
        }
        return await data.ToListAsync();
    }
    #endregion

    #region 11-分页查询(根据Lambda排序)
    /// <summary>
    /// 11-分页查询(根据Lambda排序)
    /// </summary>
    /// <typeparam name="Tkey">排序字段类型</typeparam>
    /// <param name="pageIndex">页码</param>
    /// <param name="pageSize">页容量</param>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="orderLambda">排序条件</param>
    /// <param name="isAsc">升序or降序</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetPageListAsync<T, Tkey>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda,
        Expression<Func<T, Tkey>> orderLambda, bool isAsc = true, bool isTrack = true) where T : class
    {

        IQueryable<T> data = null;
        if (isTrack)
        {
            data = db.Set<T>().Where(whereLambda);
        }
        else
        {
            data = db.Set<T>().Where(whereLambda).AsNoTracking();
        }
        if (isAsc)
        {
            data = data.OrderBy(orderLambda).Skip((pageIndex - 1) * pageSize).Take(pageSize);
        }
        else
        {
            data = data.OrderByDescending(orderLambda).Skip((pageIndex - 1) * pageSize).Take(pageSize);
        }
        return await data.ToListAsync();
    }
    #endregion

    #region 12-分页查询(根据名称排序)
    /// <summary>
    /// 12-分页查询(根据名称排序)
    /// 需要依赖:YpfCore.Utils/Extensions/SortExtension
    /// </summary>
    /// <param name="pageIndex">页码</param>
    /// <param name="pageSize">每页的条数</param>
    /// <param name="whereLambda">查询条件</param>
    /// <param name="sortName">排序字段名称</param>
    /// <param name="sortDirection">asc 或 desc</param>
    ///  <param name="isTrack">是否跟踪状态,默认是跟踪的</param>
    /// <returns></returns>
    public async Task<List<T>> GetPageListByNameAsync<T>(int pageIndex, int pageSize, Expression<Func<T, bool>> whereLambda,
        string sortName, string sortDirection, bool isTrack = true) where T : class
    {
        List<T> list;
        if (isTrack)
        {
            list = await db.Set<T>().Where(whereLambda).DataSorting(sortName, sortDirection)
             .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
        }
        else
        {
            list = await db.Set<T>().Where(whereLambda).AsNoTracking().DataSorting(sortName, sortDirection)
             .Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
        }
        return list;
    }
    #endregion

    #region 13-执行增加,删除,修改SQL操作
    /// <summary>
    /// 执行增加,删除,修改SQL操作
    /// 【自动进行参数化处理,防止SQL注入】
    /// </summary>
    /// <param name="sql">sql脚本</param>
    /// <returns></returns>
    public async Task<int> ExecuteSqlInterpolatedAsync(FormattableString sql)
    {
        return await db.Database.ExecuteSqlInterpolatedAsync(sql);
    }
    #endregion

    #region 14-执行查询SQL操作【适用于单表】
    /// <summary>
    /// 14-执行查询SQL操作【适用于单表】
    /// </summary>
    /// <typeparam name="T">表实体</typeparam>
    /// <param name="sql">sql脚本</param>
    /// <returns></returns>
    public async Task<List<T>> FromSqlInterpolated<T>(FormattableString sql) where T : class
    {
        return await db.Set<T>().FromSqlInterpolated(sql).ToListAsync();
    }
    #endregion

    #region 15-封装开启事务代码
    /// <summary>
    /// 15-封装开启事务代码
    /// </summary>
    /// <param name="Func">业务函数回调,传递参数为db, 返回task</param>
    /// <returns>
    ///  status="ok":代表事务成功
    ///  status="error":代表事务失败
    /// </returns>
    public async Task<TransResult> BeginTransactionAsync(Func<DbContext, Task> Func)
    {
        using (var transaction = await db.Database.BeginTransactionAsync())
        {
            try
            {
                await Func(db); //执行业务

                await transaction.CommitAsync();  //最终事务提交

                return new TransResult() { status = "ok" };
            }
            catch (Exception ex)
            {
                //using包裹不需要手写rollback
                return new TransResult() { status = "error", ex = ex };
            }
        }
    }

    #endregion


    /****************************************下面 EFCore.BulkExtensions封装【异步,仅支持SQLServer】***********************************************/

    #region 01-批量插入
    /// <summary>
    /// 01-批量插入
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="list">实体集合</param>
    /// <returns></returns>
    public async Task BulkInsertAsync<T>(List<T> list) where T : class
    {
        await db.BulkInsertAsync(list);
    }
    #endregion

    #region 02-批量删除
    /// <summary>
    /// 02-批量删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="delWhere">删除条件</param>
    /// <returns></returns>
    public async Task<int> BatchDeleteAsync<T>(Expression<Func<T, bool>> delWhere) where T : class
    {
        int count = await db.Set<T>().Where(delWhere).BatchDeleteAsync();
        return count;
    }
    #endregion

    #region 03-全表truncate删除
    /// <summary>
    /// 03-全表truncate删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <returns></returns>
    public async Task TruncateAsync<T>() where T : class
    {
        await db.TruncateAsync<T>();
    }
    #endregion

    #region 04-批量修改-写法1(实体模式)
    /// <summary>
    /// 04-批量修改-写法1(实体模式)
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="updateWhere">更新条件</param>
    /// <param name="model">更新实体</param>
    /// <returns></returns>
    public async Task<int> BatchUpdateAsync<T>(Expression<Func<T, bool>> updateWhere, T model) where T : class
    {
        int count = await db.Set<T>().Where(updateWhere).BatchUpdateAsync(model);
        return count;
    }
    #endregion

    #region 05-批量修改-写法2(表达式模式,支持在原值的基础上进行修改)
    /// <summary>
    /// 05-批量修改-写法2(表达式模式,支持在原值的基础上进行修改)
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="updateWhere">更新条件</param>
    /// <param name="updataExpression">更新实体表达式</param>
    /// <returns></returns>
    public async Task<int> BatchUpdateAsync<T>(Expression<Func<T, bool>> updateWhere, Expression<Func<T, T>> updataExpression) where T : class
    {
        int count = await db.Set<T>().Where(updateWhere).BatchUpdateAsync(updataExpression);
        return count;
    }
    #endregion



    /****************************************下面Zack.EFCore.Batch封装【异步,支持SQLServer和MySQL】***********************************************/
    /*
        PS: 批量修改:二次封装反而更加繁琐,所以,这里不封装了
     */

    #region  01-批量插入
    /// <summary>
    /// 01-批量插入
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="list">实体集合</param>
    /// <returns></returns>
    public async Task InsertBulkAsync<T>(List<T> list) where T : class
    {
        //await db.BulkInsertAsync(list);  

        //由于BulkInsertAsync和上述【EFCore.BulkExtensions】程序集名称重复,所以这里采用扩展方法来处理
        await MSSQLBulkInsertExtensions.BulkInsertAsync(db, list);
    }
    #endregion

    #region 02-批量删除
    /// <summary>
    /// 02-批量删除
    /// </summary>
    /// <typeparam name="T">实体类型</typeparam>
    /// <param name="delWhere">删除条件</param>
    /// <returns></returns>
    public async Task<int> DeleteRangeAsync<T>(Expression<Func<T, bool>> delWhere) where T : class
    {
        int count = await db.DeleteRangeAsync<T>(delWhere);
        return count;
    }
    #endregion




}
View Code

 

重点测试使用委托封装事务:

public async Task<string> Test1([FromServices] IBaseService myBaseService)
    {
        string result = "xxx";

        #region 02-测试事务封装--直接使用传递过来的原生db【测试通过】
        {
            TransResult myResult = await baseService.BeginTransactionAsync(async (db) =>
            {
                //里面可以写同步方法+异步方法,倾向全部异步

                //修改 【异步写法】
                var data = await db.Set<T_SysOperLog>().Where(u => u.id == "2").FirstOrDefaultAsync();
                data.operMessage = "ypf001";

                //增加 【同步写法】
                T_SysOperLog model = new();
                model.id = Guid.NewGuid().ToString("N");
                //model.id = Guid.NewGuid().ToString("N")+"sdfsdf";  //模拟错误
                model.userId = "343";
                model.userAccount = "admin";
                model.operMessage = "325423";
                model.addTime = DateTime.Now;
                model.delFlag = 0;
                db.Add(model);

                db.SaveChanges();
            });
            result = myResult.status;
        }
        #endregion

        #region 03-测试事务封装--使用注入的baseService【测试通过】
        {
            TransResult myResult = await baseService.BeginTransactionAsync(async (db) =>
            {
                //里面可以写同步方法+异步方法,倾向全部异步

                //修改  【同步写法】
                var data = baseService.Entities<T_SysOperLog>().Where(u => u.id == "2").FirstOrDefault();
                data.operMessage = "ypf12345";

                //增加  【异步写法】
                var model = new T_SysOperLog();
                model.id = Guid.NewGuid().ToString("N");
                //model.id = Guid.NewGuid().ToString("N") + "sdfsdf";  //模拟错误
                model.userId = "343";
                model.userAccount = "admin";
                model.operMessage = "325423";
                model.addTime = DateTime.Now;
                model.delFlag = 0;
                await baseService.AddAsync(model);

                await baseService.SaveChangeAsync();
            });
            result = myResult.status;
        }
       #endregion

        await Task.CompletedTask;

        return result;

    }

2. EFCore.BulkExtensions 封装

  在 Ypf.Service层安装程序集:【EFCore.BulkExtensions】,在BaseService中封装了:批量插入、批量删除、全表删除、批量修改(两种模式)。代码详见上述的BaseService。

测试:

   /// <summary>
    /// 02-【EFCore.BulkExtensions】大数据测试
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    public async Task<string> Test2()
    {
        try
        {
            #region 01-批量插入【测试通过】
            //{
            //    List<T_SysErrorLog> list = new();
            //    for (int i = 0; i < 10; i++)
            //    {
            //        T_SysErrorLog log = new();
            //        log.id = Guid.NewGuid().ToString("N");
            //        log.userId = "0001";
            //        log.userAccount = "admin";
            //        log.logLevel = "2";
            //        log.logMessage = "msg1";
            //        log.addTime = DateTime.Now;
            //        log.delFlag = 0;
            //        list.Add(log);
            //    }
            //    await baseService.BulkInsertAsync(list);
            //}
            #endregion

            #region 02-批量删除 【测试通过】
            //{
            //    int count = await baseService.BatchDeleteAsync<T_SysErrorLog>(u => u.delFlag == 1);
            //}
            #endregion

            #region 03-批量修改-表达式模式 【测试通过】
            //{

            //    int count = await baseService.BatchUpdateAsync<T_SysErrorLog>(u => u.delFlag == 0, u => new T_SysErrorLog
            //    {
            //        logLevel = "error",
            //        logMessage = u.logMessage + ",hhh"
            //    });

            //}
            #endregion

            #region 04-批量修改-实体模式 【测试通过】
            //{

            //    int count = await baseService.BatchUpdateAsync<T_SysErrorLog>(u => u.delFlag == 0, new T_SysErrorLog
            //    {
            //        logLevel = "error",
            //        logMessage = "lll"
            //    });
            //}
            #endregion

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
            LogUtils.Error(ex);
        }

        await Task.CompletedTask;
        return "成功喽";
    }

 

3. 老杨框架 Zack.EFCore.Batch.MSSQL_NET6 封装

  在 Ypf.Service层安装程序集:【Zack.EFCore.Batch.MSSQL_NET6】,在BaseService中封装了:批量插入、批量删除。代码详见上述的BaseService。

  PS: 批量修改:二次封装反而更加繁琐,所以,这里不封装了

测试:

    /// <summary>
    /// 03-【Zack.EFCore.Batch】大数据测试
    /// </summary>
    /// <returns></returns>
    [HttpPost]
    public async Task<string> Test3()
    {
        try
        {
            #region 01-批量插入【测试通过】
            //{
            //    List<T_SysErrorLog> list = new();
            //    for (int i = 0; i < 10; i++)
            //    {
            //        T_SysErrorLog log = new();
            //        log.id = Guid.NewGuid().ToString("N");
            //        log.userId = "0002";
            //        log.userAccount = "admin";
            //        log.logLevel = "2";
            //        log.logMessage = "msg1";
            //        log.addTime = DateTime.Now;
            //        log.delFlag = 0;
            //        list.Add(log);
            //    }
            //    await baseService.InsertBulkAsync(list);
            //}
            #endregion

            #region 02-批量删除 【测试通过】
            {
                //int count = await baseService.DeleteRangeAsync<T_SysErrorLog>(u => u.userId == "0002");
            }
            #endregion

            #region 03-批量修改-【不做测试】
            {
                //没有封装哦
            }
            #endregion

        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        await Task.CompletedTask;
        return "成功喽";
    }

 

 

 

 

 

 

 

!

  • 作       者 : Yaopengfei(姚鹏飞)
  • 博客地址 : http://www.cnblogs.com/yaopengfei/
  • 声     明1 : 如有错误,欢迎讨论,请勿谩骂^_^。
  • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 
posted @ 2022-09-06 21:41  Yaopengfei  阅读(330)  评论(6编辑  收藏  举报