EF Core 10 枚举集合报 InvalidCastException,先转换再物化筛选值
EF Core 10.0.5 将不同枚举组成的惰性集合展开为参数时,可能保留错误的运行时类型。明确成员转换并物化有限筛选集合,10.0.13已包含枚举转换分支。
EF Core 10.0.5 的 SQL Server 查询使用一组枚举作为筛选条件,枚举底层都是 int、数值也相同,却在创建数据库参数时抛出:
System.InvalidCastException: Invalid cast from 'ViewModel.MalwareStatus' to 'Entities.MalwareStatus'.
如果筛选集合来自 Cast<EntityEnum>(),先检查它是否仍是惰性的 IEnumerable<EntityEnum>。这条故障针对不同枚举类型组成的集合参数,不说明 EF Core 不能查询枚举,也不等同于数据库中存了非法枚举值。
集合的泛型类型没有消除运行时枚举身份
EF Core 10 的集合参数可以展开成多个 SQL 参数。处理惰性枚举集合时,装箱后的元素仍可能保留原枚举的运行时类型。参数映射期待实体枚举,却收到视图模型枚举,旧的值转换入口又尝试用 Convert.ChangeType() 转换目标枚举,于是抛出异常。
这类异常可以在 SQL 命令真正执行之前发生。更改 SQL Server 连接超时、重建索引或重试原查询,都不会替应用完成这次枚举转换。也不要仅凭两个枚举的成员名称一样,就认为它们是同一个 CLR 类型。
先把边界转换成明确的实体枚举数组
已知两套枚举采用同一套 int 编码时,可以在构建查询前完成显式值转换,再将有限大小的筛选集合物化。下面的 viewStatuses 是应用已经取得的筛选值,EntityStatus 是实体属性实际采用的枚举:
var filter = viewStatuses
.Select(value => (EntityStatus)(int)value)
.ToArray();
var query = db.Attachments
.Where(item => filter.Any(value => (value & item.MalwareStatus) != 0));
示例保留按位标志匹配的语义。EntityStatus 和 MalwareStatus 应替换为原模型的真实类型与属性;若项目原来使用普通相等匹配,就保留相等条件,不能顺手换成位运算。文件名、命名空间不同的两个枚举,也仍需明确转换。
这份写法以双方底层类型都为 int、且编码一一对应为前提。编码来自不同系统,或枚举定义正在分别演进时,应通过明确的成员映射转换,并处理不支持的值;不能强转后默认业务含义一致。Flags 枚举还要校验允许的组合,单看某个成员是否已定义并不够。
只在原来的 Cast<EntityStatus>() 后补 ToArray(),也可以改变这条惰性枚举路径。但显式转换把两种枚举之间的约定写得更清楚,便于避免以后把名称相似、编码不同的类型混在一起。物化针对有限筛选集合,不是把整张数据库表先读入内存;应保留原查询的服务器端筛选。
更新版本仍要核对实际值转换路径
正式 10.0.13 的值转换实现已经包含面向枚举的 Enum.ToObject() 分支,10.0.6 的对应实现仍使用旧转换。需要升级时,应选择包含修正、且与数据库提供程序兼容的正式 10.0 版本,让项目运行库和相关包保持一致,不能仅更新 .NET SDK 后认定 EF Core 已改变。
新实现还保留了 Microsoft.EntityFrameworkCore.Issue38008 兼容开关。若应用明确把它设为 true,会继续选择旧行为;核对现有启动配置即可,不要为了这次修复反过来打开它。
即使新版能接受这个参数组合,视图模型与实体枚举的业务映射仍由应用负责。修复不会让不相干的枚举自动变成同一套状态,也不要求修改数据库列或生成迁移。
验收应保留原参数展开方式,覆盖空集合、单值、多值及应用允许的标志组合,再核对查询结果。对于确实不受支持的外部状态,确认在进入查询前被明确拒绝或映射。只检查“不再抛 InvalidCastException”,可能漏掉位运算条件已经改变的问题。


