Dapper对Oracle多表分页必须用ROWNUM两层嵌套写法,禁用OFFSET FETCH;连接需关闭池化(Pooling=false),参数须用OracleParameter显式声明类型;SQL中字段需别名+Column特性映射,总数与数据必须拆为两个独立查询。

直接用 Dapper 做 Oracle 多表联合分页,不能套 SQL Server 的 OFFSET FETCH 写法,否则查不出数据或报 ORA-00933 错误。核心在于 Oracle 分页语法、连接驱动、参数绑定三者必须对齐。
Oracle 分页 SQL 必须用 ROWNUM 套壳,不能直接 OFFSET
Oracle 12c+ 虽支持 OFFSET ... FETCH,但 Dapper 在 Oracle.ManagedDataAccess.Core 下对命名参数(@page、@size)解析不稳定,容易触发 ORA-01036。稳妥做法是手写 ROWNUM 两层嵌套:
- 外层查
ROWNUM ,内层加ROWNUM >= :startRow - 所有排序字段必须在内层 SELECT 中显式写出,否则 ROWNUM 顺序错乱
- 避免用
*,多表 JOIN 时字段名冲突会导致映射失败
示例分页 SQL:
SELECT * FROM (
SELECT ROWNUM rnum, t.* FROM (
SELECT u.name AS user_name, o.order_no, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE u.status = :status
ORDER BY o.create_time DESC
) t WHERE ROWNUM :startRow
连接必须用 Oracle.ManagedDataAccess.Core,且禁用连接池
OracleConnection 默认开启连接池,而 Dapper 的 QueryAsync 在复用连接时可能残留上一次的绑定状态,导致分页参数被忽略或错位。必须显式关闭:
- 连接字符串中加
Pooling=false(如:"Data Source=...;User Id=...;Password=...;Pooling=false;") - 不要注册
IDbConnection为 Scoped/Transient 服务——每次查询都应 new 一个干净连接 - 用
OracleParameter显式声明参数类型,尤其NUMBER字段对应DbType.Int32,避免隐式转换失败
封装执行逻辑时,这样写才稳:
using var conn = new OracleConnection(connStr);
conn.Open();
var parameters = new OracleParameter[] {
new OracleParameter("status", DbType.String) { Value = queryDto.Status },
new OracleParameter("startRow", DbType.Int32) { Value = (queryDto.Page - 1) * queryDto.Size + 1 },
new OracleParameter("endRow", DbType.Int32) { Value = queryDto.Page * queryDto.Size }
};
var result = await conn.QueryAsync<orderwithuser>(sql, parameters);</orderwithuser>
Dapper 查询结果映射要处理字段别名和 NULL
Oracle 对大小写敏感,且默认返回大写列名(如 USER_NAME),而 C# 属性是 UserName。Dapper 默认不自动做下划线转驼峰,必须手动干预:
- SQL 中用明确别名(如
u.name AS user_name),再配合[Column("user_name")]特性标注实体属性 - 多表 LEFT JOIN 时,Oracle 可能返回
NULL字段,DTO 属性需设为可空(string?,int?),否则映射时报Invalid cast - 不用
QueryFirstOrDefault做分页总数查询——它只取一行,应单独写 COUNT(*) SQL 并用QuerySingleAsync
实体示例:
public class OrderWithUser
{
[Column("user_name")]
public string? UserName { get; set; }
<p>[Column("order_no")]
public string? OrderNo { get; set; }</p><p>[Column("amount")]
public decimal? Amount { get; set; }
}</p>
分页总数和数据要拆成两个独立查询
Oracle 不支持类似 MySQL 的 SQL_CALC_FOUND_ROWS,也没法像 SQL Server 那样用 CTE 同时出总数和分页数据。必须拆开:
- 先执行一条纯 COUNT(*) 查询(JOIN 条件、WHERE 条件完全一致),拿到
Total - 再执行带 ROWNUM 的主查询,拿到
Records - 两个查询共用同一组参数对象,但不要共用同一个
OracleParameter数组——参数值可能被第一个查询修改
最容易被忽略的是:COUNT 查询里不能出现 ORDER BY,也不能 SELECT 具体字段,否则 Oracle 报 ORA-00937;而主查询必须有 ORDER BY,否则 ROWNUM 无意义。











