XiHan.Framework.Persistence.InMemory
persistence.relational@1 的内存默认实现:每种实体一张有界的表,工作单元用覆盖层实现读己之写。只用于开发、原型与测试,没有显式 Provider 时被选中。
| 项 | 内容 |
|---|---|
| 包 ID | XiHan.Framework.Persistence.InMemory |
| 环层 | Ring 3(实现) |
| 能力 | 提供 persistence.relational@1(role: default);依赖 kernel.hosting@1 |
| 特性入口 | InMemoryPersistenceFeature |
| 配置节 | XiHan:Persistence:InMemory |
| 依赖 | XiHan.Framework.Persistence.Abstractions |
| AOT | 兼容 |
| 稳定性 | Preview |
概述
存储 InMemoryStore 是进程内单例,按实体类型各有一张 InMemoryTable<TEntity, TKey>,容量有上限。工作单元 InMemoryUnitOfWork 是作用域服务:仓储的新增、更新、删除先写进它的覆盖层,同一工作单元里的查找与查询把覆盖层叠在表的快照上,所以能读到自己的写入。表与覆盖层只存副本,读取只交出副本。提交时给涉及的表依次加锁,校验通过后应用本工作单元的变更,跨表原子;回滚只丢弃覆盖层。
创建时间、修改时间、租户、并发戳与软删除按 PersistenceConventions 执行,语义与 EF Core、SqlSugar Provider 一致,三者通过同一套一致性用例。特性同时注册 IIdGenerator<Guid>(GuidV7IdGenerator)、Transaction 阶段的 UnitOfWorkMiddleware 与持久化消息目录。
何时使用
- 本地开发、原型、演示,以及单元测试与集成测试(如
Testing.TestHost)。 - 需要零外部依赖地跑通整条管道时;
Profiles.Standard缺省装配它。
生产环境改用 Persistence.EntityFrameworkCore 或 Persistence.SqlSugar:再 UseFeature 一个显式 Provider,内存实现即被顶替,不装配、不启动。
安装与启用
dotnet add package XiHan.Framework.Persistence.InMemory --prerelease当前是预览版,安装要带 --prerelease;从源码或本地包源引用见快速上手。Profiles.Standard 已包含本包,UseStandardProfile() 即装配;单独使用时:
using XiHan.Framework.Persistence.Abstractions.Repositories;
using XiHan.Framework.Persistence.InMemory;
builder.Services.AddRepository<Order, Guid>();
builder.AddXiHan(x => x.UseFeature<InMemoryPersistenceFeature>());配置
| 键 | 类型 | 缺省 | 说明 |
|---|---|---|---|
XiHan:Persistence:InMemory:MaxEntitiesPerType | 正整数 | 100000 | 每种实体的容量上限(含已软删除的行);只接受纯数字,非法值启动时抛出 ArgumentException |
{
"XiHan": {
"Persistence": {
"InMemory": {
"MaxEntitiesPerType": 10000
}
}
}
}主要类型
| 类型 | 说明 |
|---|---|
InMemoryPersistenceFeature | 特性入口;常量 HealthName 为 persistence.inmemory.capacity |
InMemoryPersistenceOptions | 选项:MaxEntitiesPerType;FromConfiguration(configuration) |
InMemoryStore | 单例存储;Table<TEntity, TKey>() 取表,Clear() 清空全部表,同时是健康项贡献者 |
InMemoryTable<TEntity, TKey> | 一种实体的有界表:Find、Snapshot(交出副本)、Count、Capacity |
InMemoryUnitOfWork | 作用域工作单元,带覆盖层 |
InMemoryRepository<TEntity, TKey> | 仓储实现,由特性按登记注册 |
应用代码只通过 IRepository<TEntity, TKey> 与 IUnitOfWork 使用它们;InMemoryStore.Clear() 可在测试之间重置数据。
使用示例
测试里清空存储,保证用例之间互不影响:
using Microsoft.Extensions.DependencyInjection;
using XiHan.Framework.Persistence.InMemory;
// host 是测试启动的宿主;存储是单例,清空后所有作用域都看到空表
host.Services.GetRequiredService<InMemoryStore>().Clear();仓储的读写与其他 Provider 完全相同,见持久化。
行为与限制
- 容量:新增使某张表超过
MaxEntitiesPerType时,提交返回PERSISTENCE_UNAVAILABLE_002,该表的整批变更都不写入;覆盖已有行不占容量。 - 读写都复制:查找、
FindManyAsync、查询交出的是副本,新增、更新存的是调用方实例的副本。在读出的实例上的修改,不经UpdateAsync写回就不会落库,其他调用看不到,回滚后也不留痕迹。仓储写入的修改时间、并发戳与删除标记同样只进覆盖层。 - 副本是字段逐一的浅复制:标量与协议字段互不影响;集合、嵌套对象等引用类型字段(含聚合的领域事件缓冲)仍与表里的版本共享,不在隔离范围内,不要在读出的实体上原地改集合来期待它不影响别处。
- 提交时校验:覆盖层记下每个写过的标识首次读到的已提交状态。提交时按建表先后给涉及的表依次加锁,在全部锁内逐表校验,任一失败所有表都不改,工作单元记为回滚:
- 两个工作单元并发新增同一标识,后提交的返回
PERSISTENCE_CONFLICT_002。 - 更新或删除期间,别的工作单元已删掉那一行,或改过带并发戳的那一行,后提交的返回
PERSISTENCE_CONFLICT_001,不会把先提交的更新盖掉或把删掉的行写回来。 - 不带并发戳的实体只校验行还在。
- 两个工作单元并发新增同一标识,后提交的返回
- 更新沿用当前行的创建时间、租户与删除标记;软删除在当前行的副本上打标(调用方实例上的其他改动不写入),删除标记再同步回调用方实例。
- 没有数据库事务,
HasTransaction与关系型 Provider 同义(ADR-0109):可写工作单元里仓储第一次接受写入调用(单条,或集合非空的批量,不论结果)起为真,覆盖层从此可能有未提交、随回滚丢弃的变更;提交(含提交时校验失败)与回滚后为假,只读工作单元恒为假。 - 只读工作单元(只读操作或
UnitOfWorkOptions(IsReadOnly: true)):写入在进覆盖层之前就返回PERSISTENCE_UNEXPECTED_001(批量逐项都是它,空集合仍返回空列表),并把工作单元记为失败,提交回滚并返回同一错误。 - 进程退出数据即丢失;多个进程之间不共享。
- 健康项
persistence.inmemory.capacity:任一张表达到容量即 Degraded(PERSISTENCE_UNAVAILABLE_002,报告按实体名排序的第一个满表),否则 Ready。 - 命名连接(ADR-0084)不支持:内存实现没有连接。实体登记到命名连接在装配时抛出携带
PERSISTENCE_UNAVAILABLE_008的FrameworkException,UnitOfWorkOptions(Connection: …)在BeginAsync抛出同码异常,不静默落在唯一的存储上。 - 实体目录(ADR-0078):表名取实体类型名,列取有公开 setter 的非集合属性、列名即属性名;库结构读取不支持,返回
PERSISTENCE_UNAVAILABLE_008。 - 运行时数据源(ADR-0078)不支持:内存实现没有数据库。注册表
DataSourceRegistry照样登记,取用与测试连接返回PERSISTENCE_UNAVAILABLE_008。
| 错误码 | 何时出现 |
|---|---|
PERSISTENCE_CONFLICT_002 | 新增的标识已在表或覆盖层里(含已软删除、其他租户);提交时发现新增的标识已被别的工作单元提交 |
PERSISTENCE_CONFLICT_001 | 更新的游离实例并发戳与当前值不同;提交时发现更新或删除的行已被别的工作单元删除或改过 |
PERSISTENCE_NOTFOUND_001 | 更新或删除的实体不存在或不可见 |
PERSISTENCE_VALIDATION_001 | 多租户实体没有当前租户 |
PERSISTENCE_VALIDATION_002 | 工作单元状态不允许(抛异常) |
PERSISTENCE_UNEXPECTED_001 | 只读工作单元里新增、更新、删除;提交时返回写入阶段被拒的同一错误 |
PERSISTENCE_UNAVAILABLE_002 | 表已满(提交结果与健康项) |
PERSISTENCE_UNAVAILABLE_008 | 运行时数据源与库结构读取:内存实现不支持 |
相关
- 持久化
- 工作单元与事务
- 测试与一致性套件
- Persistence.Abstractions
- Profiles.Standard
- 规范:RFC-0005《领域协议与持久化》
