v10 → v11 迁移指南
本指南帮助您将 MiCake 应用从 v10 升级到 v11。v11 将 Unit of Work 确立为唯一持久化所有者,并改为自动安装 EF Core 拦截器,涉及仓储 API、UoW API、ASP.NET Core 边界与 EF Core 集成四个层面的破坏性变更。
升级后请对照下列变更逐项迁移:
- 移除
IRepository.SaveChangesAsync - 移除
AddAndReturnAsync与saveNow参数 - 移除
ClearChangeTrackingAsync UpdateAsync语义:完整替换DeleteByIdAsync语义:跟踪式删除requiresNew改为回调执行- 移除
PersistenceStrategy/OptimizeForSingleWrite - 移除
Timeout IDbContextWrapper改为IUnitOfWorkResource- 新增
FlushAsync、保存点、事件钩子 UnitOfWorkAttribute精简IsUowEnabled改为[DisableUnitOfWork]- 只读 action 推断改为 opt-in
- 上下文工厂接口合并
BypassUnitOfWorkCheck更名- 拦截器自动安装(
UseMiCakeInterceptors移除) - 行为变更
若要快速完成迁移,可直接跳转到迁移清单。
1. 仓储 API
Section titled “1. 仓储 API”1.1 移除 SaveChangesAsync
Section titled “1.1 移除 SaveChangesAsync”仓储方法不再自行保存或提交。变更仅修改 UoW 的跟踪状态,持久化由 UoW 统一拥有。
将每个 repo.SaveChangesAsync() 替换为环境 UoW 上的 CommitAsync():
// 迁移前await _bookRepository.AddAsync(book);await _bookRepository.SaveChangesAsync(); // 竞争性持久化边界
// 迁移后await _bookRepository.AddAsync(book);await _unitOfWork.CommitAsync(); // 在 UoW 边界统一提交1.2 移除 AddAndReturnAsync 与 saveNow
Section titled “1.2 移除 AddAndReturnAsync 与 saveNow”原来使用 AddAndReturnAsync 的地方,建议迁移到新加入接口的
AddAndGetIdAsync(...)——它保持了「添加 + 立即获取数据库生成键」的原有语义:
添加聚合根后自动调用 FlushAsync 填充生成键并返回 TKey。flush 发生在环境可写
UoW 事务内,不提交,仅在 UoW 提交后持久化;需要环境可写 UoW,否则抛出
InvalidOperationException:
// 迁移前var book = await _bookRepository.AddAndReturnAsync(new Book { Title = "x" });
// 迁移后(推荐):语义等价,返回生成的键var id = await _bookRepository.AddAndGetIdAsync(new Book { Title = "x" }); // 已加入 IRepository 接口await _unitOfWork.CommitAsync(); // 提交后数据持久化接口注入场景亦可使用 AddAsync + IUnitOfWork.FlushAsync() 组合,行为等价:
var book = new Book { Title = "x" };await _bookRepository.AddAsync(book);await _unitOfWork.FlushAsync(); // 生成键,book.Id 已填充,事务未提交// ...后续操作,最后统一 CommitAsync()AddAsync(..., saveNow: true) 中的 saveNow 参数同样移除,按上述方式处理。
1.3 移除 ClearChangeTrackingAsync
Section titled “1.3 移除 ClearChangeTrackingAsync”不再提供。清理变更跟踪属于 EF Core 关注点,需要时通过 DbContext 直接分离条目。
1.4 UpdateAsync 语义变更
Section titled “1.4 UpdateAsync 语义变更”未跟踪实例的 UpdateAsync 现在是完整分离聚合替换:写入实例的全部属性。
配置的并发令牌被保留,因此过期实例会以 DbUpdateConcurrencyException 暴露冲突,
而不再静默覆盖:
// 迁移前:部分更新,静默覆盖并发变更await _repo.UpdateAsync(detachedBook);
// 迁移后:完整替换;并发冲突在 flush/commit 时抛出 DbUpdateConcurrencyExceptionawait _repo.UpdateAsync(detachedBook);await _unitOfWork.CommitAsync();注意:load-and-modify 仍是首选工作流;并发冲突现在在
FlushAsync/CommitAsync时抛出,而不是在UpdateAsync调用时。
1.5 DeleteByIdAsync 语义变更
Section titled “1.5 DeleteByIdAsync 语义变更”现在先加载聚合到稳定的 UoW 上下文,再执行跟踪式删除——软删除、审计、
领域事件、回滚语义与 DeleteAsync 完全一致:
await _repo.DeleteByIdAsync(id); // 语义:load → tracked delete → UoW 提交后生效await _unitOfWork.CommitAsync();需要绕过生命周期立即物理删除时,改用显式物理删除 API (见 5.8 物理/批量操作)。
2. Unit of Work API
Section titled “2. Unit of Work API”2.1 BeginAsync(requiresNew: true) 已移除
Section titled “2.1 BeginAsync(requiresNew: true) 已移除”requiresNew 布尔参数被隔离回调执行取代。ExecuteRequiresNewAsync 创建独立 DI 作用域,
回调必须从传入的 IServiceProvider 解析仓储/DbContext;提交、回滚、释放自动完成:
// 迁移前var uow = await _unitOfWorkManager.BeginAsync(requiresNew: true);var repo = _bookRepository; // 外层作用域服务——错误await repo.AddAsync(book);await uow.CommitAsync();
// 迁移后await _unitOfWorkManager.ExecuteRequiresNewAsync(async (sp, ct) =>{ var repo = sp.GetRequiredService<IRepository<Book, int>>(); // 从回调 provider 解析 await repo.AddAsync(book); // 成功自动提交,失败自动回滚,作用域自动释放});注意:捕获外层 scoped 服务会触发所有权校验失败;无外层 UoW 时调用会抛出
InvalidOperationException。后台作业等无环境 UoW 的场景改用IStandaloneUnitOfWorkExecutor。
2.2 移除 PersistenceStrategy
Section titled “2.2 移除 PersistenceStrategy”PersistenceStrategy / OptimizeForSingleWrite 已移除。每个可写 UoW 都使用显式事务:
// 迁移前await _uowManager.BeginAsync(new UnitOfWorkOptions { PersistenceStrategy = PersistenceStrategy.OptimizeForSingleWrite });
// 迁移后await _uowManager.BeginAsync(); // 默认即可:Lazy 激活显式事务// 需要提前激活事务时:UnitOfWorkOptions.Immediate原因:
OptimizeForSingleWrite允许 EF 隐式事务在 post-save 生命周期处理完成前提交, 可能造成“数据已持久化但返回错误”的矛盾结果。
2.3 移除 Timeout
Section titled “2.3 移除 Timeout”UnitOfWorkOptions.Timeout 已移除——它原本就没有运行时效果。超时改用
EF/provider 的命令、锁、事务超时配置。
2.4 IDbContextWrapper 已移除
Section titled “2.4 IDbContextWrapper 已移除”被 provider 无关的 IUnitOfWorkResource 取代。仅影响自定义持久化 provider 集成方;
普通应用无需迁移。
2.5 新增能力
Section titled “2.5 新增能力”IUnitOfWork 新增:
FlushAsync()— 按注册顺序激活并 flush 全部资源,返回受影响行数,不提交- 保存点 —
CreateSavepointAsync/RollbackToSavepointAsync/ReleaseSavepointAsync MarkAsCompletedAsync()— 只读边界跳过提交- 事务事件 —
OnCommitting/OnCommitted/OnRollingBack/OnRolledBack IAsyncDisposable
// 保存点示例:事务内部分回滚await _uow.CreateSavepointAsync("step1");// ...执行一批操作await _uow.RollbackToSavepointAsync("step1"); // 只撤销该点之后的变更3. ASP.NET Core 边界
Section titled “3. ASP.NET Core 边界”3.1 UnitOfWorkAttribute 精简
Section titled “3.1 UnitOfWorkAttribute 精简”InitializationMode、CreateOptions() 已移除。属性现在只有两个选项:
[UnitOfWork(IsReadOnly = true)] // 只读:写操作快速失败[UnitOfWork(IsolationLevel = IsolationLevel.Serializable)]3.2 IsUowEnabled 已移除
Section titled “3.2 IsUowEnabled 已移除”// 迁移前[UnitOfWork(IsUowEnabled = false)]
// 迁移后[DisableUnitOfWork]3.3 只读 action 名称推断改为 opt-in
Section titled “3.3 只读 action 名称推断改为 opt-in”以前 GET action 自动按名称推断为只读;现在默认关闭,显式元数据始终优先:
// Startup.cs —— 依赖旧推断行为时重新开启:services.Configure<MiCakeAspNetOptions>(o => o.EnableReadOnlyActionNameInference = true);4. EF Core 集成
Section titled “4. EF Core 集成”4.1 上下文工厂接口合并
Section titled “4.1 上下文工厂接口合并”非泛型 IEFCoreContextFactory、IEFCoreAnchoredContextFactory、无参
GetDbContextWrapper() 已合并为单一公共契约。自定义工厂实现只需实现两个方法:
public class MyFactory<TDbContext> : IEFCoreContextFactory<TDbContext> where TDbContext : DbContext{ public TDbContext GetDbContext() { /* ... */ } public EFCoreDbContextWrapper GetOrCreateWrapperFor(DbContext context) { /* ... */ }}框架会通过适配器自动调用你的实现,无需了解内部视图。
4.2 BypassUnitOfWorkCheck 更名
Section titled “4.2 BypassUnitOfWorkCheck 更名”MiCakeEFCoreOptions.BypassUnitOfWorkCheck 更名为 AllowDbContextAccessWithoutUoW,
默认仍为 false:
// 迁移前options.BypassUnitOfWorkCheck = true;
// 迁移后options.AllowDbContextAccessWithoutUoW = true;注意:该选项现在仅放宽上下文解析(返回无 UoW 集成的 standalone wrapper, 用于只读 filter/middleware)。无 UoW 的写入整体按 Permissive 策略放行, 不再需要、也不应依赖此选项放行写入。
4.3 拦截器自动安装
Section titled “4.3 拦截器自动安装”UseMiCakeInterceptors 全部 3 个重载、IMiCakeInterceptorFactory 及其实现已移除。
拦截器由模块的 ConfigureDbContext 配置器自动挂载——只需在容器中注册 DbContext:
// 迁移前services.AddDbContext<AppDbContext>((sp, opt) =>{ opt.UseSqlite(connectionString); opt.UseMiCakeInterceptors(sp); // 已移除});
// 迁移后 —— 无需任何拦截器相关调用:services.AddDbContext<AppDbContext>(opt =>{ opt.UseSqlite(connectionString); opt.UseMiCake(); // 可选:安装 per-context options extension});要求:EF Core 9+(
ConfigureDbContext/IDbContextOptionsConfiguration<TContext>机制,EF Core 10 内置,已在 EF Core 10 上验证)。MiCakeDbContext子类无需调用UseMiCake()——OnConfiguring会自动调用。
5. 行为变更
Section titled “5. 行为变更”以下变更不涉及 API 删除,但会改变运行时行为,迁移后必须验证。
5.1 无 UoW 写入:Permissive 放行
Section titled “5.1 无 UoW 写入:Permissive 放行”以前会拒绝无 UoW 的直接 DbContext 写入;现在按原生 EF Core 语义放行(隐式事务,
无回滚/生命周期保证)。仓储/UoW 路径始终受守卫:
// 无环境 UoW 时:await dbContext.SaveChangesAsync(); // 放行,等同原生 EF(无 MiCake 保证)需要事务保证时,开启 UoW 或使用 IStandaloneUnitOfWorkExecutor。
5.2 嵌套 UoW:共享提交 / 根回滚
Section titled “5.2 嵌套 UoW:共享提交 / 根回滚”- 嵌套
CommitAsync只标记完成,物理提交发生在根 UoW - 嵌套
RollbackAsync标记根 UoW rollback-only,根回滚覆盖全部资源
5.3 多资源提交:best-effort + 结构化结果
Section titled “5.3 多资源提交:best-effort + 结构化结果”多资源提交按注册顺序确定性执行;部分失败抛出 PartialUnitOfWorkCommitException,
携带逐资源的结构化、非敏感结果(ID、类型、状态)。跨资源原子性需自行选择 outbox
或补偿工作流。
5.4 回滚/清理失败不再丢失
Section titled “5.4 回滚/清理失败不再丢失”回滚或清理失败时抛出 UnitOfWorkBoundaryException,同时携带主异常 + 回滚失败 +
清理失败,便于诊断。
5.5 SaveChanges 重入:有界状态机
Section titled “5.5 SaveChanges 重入:有界状态机”生命周期/领域事件处理器中递归调用 SaveChangesAsync 不再是原始递归:
嵌套保存被合并为同一事务内的保存周期,事件按实例去重。无进展或达到
MaxSaveCycles(默认 16)抛出 SaveChangesReentryException 并标记 UoW rollback-only:
// MiCakeEFCoreOptionsoptions.MaxSaveCycles = 32; // 按需调整上界5.6 分页:强制全序
Section titled “5.6 分页:强制全序”分页前必须产生全序:无调用方排序时按每个主键属性升序;有排序时缺失的主键属性
作为最后升序 ThenBy 追加;无键实体被拒绝。依赖旧隐式顺序的调用点请提供显式排序。
5.7 启动校验:DbContext 生命周期与执行策略
Section titled “5.7 启动校验:DbContext 生命周期与执行策略”- DbContext 必须注册为 scoped 或 pooled;singleton/transient 启动即失败并给出指引
RetriesOnFailure = true的执行策略对环境可写 UoW 被拒绝(需应用拥有的 可重放边界,如独立重试执行器)
5.8 物理/批量操作显式化
Section titled “5.8 物理/批量操作显式化”绕过聚合生命周期的删除改用显式 API,要求在环境可写 UoW 内,且保持在 UoW 事务中:
// 迁移前:仓储内直接物理删除// 迁移后:显式物理删除执行器var executor = sp.GetRequiredService<IEFCorePhysicalOperationExecutor<AppDbContext>>();await executor.ExecuteDeleteAsync<Book>(b => b.PublishedYear < 2000);直接使用 EF ExecuteUpdate / ExecuteDelete / ExecuteSqlRaw:UoW 内被守卫并绑定事务,
UoW 外按原生语义放行。
6. 迁移清单
Section titled “6. 迁移清单”第 1 步 — 仓储调用点
Section titled “第 1 步 — 仓储调用点”-
SaveChangesAsync()→IUnitOfWork.CommitAsync() -
AddAndReturnAsync(x)→AddAndGetIdAsync(x)(推荐,已加入IRepository接口);也可用AddAsync(x)+FlushAsync()(行为等价) - 移除
AddAsync的saveNow:参数 - 移除
ClearChangeTrackingAsync()调用 - 立即物理删除场景改用
IEFCorePhysicalOperationExecutor<TDbContext>
第 2 步 — UoW 用法
Section titled “第 2 步 — UoW 用法”-
BeginAsync(requiresNew: true)→ExecuteRequiresNewAsync(...),全部从回调 provider 解析服务 - 移除
PersistenceStrategy/OptimizeForSingleWrite/Timeout用法 - 后台作业/无环境 UoW 操作改用
IStandaloneUnitOfWorkExecutor.ExecuteAsync(...) - 确保创建的 UoW 被释放(支持异步释放;ASP.NET 边界自动处理)
第 3 步 — ASP.NET Core
Section titled “第 3 步 — ASP.NET Core”- 移除
[UnitOfWork]上的InitializationMode/CreateOptions()/IsUowEnabled -
[UnitOfWork(IsUowEnabled = false)]→[DisableUnitOfWork] - 依赖 GET 只读推断时设置
EnableReadOnlyActionNameInference = true
第 4 步 — EF Core 注册
Section titled “第 4 步 — EF Core 注册”- 移除所有
UseMiCakeInterceptors(...)调用与自定义IMiCakeInterceptorFactory - 确认 DbContext 注册为 scoped 或 pooled
-
BypassUnitOfWorkCheck→AllowDbContextAccessWithoutUoW(仅只读场景保留) - 不要注册自定义
IUnitOfWorkAmbientAccessor(框架 singleton) - 每种上下文类型仅保留一个
UseEFCore<TDbContext>()/AddUowCoreServices调用
第 5 步 — 行为验证
Section titled “第 5 步 — 行为验证”- 审查无 UoW 的直接
DbContext写入——现在会放行,需要保证时包进 UoW - 检查依赖隐式顺序的分页调用点,提供显式排序
- 检查
UpdateAsync调用点:并发冲突现在在 flush/commit 时抛出 - 检查递归调用
SaveChangesAsync的处理器:无进展循环抛SaveChangesReentryException
7. 新 API 速查
Section titled “7. 新 API 速查”| API | 用途 |
|---|---|
IUnitOfWork.FlushAsync() |
激活事务 + 按注册顺序 flush;返回受影响行数;不提交 |
IRepository<TAggregateRoot, TKey>.AddAndGetIdAsync(...) |
AddAndReturnAsync 的推荐迁移目标:添加 + FlushAsync 填充生成键并返回 TKey(需环境可写 UoW) |
IUnitOfWorkManager.ExecuteRequiresNewAsync(...) |
隔离内部边界(取代 requiresNew) |
IStandaloneUnitOfWorkExecutor |
无环境 UoW 的隔离作用域执行;成功提交、失败回滚 |
IUnitOfWorkResource |
provider 无关资源契约(取代 IDbContextWrapper) |
| 保存点三件套 | 事务内部分回滚;创建后注册的资源被拒绝 |
IEFCorePhysicalOperationExecutor<TDbContext> |
显式物理删除(绕过聚合生命周期) |
UnitOfWorkBoundaryException |
主异常 + 回滚/清理失败合并 |
PartialUnitOfWorkCommitException |
best-effort 多资源提交的结构化结果 |
SaveChangesReentryException |
无进展 / MaxSaveCycles 重入失败 |
UseMiCake()(options builder) |
仅安装 options 的 EF Core 集成入口 |
[DisableUnitOfWork] |
为 action/controller 退出 ASP.NET UoW 边界 |
MiCakeEFCoreOptions.MaxSaveCycles(默认 16) |
重入循环上界 |
MiCakeEFCoreOptions.AllowDbContextAccessWithoutUoW |
只读 filter/middleware 放宽上下文解析 |
8. FAQ
Section titled “8. FAQ”问:安装拦截器还需要调用什么吗?
不需要。在容器中注册 DbContext 即可——模块的 ConfigureDbContext 配置器自动挂载
拦截器与 options。要求 EF Core 9+。
问:UoW 外直接 SaveChangesAsync 以前会失败,现在呢?
按原生 EF Core 语义放行(Permissive 策略),无 MiCake 事务/回滚/生命周期保证。
需要保证时开启 UoW 或使用 IStandaloneUnitOfWorkExecutor。
问:Timeout 没了,超时怎么设?
使用 EF/provider 的命令、锁、事务超时配置。
问:为什么移除 OptimizeForSingleWrite?
它允许隐式事务在 post-save 生命周期处理前提交,post-save 失败会在数据已持久化后
返回错误。现在每个可写 UoW 使用显式事务(Lazy 首写前激活,或 Immediate)。
问:UpdateAsync 现在抛 DbUpdateConcurrencyException?
分离替换保留并发令牌,过期实例暴露冲突而非静默覆盖。建议 load-and-modify。
问:requiresNew 回调捕获外层服务还能用吗?
不能——所有权校验拒绝外层作用域捕获的上下文/仓储。全部从回调 IServiceProvider 解析。
