Dapper 本身不内置分页功能,但结合 SQL 的分页语法(如
OFFSET-FETCH、
LIMIT-OFFSET或
ROW_NUMBER()),可以高效、安全地实现分页查询。关键在于写对 SQL、传对参数、避免全表扫描和性能陷阱。
用 OFFSET-FETCH(SQL Server 2012+ 推荐)
这是 SQL Server 最简洁、语义最清晰的分页方式,支持 ORDER BY 必选,天然防止歧义排序。
示例(获取第 3 页,每页 20 条):
SELECT Id, Name, Email FROM Users WHERE Status = @status ORDER BY Id OFFSET @skip ROWS FETCH NEXT @take ROWS ONLY
Dapper 调用:
var users = connection.Query<User>(@sql, new { status = "Active", skip = 40, take = 20 });注意:@skip = (page - 1) * pageSize,务必确保 ORDER BY 字段有索引(如
Id),否则性能会断崖式下降。
兼容 MySQL / PostgreSQL:用 LIMIT 和 OFFSET
MySQL 和 PostgreSQL 原生支持
LIMIT offset, size或
LIMIT size OFFSET offset,写法更直观。
PostgreSQL 示例:
SELECT id, name, email FROM users WHERE status = @status ORDER BY id LIMIT @take OFFSET @skip
MySQL 同理,Dapper 参数绑定完全一致。同样强调:ORDER BY + 索引是分页性能的生命线,无索引 ORDER BY 在大数据量下会导致临时表和文件排序。
需要总数?别用 COUNT(*) 全表扫,用 CTE 或子查询优化
常见需求是「查数据 + 查总条数」。直接写两个查询(一个带分页,一个
COUNT(*))看似简单,但大表 COUNT(*) 可能极慢。
推荐用 CTE 避免重复过滤逻辑:
WITH paged AS (
SELECT Id, Name, Email,
COUNT(*) OVER() AS TotalCount
FROM Users
WHERE Status = @status
)
SELECT Id, Name, Email, TotalCount
FROM paged
ORDER BY Id
OFFSET @skip ROWS
FETCH NEXT @take ROWS ONLY这样一次查询返回数据 + 总数,且 WHERE 条件只写一遍,逻辑一致、易于维护。Dapper 映射时可定义包含
TotalCount的 DTO。
避免这些坑
不要在分页 SQL 里拼接字符串 —— 用参数化防止 SQL 注入,Dapper 默认支持 跳过页码过大时(如 OFFSET > 100w)性能骤降 —— 改用「游标分页(Keyset Pagination)」,基于上一页最后 ID 继续查:WHERE Id > @lastId ORDER BY Id LIMIT @take不加 ORDER BY 就分页,结果不可靠 —— SQL 标准不保证无序结果的稳定性,不同执行可能返回不同行 PageNumber 从 1 开始,但计算 skip 时别忘了减 1 ——
skip = (pageNumber - 1) * pageSize
基本上就这些。Dapper 分页不复杂,但容易忽略排序、索引和大数据量下的游标方案。把 SQL 写清楚,参数传准确,再配上合适索引,就能扛住大部分业务分页场景。
