如何在服务器端分页时使用数据分页器(DataPager)?

13

我正在尝试使用DataPager进行服务器端分页。 这是我的代码:

<asp:DataPager ID="pgrFooBars" PagedControlID="lvFooBars" 
    QueryStringField="page" runat="server" >
<Fields>
    <asp:NumericPagerField />
</Fields>
</asp:DataPager>

代码后端

protected void Page_Load(object sender, EventArgs e)
{
    ConfigureBlogPostListView();
}

private void ConfigureBlogPostListView()
{
    int pageNum;
    int.TryParse(Request.Params["page"], out pageNum);
    int pageSize = 20;

    PagedList<IFooBar> FooBars = FooService.GetPagedFooBars(
        new PagingSettings(pageNum, pageSize));

    ListViewPagedDataSource ds = new ListViewPagedDataSource();
    ds.AllowServerPaging = true;
    ds.DataSource = FooBars;
    ds.MaximumRows = pageSize;
    ds.StartRowIndex = pageNum;
    //TotalCount is the total number of records in the entire set, not just those loaded.
    ds.TotalRowCount = FooBars.TotalCount;

    lvFooBars.DataSource = ds;
    lvFooBars.DataBind();

    pgrFooBars.PageSize = pageSize;
    pgrFooBars.SetPageProperties(pageNum, FooBars.TotalCount, true);
}

PagedList来自RobConery有用的帖子http://blog.wekeroad.com/2007/12/10/aspnet-mvc-pagedlistt/

问题在于DataPager似乎使用ListView的Count属性来确定记录的总数,而在这种情况下是20。它需要知道有1,500条记录,而不是20条记录。DataPager有一个TotalRowCount属性,但这是只读的。

我从未见过带有服务器端分页的DataPager示例,但认为它可以进行服务器端分页,否则QueryStringField属性有什么用呢?

我知道你可以使用类似4GuysFromRolla在这里使用的方法 https://web.archive.org/web/20211020140032/https://www.4guysfromrolla.com/articles/031506-1.aspx 进行自定义分页解决方案,但我想先知道是否可以使用DataPager解决方案,然后再创建自定义解决方案。

更新 看得越多,我就越来越认为这是不可能的,并且不幸的是,datapager是一个只适用于小型网站的控件。如果控件构建正确,我想要做的应该很简单。我想能够说

dpFooBars.TotalRowCountComputed = false;
dpFooBars.TotalRowCount = AnyNumberThatISoChoose;

我一直在寻找一些方法来实现相同的功能,但似乎datapager的TotalRowCount是从它绑定的数据源中实际项目数计算得出的。对于微软创建ListViewPagedDataSource()类和DataPager,并将它们一起使用但无法正确协作,这对我来说非常奇怪,但事实就是如此。

更新2(恍然大悟?) 自从.Net 2.0以来,使用ObjectDataSource并自定义SelectCountMethod()已经可以进行服务器端分页了。我认为应该可以自定义ObjectDataSource以适应我的需要。嗯,我要去度周末了,所以我需要几天时间来看看这是否可行。请关注,真正的信徒们。


你的应用是WebForm应用还是Asp.Net MVC应用? - Sharique
3个回答

10

非常感谢你所做的事情——使用listview、datapager和linq数据源实现SQL服务器分页。正如你所说,TotalRowCount是只读的。还有一个可以用来设置总行数的SetPageProperties方法,但对我也没用。

然而,我通过以下方式绕过了这个问题。我创建了一个带有空项模板的虚拟隐藏listview。Pager将指向这个虚拟listview,而不是原始listview。然后我们需要将虚拟listview绑定到与原始数据源中相同数量的虚拟集合,以确保正确呈现pager。下面是标记语言。

<asp:DataPager ID="pdPagerBottom" runat="server" PagedControlID="lvDummy" PageSize="5" QueryStringField="page">
<Fields>
    <asp:NumericPagerField ButtonType="Link" RenderNonBreakingSpacesBetweenControls="true" NextPageText="Next" PreviousPageText="Previous" ButtonCount="40" />
</Fields>
</asp:DataPager>

<asp:ListView ID="lvDummy" runat="server" Visible="false">
<ItemTemplate></ItemTemplate>
</asp:ListView>

<asp:ListView ID="lvPropertyList" runat="server">...</asp:ListView>

和后台代码

        var datasourceQuery = context.Properties.OrderBy(x => x.PropertyID).Skip(pdPagerBottom.StartRowIndex).Take(pdPagerBottom.PageSize);

        this.lvPropertyList.DataSource = datasourceQuery;
        this.lvPropertyList.DataBind();

        this.lvDummy.DataSource = new List<int>(Enumerable.Range(0, context.Properties.Count()));
        this.lvDummy.DataBind();

测试了100k条记录。非常有效。


有趣的解决方法。我现在通过采用MVC路线来避免这个问题 :-) - John
这个答案太棒了...快速简单,可以让你获得数据分页器的所有好处。谢谢! - clamchoda

3
似乎使用DataPager实现服务器端分页的唯一方法是在标记中使用LinqDataSource(更新:这是不正确的,请参见下文),并设置ListView的DataSourceID(如ScottGu's sample - step 6),而不是通过ListView DataSource属性。但是,我发现有一个技巧可以使这更加可行,因此您可以通过代码后台定义LinqDataSource查询,而不是在标记中定义(请注意,文章说这将适用于任何DataSource控件,但我认为情况并非如此)。
对于您的情况,这可能没有任何帮助,因为我注意到您正在调用某种服务,该服务可能不会(也不应该)返回IQueryable结果,这是必要的才能使其工作。无论如何,如果您有兴趣,这里是代码...
aspx标记:
<asp:ListView ID="lvFooBars" DataSourceID="dsLinq" ....>
   ......
</asp:ListView>

<asp:DataPager ID="pgrFooBars" PagedControlID="lvFooBars" 
    QueryStringField="page" runat="server" >
<Fields>
    <asp:NumericPagerField />
</Fields>
</asp:DataPager>

<asp:LinqDataSource id="dsLinq" runat="server" OnSelecting="dsLinq_Selecting" />

代码后台:

protected void dsLinq_Selecting(object sender, LinqDataSourceSelectEventArgs e)
{
    //notice this method on FooService requires no paging variables
    e.Result = FooService.GetIQueryableFooBars(); 
}

注意,MSDN在关于QueryStringField属性方面指出:

如果您想让搜索引擎索引所有数据页面,则设置此属性很有用。这是因为该控件为每个数据页面生成不同的URL。

更新:实际上,您可以使用ObjectDataSource控件而不是LinqDataSource控件来使其工作-请参见本文和示例下载。虽然使用ObjectDataSource不像使用LinqDataSource控件那样简单,但它使用更少的 Linq 魔法 (而是使用大量映射到业务/数据层方法的魔法字符串) 并允许公开 IEnumerable 作为数据访问方法而不是 IQueryable。

然而,我从未真正成为嵌入任何类型的数据源控件到我的 UI 标记的粉丝(我认为这是必要的),因此除了你在第一个更新中建议的小应用程序外,我可能会避开 DataPager 控件。

John,我注意到你正在尝试将依赖于 ViewState 的标准 Asp.Net 服务器控件与为 Asp.Net Mvc 应用程序开发的 PagedList 类结合使用。虽然这可能有效,但可能有更简单的路线可选。


谢谢你的提示。微软对将数据访问层与应用程序层紧密耦合的痴迷仍在继续。 - John
1
谢谢,我会看一下。它似乎是我所需要的。我并没有真正考虑PagedList是那么多MVC的事情,只是更多Rob Conery的事情。在我找到PagedList之前,之前的人一直在使用像List <FooBar> GetFoobars(string id,out TotalRowCount)这样的方法。对我来说,PagedList只是以良好的面向对象的方式做某些事情而不是使用out,这是我从未感到舒服的东西。 - John
我认为更简单的方法就是一开始就使用MVC,但团队还没有准备好转换这个网站(我们正在其他地方开始使用)。我的个人感觉是,在除了最简单的网站之外,MVC比Web Forms要容易得多。 - John
是的,我同意,在我看来MVC是正确的选择 - 它将使代码库比Webforms更易于维护。 - Bermo

0

John,我在这里做了一个小的概念验证,以便您可以看到代码。我尽可能地将其简化,没有任何多余的东西。如果您需要缺少的某些属性,请告诉我,我会进行修改。

引起注意的事情:

  1. 当您不使用DataSource对象(xml、Sql、Access等)时,数据需要在分页器的OnPreRender事件上绑定。我非常确定这是您的问题,因为这是我遇到最大的麻烦之处。

  2. ListView和分页器必须启用Viewstate(默认情况下已启用)

后台代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;

public partial class _Default : System.Web.UI.Page 
{
    protected void Page_Load(object sender, EventArgs e)
    {
        if (!Page.IsPostBack)
        {
            this.BindListData();
        }
    }

    protected void dp_PreRender(object sender, EventArgs e)
    {
        this.BindListData();
    }

    protected void BindListData()
    {
        List<Nums> n = new List<Nums>();
        for (int i = 0; i <= 100; i++)
        {
            n.Add(new Nums { Num1 = i, Num2 = i * 3 });
        }

        this.lvMyGrid.DataSource = n;
        this.lvMyGrid.DataBind();
    }
}

public class Nums
{
    public int Num1 { get; set; }
    public int Num2 { get; set; }
}

ASPX(省略了所有其他无关紧要的内容):

<asp:DataPager OnPreRender="dp_PreRender" runat="server" ID="dpMyPager" QueryStringField="page" PagedControlID="lvMyGrid" PageSize="15">
    <Fields>
        <asp:NumericPagerField />
    </Fields>
</asp:DataPager>

<asp:ListView runat="server" ID="lvMyGrid" DataKeyNames="Num1">
    <LayoutTemplate>
        <asp:Literal runat="server" ID="itemPlaceholder" />
    </LayoutTemplate>
    <ItemTemplate>
        <div style="border: 1px solid red; padding: 3px; margin: 5px; float: left; clear: both;">
        <%# Eval("Num1") %> : <%# Eval("Num2") %>
        </div>
    </ItemTemplate>
</asp:ListView>

享受吧,如果你需要其他的东西,请告诉我。

迈克


不幸的是,这不是我所需要的。我需要进行服务器端分页,也就是说,虽然数据库中有1000条记录,但为了节省带宽,我只会下载15条记录。假设我的数据库中有10,000条记录。我不想从数据库中获取所有的10,000条记录,仅仅是为了让datapager能够正确地计数。 - John
好的,这就解释了为什么情况更复杂。你可能需要创建一个自定义集合,比如ICollection<FooBar> FooBarCollection,并且覆盖Count方法,以便它返回整个计数。我不确定这是否可行,但很可能你要做类似的事情来欺骗DataPager认为有比实际更多的数据存在。 - Jerzakie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接