第一节:框架全新升级(快速搭建、必备中间件集成、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 }
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 }
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); }); });
管道代码

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); }); }
特性标记
[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); } } }
(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; } }
(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}"; }
(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 }
重点测试使用委托封装事务:
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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?
2021-09-06 第一节:代码片段制作、template几种写法、各种场景下的this剖析
2017-09-06 第十五节:Expression表达式目录树(与委托的区别、自行拼接、总结几类实例间的拷贝)