突然报 “关键字 WITH 附近有语法错误“?一篇避坑指南

突然报 “关键字 WITH 附近有语法错误“?一篇避坑指南
大家好我是码农刚子最近遇到一个EF Core 查询的问题跟大家分享下。升级到 .NET 8 / EF Core 8 后原来跑得好好的Where(x ids.Contains(x.id))突然炸了日志里赫然写着关键字 WITH 附近有语法错误。如果此语句是公用表表达式那么前一个语句必须以分号结尾。——这篇文章帮你搞清楚为什么、怎么修、以后怎么写才不踩坑。1. 先看症状假设你有这样一段再普通不过的代码var ids new Listint { 1, 2, 3, 5, 8 }; var users await _db.sys_admin .Where(x ids.Contains(x.id)) .ToListAsync();在 EF Core 6/7 上完全正常。升级到 EF Core 8 后同样的代码报Microsoft.Data.SqlClient.SqlException (0x80131904): 关键字 WITH 附近有语法错误。 如果此语句是公用表表达式、xmlnamespaces 子句或者更改跟踪上下文子句 那么前一个语句必须以分号结尾。关键词WITH、分号、公用表表达式CTE。SQL Server 错误号为 156。2. 根因EF Core 8 对 Contains 的翻译方式变了这不是 Bug这是 EF Core 8 的一个有意为之的 Breaking Change官方文档明确定义为 High Impact。2.1 旧行为EF Core 6/7EF 把参数化列表的值内联为 SQL 常量-- EF Core 7 生成的 SQL SELECT [s].[id], [s].[username], ... FROM [sys_admin] AS [s] WHERE [s].[id] IN (1, 2, 3, 5, 8)简单直接没有 CTE没有任何问题——直到你开始关注查询计划缓存。2.2 新行为EF Core 8EF Core 8 不再内联常量而是通过OPENJSON或CTE公用表表达式来传递参数化集合。简化后的生成逻辑是简单值列表string/int 常量→ OPENJSON 方式 复杂查询 / 多次 Contains → CTEWITH ... AS方式对于ids.Contains(x.id)这类场景EF 可能生成类似这样的 SQL-- EF Core 8 可能生成的 SQL简化版 ;WITH [t] AS ( SELECT [v].[value] FROM OPENJSON(__ids_0) ... ) SELECT [s].[id], ... FROM [sys_admin] AS [s] WHERE [s].[id] IN (SELECT [t].[value] FROM [t])问题来了WITH前面必须有一个完整语句的分号;。如果当前 SQL 批处理中 EF 没有在前面补上分号SQL Server 就会报错 156。2.3 官方文档怎么说微软在 EF Core 8 Breaking Changes 中明确记录了这条Tracking Issue #13617Containsin LINQ queries may stop working on older SQL Server versionsImpact: High链接learn.microsoft.com - Breaking changes in EF Core 8.03. 什么时候会触发不是所有Contains都会炸但它会在你不经意间冒出来。触发条件包括但不限于场景风险ids.Contains(x.id)且ids是Listint 高ids.Contains(x.id)且ids是Listint? 高我们项目遇到的stringList.Contains(x.name) 中可能走 OPENJSON同一查询中有多个Contains 高.Contains()嵌套在复杂Where表达式中 中查询中同时有其他关联Join / Include 高最重要的信号一旦看到错误信息里出现WITH和分号99% 就是这个问题。4. 解决方案5 种从优到差方案一参数化 Raw SQL推荐 ⭐直接绕过 EF 翻译用FromSqlRawSqlParameter性能最优零坑var paramNames ids.Select((_, i) $p{i}).ToArray(); var parameters ids.Select((id, i) new Microsoft.Data.SqlClient.SqlParameter($p{i}, id)).ToArray(); var sql $SELECT * FROM sys_admin WHERE id IN ({string.Join(,, paramNames)}); var users await _db.sys_admin .FromSqlRaw(sql, parameters) .ToListAsync();✅ 生成的 SQL 就是简单的WHERE id IN (p0, p1, ...)✅Microsoft.Data.SqlClient随 EF Core SQL Server 包引入无需另装✅ 完全防注入⚠️ 需要知道表名但你的 DbContext 本来就定义了适用批量删除、批量更新、批量查询等「已知 ID 列表查实体」场景。方案二FindAsync 逐个查询小数据量 ⭐如果 ID 列表很短比如页面批量操作选 10-20 条直接用主键查var users new Listsys_admin(); foreach (var id in ids) { var user await _db.sys_admin.FindAsync(id); if (user ! null) users.Add(user); }✅FindAsync走主键索引直查不生成 CTE