魅力程序猿

  • 首页
  • Java
  • Android
  • APP
    • 扑克计分器
    • Video Wallpaper
  • 联系我
  • 关于我
  • 资助
道子
向阳而生
  1. 首页
  2. AI技术
  3. 正文

EF Core 8 + SQL Server:Contains() 突然报 "关键字 WITH 附近有语法错误"?一篇避坑指南

2026年6月9日 15点热度 0人点赞 0条评论

📰 来源: 博客园


升级到 .NET 8 / EF Core 8 后,原来跑得好好的 Where(x => ids.Contains(x.id)) 突然炸了,日志里赫然写着:关键字 'WITH' 附近有语法错误。如果此语句是公用表表达式,那么前一个语句必须以分号结尾。 ——这篇文章帮你搞清楚为什么、怎么修、以后怎么写才不踩坑。

假设你有这样一段再普通不过的代码:

var ids = new List<int> { 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/7)

EF 把参数化列表的值内联为 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 8)

EF Core 8 不再内联常量,而是通过 OPENJSON 或 CTE(公用表表达式) 来传递参数化集合。简化后的生成逻辑是:

简单值列表(string/int 常量)→ OPENJSON 方式
复杂查询 / 多次 Contains → CTE(WITH ... 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 #13617):

Contains in LINQ queries may stop working on older SQL Server versions

Impact: High

链接:learn.microsoft.com - Breaking changes in EF Core 8.0

3. 什么时候会触发?

不是所有 Contains 都会炸,但它会在你不经意间冒出来。触发条件包括但不限于:

最重要的信号:一旦看到错误信息里出现 WITH 和 分号,99% 就是这个问题。

4. 解决方案(5 种,从优到差)

方案一:参数化 Raw SQL(推荐 ⭐)

直接绕过 EF 翻译,用 FromSqlRaw + SqlParameter,性能最优,零坑:

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 List<sys_admin>();
    foreach (var id in ids)
    {
        var user = await _db.sys_admin.FindAsync(id);
        if (user != null) users.Add(user);
    }
    
  • ✅ FindAsync 走主键索引直查,不生成 CTE
  • ❌ N+1 查询,ID 数量多时性能差
  • 适用:后台管理的批量操作(用户勾选几条记录删除/启用等),ID 数量通常不超过几十个。

    方案三:全量拉到内存过滤(小表 ⭐)

    var all = await _db.sys_admin.ToListAsync();
    var users = all.Where(x =&


    🔗 原文链接: 点击阅读原文

    标签: AI 人工智能 技术博客
    最后更新:2026年6月9日

    daozi

    这个人很懒,什么都没留下

    点赞
    < 上一篇

    文章评论

    您需要 登录 之后才可以评论
    搜索
    联系方式

    QQ群:179730949
    QQ群:114559024
    欢迎您加入Android大家庭
    本人QQ:136049925

    赐我一丝安慰
    给我一点鼓励

    COPYRIGHT © 2023 魅力程序猿. ALL RIGHTS RESERVED.

    Theme Kratos Made By Seaton Jiang

    豫ICP备15000477号