Oracle不支持EF Core默认的Skip/Take转OFFSET-FETCH,因其官方驱动默认回退为ROWNUM嵌套查询;需显式配置UseOracleSQLCompatibility("19c")且满足Oracle 12c+、OrderBy存在等条件才启用原生OFFSET/FETCH。
oracle 不支持 offset-fetch 语法——这是关键前提。sql server 2012+ 的 offset ... fetch next 是 t-sql 特有功能,oracle 直到 12c 才引入类似语法(offset ... rows fetch next ... rows only),但 ef core 默认不生成它,且 oracle 官方驱动(odp.net)对它的 linq 映射支持有限、不稳定。
为什么 EF Core + Oracle 不能直接用 Skip/Take 转 OFFSET-FETCH
EF Core 的 Skip() 和 Take() 方法在 SQL Server 提供程序下会翻译成 OFFSET/FETCH,但在 Oracle 提供程序(如 Oracle.EntityFrameworkCore 7.x/8.x)中,默认仍回退为嵌套 ROWNUM 子查询。即使你显式调用:
context.Products
.OrderBy(p => p.Id)
.Skip(20)
.Take(10)生成的 SQL 仍是类似:
SELECT * FROM (
SELECT a.*, ROWNUM rnum FROM (
SELECT * FROM PRODUCTS ORDER BY ID
) a WHERE ROWNUM <= 30
) WHERE rnum > 20这不是 OFFSET/FETCH,也不是标准 ANSI SQL 分页;它是 Oracle 兼容性写法,但有 ROWNUM 固有缺陷:必须两层嵌套、无法在无排序时生效、外层 WHERE rnum > N 无法利用索引下推。
Oracle 12c+ 真正启用 OFFSET-FETCH 需手动干预
ODP.NET 从 19c 开始支持原生 OFFSET/FETCH,但 EF Core 不自动启用。要让它生效,必须满足三个条件:
- 数据库版本 ≥ Oracle 12cR1(推荐 19c 或 21c)
- 使用
Oracle.EntityFrameworkCore≥ 7.21.0,并在DbContext.OnConfiguring中显式启用:
optionsBuilder.UseOracle(connectionString, o => o.UseOracleSQLCompatibility("19c"));- 查询必须包含
OrderBy(否则 Oracle 报错:ORA-00933)
此时 Skip(20).Take(10) 才可能生成:
SELECT * FROM PRODUCTS ORDER BY ID OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY
注意:UseOracleSQLCompatibility("19c") 是开关,不设则默认走传统 ROWNUM 路径;设为 "12c" 也可能触发,但兼容性不如 "19c" 稳定。
绕过 EF Core 映射,直写原生 Oracle 分页 SQL
当需要 100% 控制分页行为(比如带 DISTINCT、复杂 JOIN、或避免 ROWNUM 错误),最可靠方式是放弃 LINQ 翻译,改用原生 SQL:
- 用
FromSqlRaw或FromSqlInterpolated(推荐后者,防注入) - 确保参数化,不拼接用户输入
- 显式加
ORDER BY,字段建议有索引
示例:
var page = 3;
var size = 20;
var skip = (page - 1) * size;
<p>var products = context.Products
.FromSqlInterpolated($@"SELECT <em> FROM (
SELECT p.</em>, ROW_NUMBER() OVER (ORDER BY p.ID) AS rn
FROM PRODUCTS p
WHERE p.NAME LIKE { $"%{keyword}%" }
) WHERE rn BETWEEN {skip + 1} AND {skip + size}")
.ToList();这个写法不依赖 EF Core 的分页映射,也不要求 Oracle 版本支持 OFFSET/FETCH,且能精准控制排序和过滤逻辑。缺点是失去强类型 LINQ 检查,需自行维护 SQL 正确性。
别踩的坑:ORDER BY 字段没索引 or 多列排序不一致
Oracle 的 OFFSET/FETCH 性能极度依赖 ORDER BY 字段是否有合适索引。常见翻车点:
-
ORDER BY CREATED_TIME DESC, ID ASC—— 但只建了(CREATED_TIME)单列索引,导致全表扫描 - 前端传
sort=price,后端却按ORDER BY name排序,分页结果错乱 - 未校验
skip值上限,用户传skip=9999999导致查询超时或 OOM
建议:所有分页接口强制校验 skip < 100000(对应约 5000 页 @20 条/页),深度分页场景改用游标分页(WHERE id < last_seen_id)。
真正麻烦的不是语法怎么写,而是 Oracle 对分页语义的实现差异:它不保证 OFFSET/FETCH 在高并发更新下结果绝对稳定,ROWNUM 方案更不可靠。上线前务必用真实数据量 + 并发请求压测排序字段的索引有效性。


















