Crping.EFCore
9.3.0
See the version list below for details.
dotnet add package Crping.EFCore --version 9.3.0
NuGet\Install-Package Crping.EFCore -Version 9.3.0
<PackageReference Include="Crping.EFCore" Version="9.3.0" />
<PackageVersion Include="Crping.EFCore" Version="9.3.0" />
<PackageReference Include="Crping.EFCore" />
paket add Crping.EFCore --version 9.3.0
#r "nuget: Crping.EFCore, 9.3.0"
#:package Crping.EFCore@9.3.0
#addin nuget:?package=Crping.EFCore&version=9.3.0
#tool nuget:?package=Crping.EFCore&version=9.3.0
Crping.EFCore
基于 EntityFramework Core 的数据库操作工具包基础类,支持读写分离,支持SQLite、SQL Server等,包含WebApi的增、删、改、查、分页等常用方法实现!
版本更新说明
9.3.0
2026年8月18日 星期二
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 9.3.0
修复 — GetOrCreateAsync 嵌套事务冲突
GetOrCreateAsync/TryCreateAsync系列方法内部调用BeginTransactionAsync()开启新事务,当调用方已开启事务时(如 Domain 层ShareDbContext+ 多 BLL 编排场景)会抛出InvalidOperationException: The connection is already in a transaction- 修复方案:检测
Database.CurrentTransaction,已有活动事务时直接执行核心逻辑(GetOrCreateCoreAsync),复用调用方事务而非嵌套;无活动事务时行为不变 - 影响范围:所有使用
GetOrCreateAsync的 Domain 编排场景(如CommunityDom.CreateAsync中调用cityBLL.GetOrCreateAsync) - 向后兼容:无活动事务时行为与 9.2.0 完全一致
9.2.0
2026年8月18日 星期一
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 9.2.0
新功能 — ResultCode 新增 HTTP 5xx 状态码枚举
ResultCode枚举新增五个服务器端状态码:InternalServerError = 500— 服务器内部错误NotImplemented = 501— 未实现的功能BadGateway = 502— 网关错误ServiceUnavailable = 503— 服务不可用GatewayTimeout = 504— 网关超时
新功能 — ApiResult.InternalError 工厂方法
ApiResult新增两个静态工厂,用于构造 500 服务器内部错误结果:InternalError(string? error = null)— 无数据载荷(字符串消息落 Error 槽)InternalError<TData>(TData? data)— 携带数据载荷(泛型参数由 data 自动推断)
- 与
StatusCode工厂的区别:InternalError语义明确(500 错误),StatusCode为通用逃生舱(任意状态码)
(测试基线 271 → 275:新增 ApiResultTests 4 例——InternalError 基本/带错误消息/带数据载荷/ToActionResult 映射)
9.1.2
2026年8月16日 星期日
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 9.1.2
新功能 — ApiResult.StatusCode 透传工厂(枚举之外的逃生舱)
ApiResult新增两个静态工厂,透传ResultCode未覆盖的任意 HTTP 状态码(如 422 / 429 / 418):StatusCode(int statusCode, string? error = null)— 无数据载荷(字符串消息落 Error 槽)StatusCode<TData>(int statusCode, TData? data)— 携带数据载荷(泛型参数由 data 自动推断,须显式传状态码——单参数调用无法推断泛型)
- 命名说明:工厂名取
StatusCode而非Code(记录结构体主构造参数Code已生成同名属性,Code会 CS0102 编译冲突) - 状态码经
ToActionResult兜底分支映射为StatusCodeResult/ObjectResult,须在 100~599 范围内(ASP.NET Core 对越界值抛异常)
新功能 — ToActionResult 兜底分支携带响应体
ResultCode未覆盖的状态码由裸StatusCodeResult(丢弃载荷)改为:非泛型取Error、泛型Error ?? Data回退,有载荷走ObjectResult设状态码、无载荷走StatusCodeResult——与 400 / 404 / 409 分支行为对齐
优化 — OffsetQuery 排序键返回类型收窄(CA1859)
ResolveSort/BuildSortKeys返回类型与_sortKeys缓存字段由IReadOnlyList<OffsetSortKey<TModel>>收窄为List<OffsetSortKey<TModel>>(internal,公共 API 不变),与 Keyset 侧KeysetSortPlan具体类做法对齐,消除 CA1859 接口调用开销警告
(测试基线 264 → 271:新增 ApiResultTests 7 例——429/422 透传、Error 槽、无载荷 StatusCodeResult、带载荷 ObjectResult、隐式提升)
9.1.1
2026年8月16日 星期日
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 9.1.1
新功能 — ApiResult 错误/创建工厂带数据重载
ApiResult新增四个与Ok<TData>(data)同构的泛型工厂:Created<TData>(data)/BadRequest<TData>(data)/NotFound<TData>(data)/Conflict<TData>(data)(泛型参数由 data 自动推断)——错误结果可携带数据载荷(如校验失败对象、未找到的标识),泛型ToActionResult经Error ?? Data回退输出- 字符串实参解析规则:
BadRequest("msg")/NotFound("msg")等字符串消息在恒等转换平局下非泛型重载胜出,自然落 Error 槽;字符串数据载荷(如NotFound("id-001"))同样落 Error 槽,响应体经Error ?? Data回退后输出一致
破坏性变更 — 错误参数收窄为 string?(9.x 未发布,无兼容负担)
BadRequest/NotFound/Conflict的错误参数由object?收窄为string?;Forbidden/Unauthorized新增string? error = null- 迁移指南:传入字符串错误消息的调用点零改动;传入非字符串对象(旧版落 Error 槽)的调用点需改为带数据重载
ApiResult.BadRequest(obj)(落 Data 槽,响应体输出一致)或new ApiResult(ResultCode.BadRequest, obj)
新功能 — ToActionResult 错误响应体
Unauthorized(error)→ 401 +UnauthorizedObjectResult;Forbidden(error)→ 403 +ObjectResult(ASP.NET Core 无 ForbidObjectResult)- 泛型
ToActionResult的 400 / 404 / 409 分支由Error ?? Data回退,错误消息与数据载荷均可渲染进响应体
(测试基线 264:新增 ApiResultTests 18 例——带数据工厂槽位 / 字符串落 Error 槽 / Error 优先级 / 201、400、401、403、404、409 响应体映射)
9.1.0
2026年8月15日 星期六
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 9.1.0
新功能 — ApiResult 静态工厂族(结果构造声明式化)
ApiResult(非泛型)新增与ResultCode一一对应的静态工厂:Ok()/Ok<TData>(data)/Created()/NoContent()/BadRequest(error)/Unauthorized()/Forbidden()/NotFound(error)/Conflict(error)ApiResult<TData>新增隐式提升转换(ApiResult → ApiResult<TData>):无数据载荷结果在泛型上下文中可直接return(如方法返回Task<ApiResult<object>>时写return ApiResult.NotFound();)- 全部固定
ResultCode调用点(约 30 处)迁移:new ApiResult<X>(ResultCode.Ok, data)→ApiResult.Ok(data)、new ApiResult(ResultCode.NotFound)→ApiResult.NotFound();动态 code 透传(new ApiResult(code)等 11 处)保持原样 - 非破坏性增量:现有
new ApiResult(...)构造写法全部兼容
新功能 — EFCoreExt.WhereLike 条件式前缀模糊查询
- 新增
WhereLike扩展(IQueryable<T>+KeysetQuery/OffsetQuery/ListQuery四重载):.WhereLike(keyword, n => n.Field)一行替代.Where(keyword.HasValue(), n => EF.Functions.Like(n.Field, $"{keyword}%"))组合;关键词为空时跳过过滤(语义等同条件式 Where) - Demo 9 处模糊查询全部迁移(TopicBLL / MemberBLL / CategoryBLL / ModuleBLL / TopicImageBLL)
(测试基线 245 不变:纯新增 API 与调用点等价改写)
9.0.0
2026年8月15日 星期六
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 9.0.0
破坏性变更 — BLL 去 MVC 依赖(分层解耦)
- 新增
ApiResult/ApiResult<TData>(ResultCode+ 数据 + 错误):BLL 全部Task<IActionResult>方法改为返回ApiResult(约 20 个方法 / 13 个接口),响应构造彻底移出 BLL WebApiResponse/IWebApiResponse响应构造迁至Crping.EFCore.Controller包;WebApiBaseBLL/BaseDomain不再继承WebApiResponse,IWebApiBaseBLL/IDomain不再继承IWebApiResponseDatabaseExtensions(IApplicationBuilder扩展)迁至 Controller 包- 删除
Microsoft.AspNetCore.AppFrameworkReference,改引Microsoft.Extensions.Configuration.Binder(MyDbContextFactory的GetSection(...).Get<T>()依赖) - Controller 层新增
ApiResultExtensions.ToActionResult()统一映射(ResultCode→IActionResult),WebApiBaseController端点改为(await BLL.Xxx(...)).ToActionResult()
破坏性变更 — 写后读绑定静态开关 Options 化
ReadWriteRouting.BindReadToWrite静态属性移除,改为ReadWriteRoutingOptions(sealed class,bool BindReadToWrite,默认关);BaseDAL构造函数新增可选参数ReadWriteRoutingOptions? options = null- 开启方式:
builder.Services.AddSingleton(new ReadWriteRoutingOptions { BindReadToWrite = true })+app.UseReadWriteRouting()(RequestWrittenAsyncLocal 与复位中间件保持不变)
优化
CacheManager.VersionLocalCacheTtl/MaxThrottleEntries由static改实例属性(CacheManager单例,语义等价)MyDbContextFactory提供protected virtual CreateWriteInstance/CreateReadOnlyInstance钩子(派生类可定制上下文创建,如注入拦截器/日志)MyDbContextFactory新增Func<string, TDb>? CreateInstance委托:复用DbContextOptions的EnableRetryOnFailure/ 拦截器 / 日志(默认走无参构造 +OnConfiguring属性注入路径,向后兼容);探活TryValidateReadOnly改传连接串(委托路径下ConnectionString属性可能为空)CacheManager单飞锁竞态修复:去掉每次Release后立即Remove的竞态(同 key 双锁导致单飞失效),改为锁常驻 + 超MaxLockEntries(默认 5 万)阈值批量清理空闲锁
破坏性变更 — 删除过时类型与兼容层(v9.0.0 未发布,无兼容负担)
- 删除 BLL 别名类
WebApiForLongBLL/WebApiForIntBLL/IWebApiForLongBLL/IWebApiForIntBLL(旧 "For" 前缀命名)与转发类EntityConfigure - 删除旧接口
IEnabled/IDisabled的兼容逻辑(BaseDAL._specialPropertyMap条目与ToggleEnableStatusAsync分支,统一IIsEnabled/IIsDisabled);删除ResolveSort默认实现的冗余"id"分支
(测试基线 245:分层解耦后 Update/Create/Delete/StatusToggle/CacheEnable/ExceptionMiddleware 等测试的 IActionResult 断言迁移为 ApiResult 断言,用例数不变)
8.11.0
2026年8月13日 星期四
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.11.0
破坏性变更 — 依赖升级:Microsoft.AspNetCore.Mvc.Core → 框架引用
Microsoft.AspNetCore.Mvc.Core由 2.3.11 包引用改为<FrameworkReference Include="Microsoft.AspNetCore.App" />(该包 2.3.12 后停更,netcoreapp3.0+ 的 MVC 类型由共享框架提供)- 根除传递依赖
Newtonsoft.Json 9.0.1高危漏洞(GHSA-5crp-9r3c-p9vr);同时移除未使用的Newtonsoft.Json直接引用与Directory.Packages.props一批死钉版(Mvc/Mvc.Abstractions/Mvc.ViewFeatures/Http等 2.x)
新功能 — 写后读绑定请求边界复位
- 新增
ReadWriteRoutingMiddleware+UseReadWriteRouting():开启ReadWriteRouting.BindReadToWrite时须在请求管道注册,请求进入 / 结束双重复位写标志,防跨请求残留(此前ResetRequestWritten无中间件接线,非 ASP.NET Core 场景 AsyncLocal 写标志会跨请求残留)
修复
ExecuteProjectionAsync的selectFunc(客户端投影)分支:由exp.Select(n => selectFunc(n))(委托无法翻译,运行抛InvalidOperationException)改为先ToListAsync物化再内存投影
优化
CacheManager注入TimeProvider(版本号本地缓存 TTL 改用GetUtcNow,可测、与工厂冷却期语义一致)MyDbContextFactory连接名缺失改TryGetValue+ 友好InvalidOperationException(原字典索引器抛裸KeyNotFoundException)WebApiBaseBLL.Db/Query移除冗余判空;MaxPageSize魔法数提取常量,SetMaxPageSize标记[Obsolete](推荐MaxPageSize属性 / 链式方法)- 补
MyDbContextFactory设计取舍(运行时副本选择 vs DI 能力)与静态开关"启动时设置、运行期勿改"文档
(测试基线 241 → 245:新增中间件复位 3 例、SelectFunc 客户端投影 1 例)
8.10.0
2026年8月13日 星期四
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.10.0
破坏性变更 — 列表 API 三态对齐(分页三态 + ListQuery 不分页)
新功能 — ListQuery 构建器(不分页列表)
- 新增
ListQuery<TModel, TKey, TResult>查询构建器(与KeysetQuery/OffsetQuery同构):Create(rows)/Top/Where/Where(when, ...)/OrderBy/Select/Apply/TagWith - 新增
GetListAsync(ListQuery)(DAL / BLL / 接口三层):不分页列表查询,未声明排序键默认 Id 降序,rows正数按MaxPageSize收敛、0 表示不限制;排序处理与 Keyset / Offset 共用同一缓存路径(按名 + 表达式指纹) - Demo 示例:
TopicBLL.GetListByQueryAsync(标题前缀 + 软删过滤 + 投影)→GET /api/Test/Topic/list/query
破坏性变更 — 分页三态收敛
- 旧分页
GetListAsync(page, pageSize, options)更名为GetListPageAsync(DAL / BLL / 接口,返回ListPage<TResult>两段式契约不变)——分页三态:GetListPageAsync(ListPage 契约)/GetKeysetPageAsync/GetOffsetPageAsync - 删除(未正式使用、无兼容负担):分页版
GetRawListAsync(page, pageSize, options)(与GetListPageAsync重复)、不分页GetRawListAsync(options / condition 两个重载)、同步GetRawList(两个重载)、同步GetList(元组版)、obsolete 同步FirstOrDefault - 旧方法族内部迁移:级联 / 键值对 / 详情 / 统计改走
GetListAsync(ListQuery.FromOptions(...))——ListQuery.FromOptions为内部适配器,以IQueryOptions为参数的方法族(GetCascadeAsync/GetKvpsAsync/GetByIdAsync等)公共签名不变
Keyset 游标格式定稿(破坏性,未发布无兼容约束)
- 枚举游标值仅存底层数值(移除内嵌
AssemblyQualifiedName类型名——游标更短、解码不再有Type.GetType类型加载路径) KeysetCursor.Decode移除双参数重载,expectedTypes必填(按期望类型还原值,键值数量/类型强校验,无 legacy 纯格式解码路径)Prev方向 + 空游标按首页处理(修复前会反转排序、返回排序末端的"最后一页")
Keyset / Offset 性能优化
- 排序方案缓存到 query 实例(
Lazy+ 线程安全发布,首次解析后复用——翻页不再重复指纹拼接 / 结果类型反射校验) - 任意表达式排序应用器编译委托按表达式指纹缓存(
KeysetRuntimeOptions.MaxExpressionAppliers,默认 1024,超限退化为每次编译) - 投影 DTO 值读取器缓存容量上限(
KeysetRuntimeOptions.MaxDtoGetters,默认 1024,消除动态投影类型的内存膨胀) - 新增
KeysetRuntimeOptions静态类统一承载引擎级缓存上限配置 - Offset 排序复用
ApplyOrderKey(与 Keyset 同路径:表达式剥壳统一 + 按名 / 表达式指纹双缓存);IsEntityIdKey提取为共享判断(Keyset / Offset 兜底逻辑收敛)
修复
- 级联树递归模式:调用方
Condition从"被覆盖丢弃"修正为每层生效(与注释语义及批量模式一致) - CA2208:
KeysetQuery两处ArgumentException的paramName由属性名改为不传(null)
(测试基线 227 → 241:新增 SortPlan 缓存 3 例、游标格式 3 例、Prev 空游标 1 例、ListQuery 集成 3 例)
8.9.0
2026年8月12日 星期三
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.9.0
新功能 — ResolveSort 排序键解析虚方法
WebApiBaseBLL新增protected virtual ResolveSort(orderBy):返回(Expression<Func<TModel, object?>> Selector, string Name),默认按主键 Id 排序;子类覆写 switch 表达式声明"字段名 → (排序表达式, 排序键名)"(Name 支持点分路径),未知名 / null 回退_兜底分支——消除子类 Keyset / Offset 查询重复的排序分支 switch- Demo
TopicBLL覆写(7 键),OffsetGetListAsync与 KeysetGetKeysetListAsync共用ResolveSort;Offset 排序表达式统一为n.Stats == null ? 0 : ...0 兜底(与 Keyset 及投影 null 兜底语义一致,避免 NULL 排序行为差异) - Selector 用
object?与KeysetQuery.OrderBy形参天然一致(Keyset 路径零转换);QueryOptions.OrderBy(object形参)赋值处用双重转换对齐 NRT 注解(运行时类型相同) - 测试:新增
SortSelectorTests4 例(基线 227 → 231)
8.8.0
2026年8月11日 星期二
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.8.0
新功能 — Keyset 排序键支持点分路径(嵌套 DTO 提取排序值)
KeysetSortKey.GetValueFrom/ResolveSort结果类型校验支持点分路径:排序键名可写为"Stats.Heat",游标编码按点分路径逐段从投影 DTO 的嵌套对象内提取排序值(无需再把排序字段平铺到 DTO 顶层,响应可保留内嵌层级)- 用法:
query.OrderBy(n => n.Stats == null ? 0 : n.Stats.Heat, name: "Stats.Heat").Select(n => new { ..., Stats = n.Stats == null ? new { Heat = 0, ... } : new { n.Stats.Heat, ... } })——排序分支与投影的 null 兜底语义须一致(SQL 与游标值不漂移);嵌套对象为 null 时游标编码抛带指引的ArgumentException(不裸抛NullReferenceException) - Demo 已迁移:
TopicBLL.GetKeysetListAsync返回的 items 内嵌stats对象({ items: [{ ..., stats: { heat, likeCount, ... } }], pageInfo })
修复 — bool 排序键谓词构建崩溃
KeysetPredicateBuilder.BuildCompare对 bool 与枚举对称处理:转int比较(false=0 < true=1),修复按 bool 字段排序时携带游标翻页(第 2 页起)抛InvalidOperationException(GreaterThan未定义于 bool)→ 500 的问题
重构 — OrderBy 全能化(合并并删除 OrderByExpr)
KeysetQuery/OffsetQuery的OrderBy升级为任意表达式排序:直接属性访问(n => n.CreatedAt)仍走按名缓存快路径(指纹格式不变,既有游标兼容);导航属性(n => n.Author.Name)/计算字段(n => n.Price * n.Quantity)走表达式重建路径(指纹附加结构签名 + 闭包捕获值)OrderBy新增可选name参数(与旧OrderByExpr一致):缺省依次取直接属性名 → 成员链最末成员名(n.Author.Name→"Name")→ 表达式结构签名;投影 DTO 场景显式传名,排序字段位于嵌套对象内时用点分路径("Stats.Heat")- 原
OrderByExpr已删除(外部未使用,无兼容负担),调用处直接改名OrderBy即可;顺带移除KeysetExpression.GetMemberName(旧OrderBy的直接属性强校验方法,升级后为死代码) - 工具类可见性收窄:
KeysetSortKey/KeysetExpression/KeysetPredicateBuilder降为internal(外部零引用,缩小公共 API 面)
测试(基线 220 → 227)
- 嵌套 DTO 点分路径翻页 + Prev、嵌套类型缺字段静态校验、嵌套对象 null 指引、
TResult=object匿名内嵌投影翻页、内嵌 + 顶层混合双键排序、bool 排序键翻页 + Prev、字符串排序键(string.CompareTo)多页翻页;原 19 例表达式排序集成测试(Keyset 15 + Offset 4)改调OrderBy后全部通过
8.7.0
2026年8月11日 星期二
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.7.0
破坏性变更 — 分页结果契约属性顺序调整与 required 强化
KeysetPage<T>/OffsetPage<T>的PageInfo/Pagination属性移至Items之前- 两个属性均改为
required init(移除默认值= new()和= []),调用方构造时必须显式提供所有属性 - 迁移指南:JSON 反序列化顺序可能变化,手动构造对象时需调整属性顺序或使用命名参数
8.6.0
2026年8月10日 星期一
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.6.0
新功能 — 任意表达式排序(OrderByExpr)
KeysetQuery/OffsetQuery新增OrderByExpr(selector, ascending, name):支持按导航属性(n => n.Author.Name)与计算字段(n => n.Price * n.Quantity)排序,突破OrderBy的直接属性访问限制(OrderBy约束不变)- 排序键名缺省时依次取:直接属性名 → 成员链最末成员名(
n.Author.Name→"Name")→ 表达式结构签名;投影 DTO 场景建议显式传name(须与 DTO 属性一致,游标编码按名提取排序值) - 游标指纹对任意表达式附加表达式结构签名 + 闭包捕获值:排序表达式(含闭包值)变化后旧游标抛
ArgumentException(控制器边界映射 400),不静默错页;直接属性排序指纹格式不变,旧游标兼容 - 主键 Id 唯一性兜底判断收窄为"实体自身主键"(修复
OrderByExpr(n => n.Author.Id)派生名恰为 "Id" 时误判已兜底的隐患);KeysetSortPlan排序值类型改从表达式体类型推导 - 实体结果 + 导航属性排序需先
.Apply(q => q.Include(...))加载导航,或改用投影 DTO;未加载或排序值本身为 null 时,游标编码抛带指引的ArgumentException(不再裸抛NullReferenceException) - 新增
KeysetExpression表达式辅助:TryGetMemberName/ResolveSortKeyName/UnwrapBody/ReplaceParameter;直接属性排序仍走按名缓存快路径,仅任意表达式排序走表达式重建路径
破坏性变更 — 旧分页列表契约两段式(ListPage)
- Common 新增
ListPage<T>(sealed、init-only):{ required Pagination Pagination, required List<T> Items } GetListAsync(DAL / BLL / 接口)返回由(Pagination, List<TResult>)元组改为ListPage<TResult>;BLL 版由返回IActionResult(PaginationOk包装)改为直接返回ListPage<TResult>- 分页版
GetRawListAsync(page, pageSize, options)(BLL / 接口)返回由(Pagination, List<TResult>)改为ListPage<TResult>;不分页版GetRawListAsync(options / condition, rows)返回List<T>不变 - 上述方法移除
[Obsolete](旧分页 API 以ListPage契约保留,不再是过时方法);同步版GetList/GetRawList仍标记[Obsolete] WebApiResponse新增PaginationOk<TResult>(ListPage<TResult>)重载;BaseDAL.List.cs/WebApiBaseBLL.List.csregion 更名(旧分页列表→分页列表、旧不分页列表→不分页列表)- 破坏性:公开 API 返回类型变更,调用方需迁移(元组解构、
PaginationOk(pagination, list)包装改为直接消费ListPage)
8.5.0
2026年8月9日 星期日
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.5.0
重构(行为不变)
WebApiBaseBLL.Cascade级联树构建方法BuildCascadeTree/BuildChildrenFromDict改 static(无实例状态依赖),NullableKeyWrapper改主构造函数(readonly struct保留)
代码整理(行为不变)
WebApiBaseBLL.List.cs/BaseDAL.List.csregion 重组:Keyset 分页 / Offset 分页拆为独立 region,旧分页列表与不分页列表 region 更名(旧分页列表/旧不分页列表)BaseDAL.Keyset.cs/BaseDAL.Offset.cs分页结果对象初始化顺序调整(Items赋值移至PageInfo/Pagination之后),无逻辑变化
缓存体系重构(破坏性 — useCache 三态化)
useCache参数bool → bool?三态语义:null(默认)不缓存 /true强制缓存 /false强制回源;源码兼容(bool隐式转bool?),二进制需重编译- 门面方法(
GetAsync/GetKvpsAsync(defaultName)/TotalAsync):useCache ?? CacheEnabled(端点开关)——端点开关控制端点路径,调用方显式传参可覆盖开关 - 共享方法(
GetByIdAsync实体版/投影版、GetRawKvpsAsync等):只响应调用方显式传入的三态参数,不再查询任何开关——自定义业务方法显式传useCache: true/false自由控制缓存,与端点开关互不干扰 - ⚠️ 行为变化:直接调用共享方法(不传
useCache)从"默认缓存"变为"默认不缓存"
- 门面方法(
- 新增端点级缓存开关
CacheEnable(Common):9 个开关默认全关(GetDetailsAsync/GetAsync/GetDetailsForAdminAsync/GetForEditorAsync/GetForAdminEditorAsync/GetSimpleAsync/GetListItemAsync/GetKvpsAsync/GetTotalAsync),按控制器类型RegisterCacheEnable<TController>()注册并由构造函数同步到 BLL;删除原方法级开关GetByIdAsync/GetRawKvpsAsync(11 → 9) - 缓存默认关闭:注入
ICacheManager后读方法默认也不缓存,需显式开端点开关(端点路径)或传useCache: true(自定义方法)
缓存方法扩展
GetCascadeAsync(级联树)新增三态缓存:键含加载模式(batch/recursive)+keySelector/Condition/OrderBy/Selector表达式指纹 + 排序/标签;QueryFunc/SelectFunc为委托无法指纹化,非null时强制回源(防串数据)CountAsync/ExistAsync/AnyAsync新增三态缓存:count:{表达式指纹}/exist:{表达式指纹}键;ExistAsync与AnyAsync语义等价共享缓存键互相命中
缓存健壮性增强
- 故障降级:缓存/版本号存储的读写异常不影响主流程——读失败按未命中回源、写失败仅记录警告、值反序列化失败回源重写、版本号存储不可用无法拼键直接回源;仅
OperationCanceledException向上传播(取消不降级) ICacheManager.GetAsync<T>新增CachePolicy? policy可选参数:命中时续期滑动过期(与GetOrAddAsync行为统一,修复纯读不续期导致高频键过期的问题)- 续期节流:命中续期窗口 = 滑动 TTL 的 1/2,高 QPS 热键减少 Redis 往返(默认滑动 4h 最多每 2h 续一次);
CacheManager.MaxThrottleEntries(默认 50k)超过后过滤重建清理,误删无害(下次多续一次) - 删除
WebApiBaseBLL中与CachePolicy.Default重复定义的DetailCachePolicy(值完全相同),详情投影缓存统一使用CachePolicy.Default
8.4.0
2026年8月8日 星期六
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.4.0
新功能 — TimeProvider 时间源注入(可测试)
WebApiBaseBLL/BaseDAL主构造函数新增可选参数TimeProvider? timeProvider = null(默认TimeProvider.System,与DateTimeOffset.Now行为一致)- 新增
protected TimeProvider TimeProvider属性,BLL / DAL 子类可直接使用统一时间源 - 审计时间字段(
CreatedAt/UpdatedAt/DeletedAt)与统计过滤(TotalAsynclastDays)共 10 处DateTimeOffset.Now全部改由注入的TimeProvider提供 - 测试可注入
FakeTimeProvider(Microsoft.Extensions.TimeProvider.Testing包)精确控制时间、断言审计字段;DI 注册builder.Services.AddSingleton(TimeProvider.System)后自动注入 - 源码兼容:可选参数,现有构造调用(位置 / 命名 / DI)零改动,未注册
TimeProvider时行为不变;升级需重新编译(构造签名变化,二进制不兼容)
8.3.0
2026年8月8日 星期六
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.3.0
新功能 — DatabaseExtensions
- 新增
DatabaseExtensions:Migrate<TDb>()/EnsureCreated<TDb>(isDelete)由Crping.EFCore.Controller包的Utils类迁入(命名空间改为Crping.EFCore),消除 Controller 包承载 EF 工具方法的不合理依赖
8.1.0
2026年8月8日 星期六
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.1.0
破坏性变更 — 分页结果契约两段式
GetOffsetPageAsync返回OffsetPage<T>→{ items, pagination };GetKeysetPageAsync返回KeysetPage<T>→{ items, pageInfo }(顶层扁平属性移除,调用方改用page.Pagination.X/page.PageInfo.X)- Demo BLL 6 处
PaginationOk(pageResult.ToPagination(), pageResult.Items)手动转换删除,直接Ok(pageResult)返回两段式契约
8.0.0
2026年8月7日 星期五
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 8.0.0
破坏性变更 — ConnectionName 只读化
IMyDbContextFactory.ConnectionName由可变属性改为{ get; init; }(构造后不可修改)- 一个工厂实例对应一个连接目标:运行时修改连接名的代码须改为构造传参(
new MyDbContextFactory<TDb>(configuration, "连接名"))或每连接一个工厂实例 - 目的:消除 Singleton 工厂跨请求竞态(多租户数据隔离);
Config.Options字典只读查询,不再有运行时切换路径
新功能 — 缓存方法级开关(useCache)
- 所有自动走缓存的读方法新增
bool useCache = true可选参数,传false强制回源查询,实现"全局缓存配置 + 个别方法不缓存":GetByIdAsync(id)/FindAsync(id)/GetModelAsync(id)/GetAsync(id)(详情)GetByIdAsync(id, options/selector)(投影详情系列)TotalAsync(lastDays, policy)(统计)GetRawKvpsAsync/GetKvpsAsync(键值对)
- 默认
true,现有调用零改动(source 兼容);TotalAsync()无参重载保持无参签名,避免与TotalAsync(int=0,...)形成无参调用歧义
修复
- 共享 DbContext 守卫收窄:仅拦截"会 Dispose 共享实例的切换"(读库切换仍抛异常),共享后
SetWriteDb()空操作路径放行——修复跨 BLL 事务编排(ShareDbContext+ 多 BLL 写方法)被误拦截的问题,新增DomainTransactionTests集成测试复现验证
缓存扩展重命名
AddCrpingRedisCache→AddEFCoreRedisCache、AddCrpingMemoryCache→AddEFCoreMemoryCache:方法名前缀与库名对齐(Crping.EFCore→EFCore);AddEFCoreRedisCache改为惰性连接(首次解析IConnectionMultiplexer时才ConnectionMultiplexer.Connect,避免注册即急切连接导致应用启动失败),并补充空配置参数校验
7.7.0
2026年8月7日 星期五
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 7.7.0
读写分离健壮性(2026-08-07)
- DAL 写操作强制走写库:Create / Update / Delete 全部写方法入口调用
SetWriteDb()(18 处),解决"先读后写"场景(_currentReadOnly=true)下写操作落到只读副本的隐患;共享实例(_currentReadOnly=false)下为空操作,不 Dispose - 共享 DbContext 切换守卫:共享模式下(
_isSharingDbContext)切换到会 Dispose 共享实例的 DbContext 时抛明确异常,防止跨 BLL 事务中误释放共享实例 - 只读副本故障摘除:新增
ReadOnlyFailureCooldown(默认 30 秒冷却)+ValidateReadOnlyOnCreate(默认开启探活)——副本连接失败进入冷却期轮询跳过,到期自动重新尝试恢复;仅连接类异常(DbException)摘除,其他异常原样抛出;单副本走快速路径不探活 - 多从库轮询计数器溢出修复:
Interlocked.Increment结果用 uint 取模,避免计数器回绕到int.MinValue时Math.Abs溢出为负数导致负索引 - 写后读绑定(read-your-writes):新增
ReadWriteRouting.BindReadToWrite静态开关(默认false,不改变现有负载分布);开启后请求内发生写操作则后续读自动绑定主库,直到请求结束(AsyncLocal隔离请求);⚠️ 开启后必须在请求管道注册app.UseReadWriteRouting()(ReadWriteRoutingMiddleware,请求开始/结束复位写标志),否则写标志在非 ASP.NET Core 场景(后台服务 / 长生命周期作用域)会跨请求残留
破坏性变更 — 统一缓存管理(全新设计,替换旧缓存实现)
- 新增
ICacheManager统一缓存门面(Common 层,无 EF 依赖):泛型GetOrAddAsync<T>/GetAsync<T>/SetAsync<T>/RemoveAsync/InvalidateAsync,内置 JSON 序列化、单飞防击穿、空值防穿透 - 模型版本号失效:每个模型一个版本号(
ICacheVersionStore),缓存键携带版本号({FullName}:v{n}:detail:{id});写操作(Create / Update / Delete)成功后只递增版本号,该模型全部缓存键原子性失效,无需枚举键,旧键由 TTL 兜底清理 - 版本号存储双实现:
RedisCacheVersionStore(StackExchange.RedisINCR原子递增,分布式全局生效)、MemoryCacheVersionStore(Interlocked,单机/测试) - 注入方式变更(破坏性):
WebApiBaseBLL/BaseDomain/ 所有 Controller 基类的构造参数由IDistributedCache?替换为ICacheManager?;IKit.Cache/ICacheBLL.CacheManager类型同步更新;旧的GetCacheValueAsync(int 专用)/GetTotalCacheKey/InvalidateTotalCacheAsync已移除 - 自动缓存接入:
GetByIdAsync/FindAsync详情(detail:{id})、GetRawKvpsAsync键值对(kvp:{selector}:{condition}:{ascending})、TotalAsync统计(total:{lastDays})三类读方法自动走缓存,未注入缓存管理器时直查数据库(零开销) - 缓存策略
CachePolicy:滑动/绝对过期、CacheNull空值防护开关 - DI 注册示例(Redis):
AddSingleton<ICacheVersionStore, RedisCacheVersionStore>()+AddSingleton<ICacheManager, CacheManager>() - 新增测试:
CacheManager命中/未命中/滑动刷新/空值防穿透/版本号失效、写操作失效、单飞防击穿
DI 一键注册(2026-08-07)
AddCrpingRedisCache(configuration):一行注册IConnectionMultiplexer(单例复用连接)+RedisCacheVersionStore+CacheManagerAddCrpingMemoryCache():单机/测试场景,MemoryCacheVersionStore+CacheManager- ⚠️ 注意:
AddStackExchangeRedisCache只注册IDistributedCache,不注册IConnectionMultiplexer——版本号 INCR 依赖后者,必须额外注册(AddCrpingRedisCache已包含);业务数据存储仍需自行调用AddStackExchangeRedisCache - 8.0.0 起两个方法重命名为
AddEFCoreRedisCache/AddEFCoreMemoryCache(前缀与库名对齐),AddEFCoreRedisCache改为惰性连接 + 参数校验
缓存健壮性增强(2026-08-07)
- 修复 Kvp 缓存键闭包冲突(正确性):
CacheKeys.BuildExpressionKey提取表达式结构签名 + 闭包捕获值,同一 lambda 位置不同捕获值时键不同,杜绝不同请求串数据(此前Expression.ToString()对闭包变量生成相同字符串) - 详情投影版接入缓存:
GetByIdAsync(id, selector/options)键含条件/投影指纹,GetDetailsAsync/GetForEditorAsync等投影端点生效 - 版本号本地短 TTL 缓存(
CacheManager.VersionLocalCacheTtl,默认 5 秒):避免每次读请求访问 Redis 取版本号;写操作同步刷新本地值,写后立即读到新版本 - 命中率统计:
ICacheManager.Stats(按模型键聚合 Hits / Misses / HitRate),可观测性 - 防雪崩:
CachePolicy.ExpirationJitter绝对过期时间叠加 [0, Jitter) 随机偏移,同一批键过期时间错开
7.6.0
2026年8月6日 星期四
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 7.6.0
Offset 偏移分页统一
- 新增
GetOffsetPageAsync(query, page, ct):返回OffsetPage<T>契约(Items + Page + PageSize + RecordCount + PageCount + HasNext + HasPrev + OrderBy + Ascending),与GetKeysetPageAsync/KeysetPage<T>完全对称 - 新增
OffsetQuery<TModel, TKey, TResult>查询构建器:链式OrderBy(未声明排序键默认 Id 降序,显式排序自动追加 Id 唯一性兜底)/Where/Apply/Select/TagWith,与KeysetQuery同构 - 旧偏移分页方法标记
[Obsolete](签名不变,完全兼容):GetListAsync(DAL/BLL)、GetRawListAsync(BLL 分页版),请迁移至GetOffsetPageAsync - Controller 新增
GET /Page端点(MethodsEnable.GetOffsetPageAsync开关),直接返回 OffsetPage 契约 - Demo 全部迁移:
MemberController新增GET /OffsetPage演示新契约直出;其余 BLL 内部切换至新 API 并保持旧响应形状 - 适用场景提示:Offset 适合中小数据集与按页码跳转;大数据集、深分页、高频翻页请使用 Keyset 游标分页(
GetKeysetPageAsync),避免大偏移(OFFSET)扫描 - 页码越界宽容处理:
page < 1收敛为首页,page > PageCount返回空页(不抛异常)
修复与增强(2026-08-06)
- 分页健壮性:Offset 分页计算改用 long 中间量防 int 溢出(
pageSize * (page - 1)/recordCount + pageSize),极端页码下 OFFSET 截断保护 - 投影防护:未指定投影(Select)且结果类型与实体类型不一致时,
GetOffsetPageAsync/GetKeysetPageAsync提前抛出明确的ArgumentException,替代运行时InvalidCastException - 查询管道去重:新增
BaseDAL.Query.cs共享辅助BuildBaseQuery/ExecuteProjectionAsync,GetListAsync/GetOffsetPageAsync/GetKeysetPageAsync三处统一复用(投影防护一处修改、三处生效) - 文件重命名:
BaseDAL.OffsetPage.cs→BaseDAL.Offset.cs(与BaseDAL.Keyset.cs等功能名命名对齐)
Demo 修复(2026-08-06)
Topic列表投影 N+1 修复:Images改为一对多导航属性(Topic.Images+WithMany配置),ListItemSelector由Db.Set<TopicImage>()客户端评估(逐行查询)改为 EF 可翻译的相关子查询(单条 SQL);迁移快照已同步,无 schema 变更
修复与增强(2026-08-07)
- 恢复写操作自动失效缓存:Create / Update / Delete 全部写路径在成功后自动调用
InvalidateTotalCacheAsync()(7.0.0 曾改为手动失效,现恢复自动),仅在实际写入成功(Created/NoContent/Ok/count > 0)时失效:- Create:
CreateModelAsync、批量CreateAsync、GetOrCreate*系列 - Update:
UpdateModelAsync/UpdateSelectedPropsAsync/UpdateIgnoreAsync/UpdateModelListAsync/UpdateListAsync/ExecuteUpdateAsync/ 三个 Toggle 状态切换 - Delete:
SoftDeleteAsync/DeleteListAsync/ExecuteDeleteAsync/ResetSoftDeleteAsync
- Create:
- 缓存键防冲突:
GetTotalCacheKey由typeof(TModel).Name改为FullName(含命名空间),避免不同库/命名空间同名模型缓存键冲突 - 缓存击穿防护(单飞):
GetCacheValueAsync每个缓存键一把SemaphoreSlim(_cacheLocks并发字典),并发未命中合并为一次回源查询,写缓存后其余等待请求直接读取 - 写失败清理跟踪状态:
DbUpdateConcurrencyException/DbUpdateException后调用Db.ChangeTracker.Clear()(BaseDAL Create / Update / Delete),避免失败状态污染同一 DbContext 的后续操作 - 软删除补全审计字段:单条软删除若实体实现
IDeletedAt则自动补DeletedAt ??= DateTimeOffset.Now;批量软删除(实现IDeletedAt)ExecuteUpdateAsync同时写入IsDeleted+DeletedAt BaseDAL生命周期契约:文档注释明确 Scoped 生命周期(每个请求独立实例,禁止跨请求/线程共享);Db属性与SetDb增加ObjectDisposedException.ThrowIf防护,释放后继续访问抛明确异常ResultCode新增Unauthorized(401):WebApiResponse.Res映射ResultCode.Unauthorized→Unauthorized(),用于未登录/身份验证失败场景- 创建冲突防护:
CreateModelAsync捕获DbUpdateException(唯一键/主键冲突)返回Conflict而非 500;TryCreateAsync(models, condition)改为整体事务(BeginTransactionAsync),任一冲突整体回滚,避免部分成功;GetOrCreateAsync提取GetOrCreateCoreAsync内核逻辑,事务边界由调用方决定 - 新增单元测试:
BatchCreateTransactionTests(批量创建事务回滚)、CacheKeyInvalidationTests(写操作后缓存失效)、FactoryRoundRobinTests(读副本轮询)、Controller/ExceptionMiddlewareTests(5xx 不泄露异常消息)
7.5.0
2026年8月6日 星期四
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 7.5.0
破坏性变更 — Keyset 游标分页重写
GetKeysetListAsync/GetKeysetRawListAsync移除,替代为GetKeysetPageAsync(query, cursor, direction),返回KeysetPage<T>(Items + NextCursor / PrevCursor + HasNext / HasPrev)- 游标为不透明 Base64Url 令牌(
KeysetCursor):携带排序指纹防错配;可配置KeysetCursor.SigningKey启用 HMAC 签名防伪造与篡改 - 多字段排序生成全字段行值复合比较(
k1 ▷ c1 OR (k1 = c1 AND k2 ▷ c2) ...),修复首键重复时翻页丢数据问题 - 排序键自动追加主键 Id 唯一性兜底,保证排序全序;投影 DTO 必须包含排序字段
- 使用说明详见 docs/Keyset游标分页.md
健壮性与性能
KeysetQuery解析排序方案不再修改内部状态,构建后可并发复用- 游标取值改用编译表达式缓存:实体路径 AOT 安全,投影 DTO 走缓存成员访问,消除逐次反射
- BLL 层对无效游标(过期缓存/篡改)记录警告日志
- 新增 SQLite 集成测试(翻页正确性、同值首键、反向翻页、投影、签名、篡改防护)
修复与增强(2026-08-06)
- 修复空页游标契约:游标指向的行被删除时,
HasNext/HasPrev与游标值保持一致,不再出现"有标志无游标"的矛盾 - 恶意/篡改游标解码异常统一包装为
ArgumentException(签名校验、JSON 反序列化、值解析全流程),不再以 500 泄漏 - 枚举排序键解码按排序方案值类型校验(消除对程序集版本脆弱的
Type.GetType),并修复枚举排序键翻页崩溃(比较表达式转底层数值类型) RangeLimit语义修正:<1归 1、超过MaxPageSize封顶;GetRawList*的rows<=0表示不限制KeysetPage属性收紧为init,响应构造后不可篡改KeysetQuery.Where(bool when, ...)新增条件式过滤重载:条件不成立时整体忽略,消除调用处分散的 if 分支KeysetQuery支持objectTResult:可直接复用ListItemSelector(返回 object 的匿名投影)作为 Selector,游标按元素运行时类型自动取排序值(Demo 已演示GetKeysetListAsync)
7.4.0
2026年8月3日 星期一
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 7.4.0
破坏性变更 — 枚举重命名
DalResult枚举重命名为ResultCode,全项目 163 处引用更新- 文件从
DalResult.cs改为ResultCode.cs
迁移指南
- 将代码中所有
DalResult替换为ResultCode
7.3.0
2026年7月30日 星期三
与
Crping.EFCore.Common/Crping.EFCore.Controller同步升至 7.3.0
破坏性变更 — 时间类型从 DateTime 改为 DateTimeOffset
- 所有 BLL 层的
DateTime.Now改为DateTimeOffset.Now(WebApiBaseBLL.cs、WebApiBaseBLL.Update.cs、WebApiBaseBLL.Delete.cs) - 统计查询
DateTime.Today改为DateTimeOffset.Now(WebApiBaseBLL.Statistics.cs) EntityHelper.DefaultTime类型从DateTime改为DateTimeOffsetEntityHelper.GenerateId()参数fixedTime从DateTime?改为DateTimeOffset?Utils.CustomTimestamp()参数fixedTime从DateTime?改为DateTimeOffset?EFCoreExt.UpdateTo()中DateTime.Now改为DateTimeOffset.Now
迁移指南
- 将实体中的
DateTime属性改为DateTimeOffset DateTime.Now改为DateTimeOffset.Now- 数据库列类型从
datetime2改为datetimeoffset
7.2.0
2026年7月28日 星期一
接口更新
MyDbContextFactory<TDb>泛型约束从IDbConfiguration更新为IAppConfigurationIDbConfiguration已删除,统一使用IAppConfiguration
Demo 示例同步更新
EntityConfigure→EntityHelper:种子数据中 16 处引用已更新IMyConfiguration→IAppConfiguration:TestDbContext 已更新.Dom→.Domain:TopicController 已更新IEnabled→IIsEnabled:StatusToggleTests 已更新
单元测试修复
CreateTests:修正 3 个 CreateAsync 测试断言(ObjectResult+ 201 状态码替代CreatedAtActionResult)CreateTests:修正空 ID 测试,验证GenerateId返回空字符串时的行为
7.1.0
2026年7月28日 星期一
SQLite 数据库支持
EntityHelper.UseDefaultTime()新增sql参数,支持不同数据库的默认时间函数(SQLite:datetime('now'),PostgreSQL:NOW(),MySQL:CURRENT_TIMESTAMP)EntityHelper.UseStringKey()/UseStringBin()新增collation参数,支持不同数据库的排序规则(SQLite:BINARY)- 消费者只需在
DbContext.OnConfiguring()中调用UseSqlite(ConnectionString)即可切换到 SQLite
新增类(向后兼容 — 旧类标记 [Obsolete])
EntityHelper:实体配置辅助类,替代EntityConfigure(名称更清晰)WebApiIntBLL:基于 int 类型用户主键的 BLL,替代WebApiForIntBLLWebApiLongBLL:基于 long 类型用户主键的 BLL,替代WebApiForLongBLL
新增方法
EntityHelper.UseUrl():URL 路径配置方法(C# 命名规范:Url而非URL)
重命名(向后兼容)
EntityConfigure→EntityHelper:旧类保留静态成员转发,扩展方法仅在新类中UseURL→UseUrl:旧方法标记[Obsolete]并转发到新方法WebApiForIntBLL→WebApiIntBLL:旧类保留并继承新类WebApiForLongBLL→WebApiLongBLL:旧类保留并继承新类
代码质量
- 修复
EntityHelper.csregion 注释拼写错误:Decimel→Decimal - 重命名私有常量
parent_error_in_create→ParentErrorInCreate(PascalCase) - 重命名私有字段
_dom→_domain(避免与 DOM 混淆) - 重命名文件
BaseDAL.Kvps.cs→BaseDAL.Kvp.cs,WebApiBaseBLL.Kvps.cs→WebApiBaseBLL.Kvp.cs
7.0.0
2026年7月26日 星期日
重大版本:含多项破坏性变更,下游升级请阅读完整说明
破坏性变更(必须关注)
- DAL 层返回值由 HTTP code (int) 改为
ResultCode枚举(200/201/204/400/403/404/409),解耦与 ASP.NET CoreStatusCodes的耦合;BaseDAL公开方法签名变化 BaseDAL和WebApiBaseBLL拆分为 partial class(按职责分离到独立文件:Create/Delete/Detail/Kvps/List/Persistence/Statistics/Update),部分内部 API 可见性变化(IDE 的"转到定义"层级会变)
安全性与并发
- DAL 层捕获
DbUpdateConcurrencyException,写操作统一返回Conflict(409);先检查实体是否存在,不存在则返回NotFound(404) - 批量操作(obsolete 路径)启用事务保护(
BeginTransactionAsync),updater 抛异常时回滚
性能
DeleteListAsync/UpdateListAsync改用ExecuteUpdateAsync/ExecuteDeleteAsync服务端执行,不再加载全部数据到内存Kvps查询改用编译表达式,绕过AsNoTracking实体的 ChangeTrackerMyDbContextFactory读副本轮询改Interlocked.Increment无锁化(替代Lock + ++index)IgnoreSpecialProperties字典映射替代 10 个if检查
可靠性与一致性
BaseDAL.Dispose模式补全:_disposed标志防重复释放;移除冗余GC.SuppressFinalizeWebApiBaseBLL移除Dispose,由 DI 容器统一管理 DAL 生命周期- 缓存层:Create / Update / Delete 不再自动调用
InvalidateTotalCacheAsync();下游若依赖 CRUD 后缓存自动失效,需改为显式调用(注意:此变更为不对称 Revert 的结果,详见 git log92a976e↔8fc943c)
API 与代码组织
- WebApiBaseBLL 同步方法标记
[Obsolete],与 BaseDAL 对齐 - WebApiBaseBLL 提取
PrepareForCreate/ValidateUpdatePreCheck/ValidateUpdateCore/BuildSelfOnlyCondition,消除多处重复 - 级联查询支持 batch 模式(
batchMode参数,一次性加载全部);引入AsyncLocal隔离IsVerifyOwner/GetCurrentUserId;新增NullableKeyWrapper解决 nullableTKey在Dictionary中的约束 BaseDAL.SetWriteDb恢复跟踪行为;Statistics统一AsNoTracking并简化为表达式体Update错误处理提取为私有方法;Kvps简化IDomain中未使用的 HTTP 类型依赖相关 refactor(commit3eb4504)已被回退(65ae448),净状态未变DateTime.Now → DateTime.UtcNow改造 + 注释死代码清理(commitff13f66)已被回退(bf06cb9),净状态未变
项目结构
- SLN 嵌套重构:将
Crping.EFCore项目从仓库根目录迁入Crping.EFCore/子目录下(与Common/Controller一致),slnx与ProjectReference路径相应更新
文档
- 所有类与方法补全 XML 文档注释
- 完整内部重构与注释微调(
aa86f80/9c916de/13d591c)
6.11.0
2026年6月16日 星期二
- 优化
Create,Update,GetOrCreate相关方法 - 移除接口
IDomainCommonMethods - 移除
BaseDomain中方法的virtual修饰符 - 升级所有相关组件版本
6.10.0
2026年6月9日 星期二
- 删除大部分方法的
virtual修饰符 - 新增方法
FirstOrDefaultAsync<TOrderKey, TResult> (IQueryOptions<TModel, TOrderKey, TResult>? options),并添加相关接口方法 - 新增方法
FirstOrDefault<TOrderKey, TResult> (IQueryOptions<TModel, TOrderKey, TResult>? options),并添加相关接口方法
6.9.2
2026年3月20日 星期五
- 为
Create相关方法添加字符串类型Id的检查与赋值逻辑,并添加异常明确的消息提示
6.9.1
2026年3月18日 星期三
- 优化创建相关方法的Id生成逻辑
6.9.0
2026年2月26日 星期三
- 扩展接口支持管理员权限:修改
IDetailsSelector、IEditorSelector、IGetDetails、IGetForEditor、IListItemSelector等接口,添加管理员权限相关方法 - 增强
WebApiBaseBLL类:添加DetailsSelectorForAdmin、EditorSelectorForAdmin、ListItemSelectorForAdmin等属性,支持管理员权限的数据选择
6.8.0
2026年2月6日 星期五
- 新增方法
ToggleDeleteStatusAsync
6.7.0
2026年2月6日 星期五
- 优化软删除相关方法逻辑
- 新增状态切换方法
ToggleEnableStatusAsync,ToggleAuditStatusAsync
6.6.0
2026年1月20日 星期二
- 新增属性
Query - 新增一系列求和、平均值方法
- 新增方法
MaxPageSize并优化相关命名
6.5.0
2026年1月19日 星期一
- 新增工具类
Toolkit
6.4.0
2025年12月31日 星期三
- 删除接口
ICreateAndUpdateByDto - 优化所有命名空间
6.3.0
2025年12月28日 星期日
BLL添加并实现接口新增接口ICommonSelectors<TModel, TResult>,IListItemSelector<TModel, TResult>,IDetailsSelector<TModel, TResult>,IEditorSelector<TModel, TResult>,ISimpleSelector<TModel, TResult>,IGetKvps- 优化方法
GetKvpsAsync - 新增接口
ICreateAndUpdateByDto
6.2.0
2025年12月25日 星期四
- 新增接口
ICommonSelectors<TModel, TResult>,IListItemSelector<TModel, TResult>,IDetailsSelector<TModel, TResult>,IEditorSelector<TModel, TResult>,ISimpleSelector<TModel, TResult>,IGetKvps - 优化方法
GetRawKvpsAsync,GetKvpsAsync
6.1.0
2025年12月2日 星期二
- 新增接口
IDomainCommonMethods<TModel, TKey>,ICreateAsync<TModel>,IUpdateAsync<TModel, TKey>,IGetDetails<TKey>
6.0.0
2025年11月27日 星期四
- 升级为
.net10.0 - 适配
ExecuteUpdate相关方法
5.11.0
2025年11月13日 星期四
- 新增接口:
ICommonMethods<TKey>,IGetModelSelector
5.10.0
2025年11月2日 星期日
- 将所有列表方法的返回值从接口改为具体类型
- 新增
Keyset分页方法GetKeysetListAsync,GetKeysetRawListAsync
5.9.0
2025年9月25日 星期四
BaseDAL添加对IAudit,IDeletedAt的支持
5.8.0
2025年9月23日 星期二
- 优化
BLL,DAL中所有更新相关方法对特殊状态属性的更新保留,并重命名部分方法
5.7.0
2025年7月31日 星期四
BLL新增方法UpdateExcludeAsyncDAL方法UpdateAsync新增更多特殊状态属性的更新保留
5.6.0
2025年7月31日 星期四
- 将
Update相关方法的数组参数添加params
5.5.0
2025年6月18日 星期三
GetByOptionsAsync与GetBySelectorAsync重命名为GetByIdAsync
5.4.3
2025年5月23日 星期五
- 修复方法
SoftDeleteAsync(...)
5.4.2
2025年5月23日 星期五
- 修复方法
SoftDeleteAsync(...)
5.4.1
2025年5月23日 星期五
WebApiBaseBLL优化方法SoftDeleteAsync(...)
5.4.0
2025年5月22日 星期四
WebApiBaseBLL新增方法SoftDeleteAsync,ResetSoftDeleteAsync
5.3.0
2025年5月21日 星期三
- 修改
BaseDAL的方法RangeLimit(...)
5.2.0
2025年4月19日 星期六
WebApiBaseBLL新增方法GetByIdAsync,GetById
5.1.1
2025年4月9日 星期三
- 优化方法:
CustomTimestamp(...)
5.1.0
2025年3月25日 星期二
BaseDAL新增属性MaxList,新增方法SetMaxList(...)WebApiBaseBLL新增方法SetMaxList(...)- 所有列表方法新增对列表最大数量的控制
5.0.2
2025年3月9日 星期日
- 统一子组件传递依赖中的组件版本
5.0.1
2025年3月9日 星期日
- 删除未使用的引用
5.0.0
2024年11月17日 星期日
- 升级为
.net9.0 - 更新引用组件,并清理替换弃用的组件
4.20.0
2024年11月16日 星期六
AuthenticationHeaderValue修改为AuthHeaderValueGetAuthHeaderValue修改为GetAuthHeaderValue
4.19.0
2024年11月14日 星期四
BaseDomain新增委托GetAuthenticationHeaderValue以及属性AuthenticationHeaderValue
4.18.0
2024年11月14日 星期四
BaseDomain新增委托GetAuthorization以及属性Authorization
4.17.0
2024年11月12日 星期二
- 修改
EntityConfigure中方法UseURL的默认值 - 新增方法
SetTable
4.16.0
2024年10月22日 星期二
EntityConfigure添加方法ToStringId(...)并修改GenerateId(...)DefaultDate改为DefaultTime
4.15.0
2024年10月20日 星期日
- 新增方法
GenerateId(...),CustomTimestamp(...) - 修改方法
UseStringKey(...)的参数顺序
4.14.0
2024年10月19日 星期六
- 新增实体配置辅助类
EntityConfigure以及一系列常用方法
4.13.1
2024年10月16日 星期三
BLL方法GetByOptionsAsync(...)修改条件控制逻辑,优化使用外部传入控制条件
4.13.0
2024年10月12日 星期六
BLL新增接口IConfigBLLBLL修改类BaseDomain的构造方法参数与相关属性BaseDomain新增方法ShareDbContext
4.12.0
2024年10月11日 星期五
BLL新增接口IGetSimple<TKey>
4.11.1
2024年10月11日 星期五
BaseDomain优化方法SyncConfig(),并移除构造方法对SyncConfig()的调用
4.11.0
2024年10月11日 星期五
BLL修改CurrentUserId属性逻辑BaseDomain修改CurrentUserId属性逻辑,以及构造函数,自动同步BLL配置- 升级
EFCore相关包引用
4.10.0
2024年10月6日 星期日
- 修复
BLL中IQueryOptions的相关警告 BLL新增方法GetBySelectorAsync(...)BLL新增接口IGetListItem- 方法
GetForEditorAsync(...)修改为GetByOptionsAsync(...)
4.9.0
2024年9月25日 星期三
BLL新增接口IGetDetails<TKey>,IGetForEditor<TKey>BLL新增方法GetForEditorAsyncIQueryOptions添加泛型约束
4.8.0
2024年9月20日 星期五
- 跳过有
BUG的 4.7.0 BLL新增方法SetCurrentUserBaseDomain修改构造方法,新增可选参数,并新增属性BaseDomain新增方法SyncConfig
4.6.0
2024年4月19日 星期五
- 将
ILogger改为泛型 BaseDomain新增对接口IKit的实现,增加对log,cache的支持
4.5.0
2024年4月9日 星期二
- 类
WebApiBaseBLL新增可选构造参数IToolkit,以方便注入外部工具 WebApiBaseBLL相关子类新增可选构造参数IToolkit,以方便注入外部工具- 方法
CheckStringId(...)修改逻辑,以调用IToolkit中的方法
4.4.0
2024年4月7日 星期日
- 清除不再使用的接口,如:
IHostBLL等 - 添加新接口;
IGenerateId - 接口
IDomain添加委托;GenerateId
4.3.0
2024年3月28日 星期四
- 新增接口:
ICacheBLL - 修改接口:
IWebApiBLL,并新增IWebApiForIntBLL,IWebApiForLongBLL - 修改
WebApiBLL,并新增WebApiForIntBLL,WebApiForLongBLL - 新增委托:
GenerateId - 所有创建相关方法添加字符串类型 Id 的检查与赋值
4.2.0
2024年3月15日 星期五
- 新增方法:
Any(...),AnyAsync(...)
4.1.0
2024年3月10日 星期日
- 新增方法:
Forbidden(...),MethodNotAllowed()
4.0.0
2024年1月27日 星期六
- 升级目标框架为
.net8.0 - 升级
EF相关组件为8.0以上最新版权 - 降级两个官方弃用的包
- 与系列相关组件对齐版本号
3.44.1
2023年11月13日 星期一
SetDb(...)方法添加相同Db判断,跳过不必要的替换
3.44.0
2023-08-30
- 新增更简洁的获取键值对方法
GetKvpsAsync
3.43.0
2023-07-27
- 为方法
GetCacheValueAsync添加默认值
3.42.0
2023-07-24
- 修改
IRelatedBLL - 修改
Update相关方法对UpdatedAt,UpdatedBy的赋值逻辑
3.41.0
2023-07-04
- 新增
IDomain<TKey>,BaseDomain<TKey>
3.40.0
2023-06-29
- 为所有泛型参数
TKey添加约束notnull - 为用户操作自动绑定用户ID添加
TUserKey,并添加notnull约束 - 修改所有接口类因添加
TUserKey所引发的所有问题 - 修复所有创建功能因接口差异无法自动绑定创建者ID的问题
- 修复所有更新功能因接口差异无法自动绑定创建者ID的问题
- 修复所有删除功能因接口差异无法自动绑定创建者ID的问题
3.39.0
2023-06-11
DAL新增方法GetKeysetRawListAsync(...),及其接口BLL新增方法GetKeysetRawListAsync(...),及其接口BLL新增方法GetKeysetListAsync(...),及其接口WebApiResponse新增方法PaginationOk(...)- 新增
Keyset分页辅助类KeysetQueryOptions
3.38.0
2023-05-25
- 修改领域接口为
IDomain,并新增委托GetCurrentUserId() - 修改领域基类为
BaseDomain,并新增委托GetCurrentUserId()
3.37.0
2023-05-18
- 新增领域接口
IDomain<TUserKey> - 新增领域基类
BaseDomain<TUserKey>
3.36.1
2023-05-17
- 将方法
FirstAsync(...),First(...)的返回值改为非可空类型
3.36.0
2023-05-17
DAL新增方法:FirstAsync(...),First(...)BLL新增方法:FirstAsync(...),First(...)BLL新增方法:TryCreateAsync(...),TryCreate(...)
3.35.0
2023-05-17
- 新增方法
TryCreateAsync(...),TryCreate(...)
3.34.0
2023-05-12
- 新增种子数据接口
ISeedData<T>
3.33.0
2023-05-11
QueryOptions新增属性SelectFunc,并在所有列表相关方法中添加对其使用
3.32.0
2023-04-26
- 修复方法
GetOrCreateAsync(...)系列方法内部查询库与创建库不一致的问题 - 新增方法:
GetCascaderAsync(...)
3.31.0
2023-04-23
QueryOptions新增查询标签属性Tag- 为所有相关搜索列表方法添加对
TagWith的调用
3.30.0
2023-04-04
- 启用可空类型
- 修复启用可空类型后产生的所有警告
3.22.0
2023-04-04
DAL新增方法SetReadDbIfNull(),属性ReadIfNullDAL访问属性Db时,若为Null默认初始化为ReadDbBLL将默认调用的方法WriteIfNull改为ReadIfNull
3.21.0
2023-03-28
- 将接口
IConnectionString,IMyConfiguration转移到Common
3.20.1
2023-03-13
- 将
GetKvpsAsync的零值项目名称请选择改为全部
3.20.0
2023-02-24
DAL新增LongCountAsync,LongCountBLL新增LongCountAsync,LongCountBLL修改TotalAsync相关方法,新增最后几天限定统计
3.19.0
2023-02-17
DAL新增方法SetNoTracking,SetTrackAllDAL将_db等重要对象私有化DAL将所有对私有字段的访问改为公共属性,并去除所有多余的判空逻辑DAL在切换为只读库里自动关闭查询跟踪BLL新增属性Dal,用于统一验证BLL将所有对_dal字段的引用改为属性DalBLL并去除所有多余的判空逻辑BLL新增几个未添加的接口BLL新增接口IKit,属性Cache,Logger
3.18.0
2023-02-03
- 将运行时修改为
net7.0
3.17.1
2023-01-13
BLL修改方法Task<IActionResult> TotalAsync()中的默认缓存Key
3.17.0
2023-01-13
BLL新增方法Task<int> TotalAsync(string, TimeSpan?, TimeSpan?)BLL新增方法Task<IActionResult> TotalAsync()
3.16.0
2023-01-09
DAL新增方法(int, TModel) CreateModel(TModel model)BLL新增方法(int, TModel) CreateModel(TModel model)BLL将3.15版所有新增方法的返回值改为(int, TModel)
3.15.0
2023-01-09
DAL新增同步方法FirstOrDefault(condition)BLL新增方法GetOrCreate(model),GetOrCreateAsync(model)BLL新增方法GetOrCreate(model, condition),GetOrCreateAsync(model, condition)
3.14.0
2023-01-03
- 更新对
Crping.EFCore.Common的引用,EFCore相关组件的升级 - 新增全局引用
Global,并优化所有引用、命名空间、以及部分注释
3.13.0
2022-12-15
- 新增接口
IMyConfiguration,用于向DbContext相关类传递配置对象 MyDbContextFactory<TDb>类添加IConfiguration属性,并通过方法CreateDbContext将此属性赋值给DbContext相关类
3.12.0
2022-12-09
- 添加批量删除方法
ExecuteDelete,ExecuteDeleteAsync
3.11.0
2022-12-09
- 添加批量更新方法
ExecuteUpdateAsync,ExecuteUpdate
3.10.1
2022-11-28
- 解决'pagination'添加返回值'orderBy'在不同.net版本中的返回值不同的问题
3.10.0
2022-11-28
- DAL的列表分页返回类型'pagination'添加返回值'orderBy'
3.9.1
2022-11-26
- 解决
pagination添加返回值Ascending时queryOptions为空的Bug
3.9.0
2022-11-23
pagination添加返回值Ascending- 升级
EFCore到7.0.0
3.8.0
2022-09-17
GetValueFromCacheAsync重命令为GetCacheValueAsync,并删除参数isRefresh- 新增重载方法
Task<int> GetCacheValueAsync(string key, Func<Task<int>> getNewValue, TimeSpan? slidingExpiration, TimeSpan? absoluteExpirationRelativeToNow)
3.7.1
2022-09-16
GetPageCountFromCacheAsync重命名为GetValueFromCacheAsync- 修改
GetValueFromCacheAsync的委托参数的返回类型,以支持异步方法
3.7.0
2022-09-16
- 添加内部工具类
Utils - 添加分页缓存方法:
GetPageCountFromCacheAsync
3.6.0
2022-06-05
- 将所有DAL中业务相关逻辑转移到BLL,并将部分强制更新字段改为可选更新
2022-05-18
- 修改所有InitDb相关方法
- BLL层添加SetReadDb,SetWriteDb方法
2022-04-01
- 更新对Crping.EFCore.Common的引用,并对齐版本号
2021-07-15:
- IWebApiBLL接口与基础实现类添加Read,Write属性,以实现读写分离链接调用
- IBaseDAL接口与与基础实现类添加Read,Write属性,以实现读写分离链接调用
- IBaseDAL接口与与基础实现类添加ReadOnly方法,以实现以参数的方式读写分离链接调用
- IWebApiBLL接口与基础实现类去除所有readOnly参数,改为链式调用
历史记录
- 3.6.0:将所有DAL中业务相关逻辑转移到BLL,并将部分强制更新字段改为可选更新
- 3.5.1:更新引用组件版本
- 3.5.0:修改所有InitDb相关方法,BLL层添加SetReadDb,SetWriteDb方法
- 3.4.0:添加IHostBLL,删除IRelatedBLL,并修改相关方法
- 3.3.0:更新对Crping.EFCore.Common的引用,并对齐版本号
- 3.1.0:给所有方法添加判空逻辑,DbContextFactory类改为MyDbContextFactory
- 3.0.0:目标框架改为.net6.0,并引用EFCore6.0
- 2.4.0:新增方法:ShareCurrentUserIdFunc,重命名方法:ShareDbContext
- 2.3.0:新增方法 GetRawKvpsAsync,并升级EFCore到5.0.10
- 2.2.0:添加带条件的 Include 扩展方法
- 2.1.0:修改默认排序BUG,新增IDisposable, IAsyncDisposable
- 2.0.2:获取列表相关方法添加默认以ID为排序方式的升降序配置
- 2.0.1:CreateDbContext ReadOnly时添加Lock,以防多线程处理时数组越界
- 2.0.0:使用自定义DbContextFactory,延迟创建DbContext等(重大改版!!!)
- 1.5.3:GetKvpsAsync添加参数,修改默认排序
- 1.5.2:修复共享DbContext时的Bug
- 1.5.1:添加DbContext共享
- 1.5.0:重要!!,添加并实现IRelatedBLL接口,拆分DAL,BLL为CRUD等接口
- 1.4.1:去除GetCurrentUserID未赋值时的默认值0,暴露BUG
- 1.4.0:增加并修改DAL、BLL层相关列表方法
- 1.3.1:简化Get类方法重载的参数
- 1.3.0:BLL中的列表方法添加一系列重载
- 1.2.0:修改DAL,BLL的实现基类名称
- 1.1.0:添加Read、Write属性以链式调用方式取代传参方式。
- 1.0.0:包含数据库常规操作的DAL基类,WebApi常规操作的BLL基类等
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Crping.EFCore.Common (>= 9.3.0)
- Microsoft.EntityFrameworkCore (>= 10.0.11)
- Microsoft.EntityFrameworkCore.Relational (>= 10.0.11)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.11)
- StackExchange.Redis (>= 3.1.13)
NuGet packages (7)
Showing the top 5 NuGet packages that depend on Crping.EFCore:
| Package | Downloads |
|---|---|
|
Crping.EFCore.Controller
基于 Crping.EFCore 的 WebApi 控制器基类 |
|
|
Crping.Auth.BLL
Crping.Auth 的业务逻辑层 |
|
|
Crping.Auth.MySql.DAL
Crping.Auth.DAL 的 MySql 实现 |
|
|
Crping.AuthPolicy.BLL
Package Description |
|
|
Crping.Auth.Domain
Package Description |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 9.4.0 | 0 | 8/18/2026 |
| 9.3.0 | 35 | 8/18/2026 |
| 9.2.0 | 37 | 8/18/2026 |
| 9.1.2 | 73 | 8/16/2026 |
| 9.1.1 | 63 | 8/16/2026 |
| 9.1.0 | 61 | 8/15/2026 |
| 9.0.0 | 64 | 8/14/2026 |
| 8.10.0 | 68 | 8/13/2026 |
| 8.8.0 | 101 | 8/11/2026 |
| 8.7.0 | 95 | 8/11/2026 |
| 8.6.0 | 97 | 8/10/2026 |
| 8.5.0 | 98 | 8/9/2026 |
| 8.4.0 | 102 | 8/8/2026 |
| 8.3.0 | 103 | 8/8/2026 |
| 8.0.0 | 107 | 8/7/2026 |
| 7.5.0 | 92 | 8/6/2026 |
| 7.4.0 | 113 | 8/3/2026 |
| 7.3.0 | 118 | 7/30/2026 |
| 7.2.0 | 124 | 7/28/2026 |
| 7.1.0 | 119 | 7/28/2026 |
9.3.0 · 2026-08-18
【修复】GetOrCreateAsync / TryCreateAsync 系列方法在调用方已开启事务时嵌套 BeginTransaction 导致 InvalidOperationException 的问题:检测 CurrentTransaction,已有事务时复用而非嵌套