ページングサポートしたカスタム DataSourceView と ListView + DataPager

ページングサポートしたカスタム DataSource を作成するにあたっては、DataSoruceView の ExecuteSelect を実装することになる。この時点で SQL文 として Rank() 関数を使いながら、必要なデータのみを取り出す処理をするとページに必要なデータのみを SQL Server からもってこれて非常に嬉しい。

これと .NET Framework 3.5 から採用された ListView + DataPager を使うとページングサポートをしながら柔軟な画面作成が出来てよい。

最初に直面したのは DataPager の QueryStringField を利用して、URLに含まれた QueryString でページングをしようとした場合、DataPager 自体の描画は正しく行われるものの表示されるデータの中身にページ番号が反映しないというもの。ASP.NET Forums Problem with DataPager にある症状と似ているものと思われるが、DataPager におけるページ番号の取り出しタイミングが ListView への反映のタイミングよりも遅いようだ。これはForumsにあるように DataBind() をどこかしらで呼べばよい。(ただし、後述するように最終的には不要)

が、更に問題になったのは、QueryString で指定されたページ番号として最終ページよりも大きい値を指定した場合に、内部的に無限ループが発生し例外となるというもの。ただの例外であればまだしも、この StackOverflow は運用環境においてワーカープロセスを停止させてしまい、長時間のサービス停止となってしまった。

.NET Framework のソースコード内でループを起こしている箇所までは特定できなかったが、少なくとも 最初に実装した箇所である ExecuteSelect を何度も呼び出しているという現象は確認できた。このため最初のガードとしては ExecuteSelect の時点で return value である IEnumerable が空の場合は、トータルレコード数 arguments.TotalRowCount を(本来の値ではなく)0にして返すという処理を追加した。

protected override IEnumerable ExecuteSelect(DataSourceSelectArguments arguments)
{
    _startRowIndex = arguments.StartRowIndex;
    _maximumRows = arguments.MaximumRows;
    _ソート順.Clear();
    if (!String.IsNullOrEmpty(arguments.SortExpression)) _ソート順.Add(arguments.SortExpression);

    ExampleDataSet.ExampleDataTable dt = テーブルの取得();
    if (arguments.RetrieveTotalRowCount)
    {
        arguments.TotalRowCount = レコード数の取得();
        if (dt.Count == 0) arguments.TotalRowCount = 0;
    }
    return dt.DefaultView;
}

このままでは表示上はレコードなし となってしまうが、ユーザに送信すべき出力は「過大なページ数だったら最終ページにしてしまう」だと判断し、カスタム DataPager を作成した。

public class カスタムDataPagerQueryStringField専用 : DataPager
{
    protected override void OnTotalRowCountAvailable(object sender, PageEventArgs e)
    {
        int 全レコード数 = e.TotalRowCount;
        int ページあたりレコード数 = e.MaximumRows;

        string pageStr = Page.Request.QueryString[QueryStringField];
        int pageNum;
        if (Int32.TryParse(pageStr, out pageNum) && pageNum > 0)
        {
            int 開始レコード番号 = (pageNum - 1)*PageSize;
            if (開始レコード番号 >= 全レコード数) 開始レコード番号 = 全レコード数 - 1;
            SetPageProperties(開始レコード番号, ページあたりレコード数, false);
        }
    }
}

また、このカスタムDataPager を使う場合、SetPageProperties の明示的な呼び出しがあるおかげで、最初にやった DataBind() の追加呼び出しは不要になる。

(6/10 追記) OnTotalRowCountAvailable を実装したらなぜかページャが表示されなくなった。レコード数確定してからの処理はあきらめて OnInit に記述。

public class カスタムDataPagerQueryStringField専用 : DataPager
{
    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);
        int ページあたりレコード数 = PageSize;
        string pageStr = Page.Request.QueryString[QueryStringField];
        int pageNum;
        if (Int32.TryParse(pageStr, out pageNum) && pageNum > 0)
        {
            int 開始レコード番号 = (pageNum - 1) * PageSize;
            SetPageProperties(開始レコード番号, ページあたりレコード数, false);
        }
    }
}

そもそもは ListView に対して DataBind するタイミングの問題。ASP.NET の DataBind の実行タイミングの問題にはよく悩まされる。

広告

コメントを残す

以下に詳細を記入するか、アイコンをクリックしてログインしてください。

WordPress.com ロゴ

WordPress.com アカウントを使ってコメントしています。 ログアウト / 変更 )

Twitter 画像

Twitter アカウントを使ってコメントしています。 ログアウト / 変更 )

Facebook の写真

Facebook アカウントを使ってコメントしています。 ログアウト / 変更 )

Google+ フォト

Google+ アカウントを使ってコメントしています。 ログアウト / 変更 )

%s と連携中

%d人のブロガーが「いいね」をつけました。