我正在使用 Dapper ORM,并且正在查询
Posts
表。如何使用 Dapper 获取分页结果?有没有帮忙的人?
Dapper Query 可以返回
IQueryable
吗?
您没有指定数据库或版本。如果您足够幸运能够使用全新的 SQL Server 2012 并可以访问 MSDN,则可以使用闪亮的新
OFFSET
和 FETCH
关键字。以下查询将跳过 20 条记录并返回接下来的 5 条记录。
SELECT * FROM [Posts]
ORDER BY [InsertDate]
OFFSET 20 ROWS
FETCH NEXT 5 ROWS ONLY
查看 http://msdn.microsoft.com/en-us/library/ms188385(v=sql.110).aspx#Offset 了解更多信息。
此外,复制 Massive 的方式并为 IDbConnection 编写自己的扩展方法也很容易。这是 Massive 的代码。
var query = string.Format("SELECT {0} FROM (SELECT ROW_NUMBER() OVER (ORDER BY {2}) AS Row, {0} FROM {3} {4}) AS Paged ", columns, pageSize, orderBy, TableName, where);
1) Dapper 没有内置分页功能。但直接在查询中实现它并不太难。示例:
SELECT *
FROM ( SELECT ROW_NUMBER() OVER ( ORDER BY InsertDate) AS RowNum, *
FROM Posts
WHERE InsertDate >= '1900-01-01'
) AS result
WHERE RowNum >= 1 // *your pagination parameters
AND RowNum < 20 //*
ORDER BY RowNum
需要 SQL Server 2005+
2) Dapper 返回一个
IEnumerable<T>
。
这是使用 C# 和 Dapper 的完整工作版本。
/// <summary>
/// Gets All People
/// </summary>
/// <returns>List of People</returns>
public IEnumerable<Person> GetAllPeople(Pager pager)
{
var sql = (@" select * from [dbo].[Person]
order by [PeplNo]
OFFSET @Offset ROWS
FETCH NEXT @Next ROWS ONLY");
using (IDbConnection cn = Connection)
{
cn.Open();
var results = cn.Query<Person>(sql,pager);
return results;
}
}
public class Pager
{
public int Page { get; set; }
public int PageSize { get; set; }
public int Offset { get; set; }
public int Next { get; set; }
public Pager(int page, int pageSize = 10)
{
Page = page < 1 ? 1 : page;
PageSize = pageSize < 1 ? 10 : pageSize;
Next = PageSize;
Offset = (Page - 1) * Next;
}
}
public async Task<IEnumerable<Blog>> GetBlogs(int pageNo = 1, int pageSize = 10)
{
int skip = (pageNo - 1) * pageSize;
using (var db = _context.GetOpenConnection())
{
var query = string.Format(@"SELECT * FROM [blog] ORDER BY [Id] OFFSET {0} ROWS FETCH NEXT {1} ROWS ONLY", skip, pageSize);
var result = await db.QueryAsync<Blog>(query);
return result;
}
}
我创建了一个带有强类型参数的泛型方法来获得可重用的解决方案。 这依赖于 FETCH NEXT 和 OFFSET,这意味着您需要 SQL Server 2012 或更高版本。
/// <summary>
/// Fetches page with page number <paramref name="pageNumber"/> with a page size set to <paramref name="pageSize"/>.
/// Last page may contains 0 - <paramref name="pageSize"/> items. The page number <paramref name="pageNumber"/> is 0-based,
/// i.e starts with 0. The method relies on the 'FETCH NEXT' and 'OFFSET' methods
/// of the database engine provider.
/// Note: When sorting with <paramref name="sortAscending"/> set to false, you will at the first page get the last items.
/// The parameter <paramref name="orderByMember"/> specified which property member to sort the collection by. Use a lambda.
/// </summary>
/// <typeparam name="T">The type of ienumerable to return and strong type to return upon</typeparam>
/// <param name="connection">IDbConnection instance (e.g. SqlConnection)</param>
/// <param name="orderByMember">The property to order with</param>
/// <param name="sql">The select clause sql to use as basis for the complete paging</param>
/// <param name="pageNumber">The page index to fetch. 0-based (Starts with 0)</param>
/// <param name="pageSize">The page size. Must be a positive number</param>
/// <param name="sortAscending">Which direction to sort. True means ascending, false means descending</param>
/// <returns></returns>
public static IEnumerable<T> GetPage<T>(this IDbConnection connection, Expression<Func<T, object>> orderByMember,
string sql, int pageNumber, int pageSize, bool sortAscending = true)
{
if (string.IsNullOrEmpty(sql) || pageNumber < 0 || pageSize <= 0)
{
return null;
}
int skip = Math.Max(0, (pageNumber)) * pageSize;
if (!sql.Contains("order by", StringComparison.CurrentCultureIgnoreCase))
{
string orderByMemberName = GetMemberName(orderByMember);
sql += $" ORDER BY [{orderByMemberName}] {(sortAscending ? "ASC": " DESC")} OFFSET @Skip ROWS FETCH NEXT @Next ROWS ONLY";
return connection.ParameterizedQuery<T>(sql, new Dictionary<string, object> { { "@Skip", skip }, { "@Next", pageSize } });
}
else
{
sql += $" OFFSET @Skip ROWS FETCH NEXT @Next ROWS ONLY";
return connection.ParameterizedQuery<T>(sql, new Dictionary<string, object> { { "@Skip", skip }, { "@Next", pageSize } });
}
}
下面支持的方法,获取属性名称并运行参数化查询。
private static string GetMemberName<T>(Expression<Func<T, object>> expression)
{
switch (expression.Body)
{
case MemberExpression m:
return m.Member.Name;
case UnaryExpression u when u.Operand is MemberExpression m:
return m.Member.Name;
default:
throw new NotImplementedException(expression.GetType().ToString());
}
}
public static IEnumerable<T> ParameterizedQuery<T>(this IDbConnection connection, string sql,
Dictionary<string, object> parametersDictionary)
{
if (string.IsNullOrEmpty(sql))
{
return null;
}
string missingParameters = string.Empty;
foreach (var item in parametersDictionary)
{
if (!sql.Contains(item.Key))
{
missingParameters += $"Missing parameter: {item.Key}";
}
}
if (!string.IsNullOrEmpty(missingParameters))
{
throw new ArgumentException($"Parameterized query failed. {missingParameters}");
}
var parameters = new DynamicParameters(parametersDictionary);
return connection.Query<T>(sql, parameters);
}
使用 Northwind DB 的示例用法:
var sql = $"select * from products";
var productPage = connection.GetPage<Product>(m => m.ProductID, sql, 0, 5, sortAscending: true);
POCO 示例如下所示:
public class Product
{
public int ProductID { get; set; }
public string ProductName { get; set; }
public int? SupplierID { get; set; }
public int? CategoryID { get; set; }
public string QuantityPerUnit { get; set; }
public decimal? UnitPrice { get; set; }
public short? UnitsInStock { get; set; }
public short? UnitsOnOrder { get; set; }
public short? ReorderLevel { get; set; }
public bool? Discontinued { get; set; }
}
这种方法的更广泛的变体还可以构建一种方法来使用 DB Cursor 获取 IEnumerable 的 IEnumerable 并包装此处使用的逻辑,但我的方法是一个坚固的可预测类型安全解决方案的基本演示,而不是依赖更灵活的动态方法。缺点是,由于我们有通用参数,因此我们还必须为数据库类编写 POCO,但并非所有开发人员都喜欢花时间在这上面。
我创建了一个示例项目来演示 Dapper 自定义分页,支持排序、条件和过滤器:
https://github.com/jinweijie/Dapper.PagingSample
基本上,方法如下:
Tuple<IEnumerable<Log>, int> Find(LogSearchCriteria criteria
, int pageIndex
, int pageSize
, string[] asc
, string[] desc);
第一个返回值是项目列表。第二个返回值是总数。
希望有帮助。
谢谢。
如果你想在dapper中使用分页,你可以使用OFFSET和FETCH。 我使用存储过程。首先在 SQL 中输入这样的查询并创建您的过程:
CREATE PROCEDURE SpName
@OFFSET int
AS
BEGIN
SELECT *
FROM TableName
WHERE (if you have condition)
ORDER BY columnName
OFFSET @OFFSET ROWS
FETCH NEXT 25 (display 25 rows per page) ROWS ONLY
END;
然后您已经在代码中创建了用于使用 Dapper 和存储过程获取数据的方法:
public async Task<List<T>> getAllData<T>(string spName, DynamicParameters param)
{
try
{
using (var con = new SqlConnection(_connections.DefaultConnection))
{
var result = await con.QueryAsync<T>(spName, param, commandType: System.Data.CommandType.StoredProcedure);
return result.ToList();
}
}
catch (Exception ex)
{
//
}
}
然后最终您调用存储过程并在您想要使用的每个方法中设置参数(
@OFFSET
):
public async Task<List<YourModel>> methodName (int offset)
{
var param = new DynamicParameters();
param.Add("@OFFSET" , offset);
var data = await getAllData<yourModel>("spName", param);
var result = _mapper.Map<List<yourModel>>(data);
return result;
}
我发现这个解决方案对我有用。它还添加了排序。
public async Task<PagedCustomerResult> GetPagedCustomersAsync(string? sortBy = null, string? sortOrder = null, string pageNumber = 0, string pageSize = 0)
{
var param = new
{
offSet = (pageNumber -1) * pageSize,
pageSize
};
// First query to get total number of records
StringBuilder sb = new StringBuilder(@"SELECT COUNT(0) [Count] from Customers;");
// Second query to get the data
sb.AppendLine(@" SELECT * from Customers c");
if (sortBy != null && sortOrder != null)
{
sb.AppendLine($" ORDER BY {BuildOrderBySqlUsingIntepolation(sortBy, sortOrder)}");
}
if (pageNumber > 0 && pageSize > 0)
{
sb.AppendLine(@" OFFSET @OffSet ROWS FETCH NEXT @PageSize ROWS ONLY");
}
var multi = await sqlConnection.QueryMultipleAsync(sb.ToString(), param);
var totalRowCount = await multi.ReadSingleAsync<int>();
var customers = await multi.ReadAsync<Customer>();
return new PagedCustomerResult(customers, pageNumber, pageSize, totalRowCount);
}
public class PagedCustomerResult
{
private IEnumerable<Customer> _data;
private int _currentPage;
private int _pageSize;
private int _totalRecords;
private int _totalPages;
public PagedCustomerResult(IEnumerable<Customer> data, int page, int pageSize, int totalRecords)
{
_data = data;
_currentPage = page;
_pageSize = pageSize;
_totalRecords = totalRecords;
_totalPages = (int)Math.Ceiling(totalRecords / (double)pageSize);
}
public IEnumerable<Customer> Data => _data;
public int CurrentPage => _currentPage;
public int PageSize => _pageSize;
public int TotalRecords => _totalRecords;
public int TotalPages => _totalPages;
}
private static string BuildOrderBySqlUsingIntepolation(string sortOrderColumn, string sortOrderDirection)
{
string orderBy;
switch (sortOrderColumn)
{
case "name":
orderBy = "c.[Name]";
break;
default:
orderBy = "c.[CreatedDateTime]";
break;
}
if (!string.IsNullOrEmpty(sortOrderDirection))
{
var sortOrder = "asc";
if (sortOrderDirection == "desc")
{
sortOrder = "desc";
}
orderBy = $"{orderBy} {sortOrder}";
}
return orderBy;
}
如果您没有 Sql Server 2012 或者您有其他 DBMS,进行分页的一种方法是在 DBMS 和 Web 服务器或客户端之间拆分处理。 --- 建议仅针对较小的套装尺寸。 您可以使用 Sql Server 中的“TOP”关键字或 MySql 中的 LIMIT 或 Oracle 中的 ROWNUM 来获取数据集中的最高行数。您要获取的行数等于您要跳过的行数加上您要获取的行数:
top = skip + take;
例如,您想要跳过 100 行并获取接下来的 50 行:
top = 100 + 50
因此您的 SQL 语句将如下所示(SQL 服务器风格)
SELECT TOP 150 Name, Modified, content, Created
FROM Posts
WHERE Created >= '1900-01-01'
在客户端: 如果您使用 C# 等 .NET 语言并使用 Dapper,则可以使用 linq 跳过多行并获取多行,如下所示:
var posts = connection.Query<Post>(sqlStatement, dynamicParameters);
return posts?.ToList().Skip(skipValue).Take(takeValue);