EF Core 9调用List<int>的Contains()时生成无效SQL,提示'$'附近语法不正确
嘿,我来帮你梳理下这个问题——这不是EF Core的bug,而是EF Core 9引入的新查询优化策略,只是在特定环境下触发了兼容性问题,咱们一步步拆解:
为什么会生成OPENJSON的SQL?
EF Core 9针对值类型列表(比如List<int>、int[]这类整数/数值类型集合)的Contains查询,默认启用了一个性能优化:不再像旧版本那样把列表拆成IN (1,2,3)这种多参数SQL,而是将整个列表序列化为JSON,用OPENJSON来解析查询。这么做的好处是当列表元素很多时,能避免SQL Server因参数过多导致的解析性能下降。
但问题出在生成的OPENJSON(@__ids_0) WITH ([value] int '$')语法上:这个写法依赖SQL Server 2016及以上版本(或者Azure SQL Database),同时要求数据库的兼容性级别至少为130。如果你的数据库版本不够,或者兼容性级别没达标,就会抛出语法错误。
至于字符串列表的Contains没问题,是因为EF Core目前对字符串集合的Contains还沿用传统的IN子句逻辑,没有启用这个OPENJSON优化,所以不会触发这个问题。
解决办法
这里有几个可行的方案,你可以根据自己的环境选择:
1. 禁用OPENJSON优化,回退到传统IN子句
如果你暂时不想升级数据库,可以在DbContext的配置里关闭这个优化:
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { optionsBuilder.UseSqlServer( "你的数据库连接字符串", sqlOptions => sqlOptions.DisableOpenJsonForListContains()); }
这样EF Core就会回到旧的处理方式,生成IN (@p0, @p1, ...)的SQL,不会再用OPENJSON。
2. 升级数据库或调整兼容性级别
如果你的数据库是SQL Server 2016及以上版本,先检查兼容性级别:
执行以下SQL查询:
SELECT name, compatibility_level FROM sys.databases WHERE name = '你的数据库名称';
如果返回的compatibility_level低于130,执行下面的语句调整:
ALTER DATABASE 你的数据库名称 SET COMPATIBILITY_LEVEL = 130;
调整后,OPENJSON的语法就能正常被数据库识别了。
3. 临时 workaround:手动转换列表(不推荐,仅应急可用)
如果你不想改配置也不想动数据库,可以把List<int>转换成List<string>再用Contains?不过这需要你的实体Id字段也转成字符串比较,会影响性能,只适合临时应急:
var stringIds = ids.Select(id => id.ToString()).ToList(); return await _dbContext.Entities .Include(e => e.RelatedEntities) .Where(e => stringIds.Contains(e.Id.ToString())) .ToListAsync();
这个方法不推荐长期使用,毕竟会导致索引失效,性能下降。
总结
这个问题本质是EF Core 9的新优化和旧数据库环境的兼容性冲突,要么禁用优化,要么升级/调整数据库配置就能解决啦。
内容来源于stack exchange

