如何使用Dapper实现分页?

问题描述 投票:0回答:9

我正在使用 Dapper ORM,并且正在查询

Posts
表。
我想获得分页结果。

  1. 如何使用 Dapper 获取分页结果?有没有帮忙的人?

  2. Dapper Query 可以返回

    IQueryable
    吗?

c# pagination dapper iqueryable
9个回答
85
投票

您没有指定数据库或版本。如果您足够幸运能够使用全新的 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);

47
投票

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>


9
投票

这是使用 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;
    }
    
}

2
投票
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;
    }
}

2
投票

我创建了一个带有强类型参数的泛型方法来获得可重用的解决方案。 这依赖于 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,但并非所有开发人员都喜欢花时间在这上面。


1
投票

我创建了一个示例项目来演示 Dapper 自定义分页,支持排序、条件和过滤器:

https://github.com/jinweijie/Dapper.PagingSample

基本上,方法如下:

 Tuple<IEnumerable<Log>, int> Find(LogSearchCriteria criteria
        , int pageIndex
        , int pageSize
        , string[] asc
        , string[] desc);

第一个返回值是项目列表。第二个返回值是总数。

希望有帮助。

谢谢。


1
投票

如果你想在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;
}

1
投票

我发现这个解决方案对我有用。它还添加了排序。

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;
}


-15
投票

如果您没有 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);
© www.soinside.com 2019 - 2024. All rights reserved.