チュートリアル: ASP.NET MVC アプリケーションで Entity Framework を使用して並べ替え、フィルター処理、ページングを追加する

前の チュートリアルでは、エンティティの基本的な CRUD 操作用の一連の Web ページを Student 実装しました。 このチュートリアルでは、[ 学生 のインデックス] ページに並べ替え、フィルター処理、ページング機能を追加します。 また、単純なグループ化ページも作成します。

次の図は、完了したページの外観を示しています。 列見出しとは、ユーザーがクリックしてその列で並べ替えを行うことができるリンクです。 列見出しを繰り返しクリックすると、昇順と降順の並べ替え順序が切り替えられます。

Students_Index_page_with_paging

このチュートリアルでは、次の作業を行いました。

  • 列の並べ替えリンクを追加する
  • [検索] ボックスを追加する
  • ページングの追加
  • About ページを作成する

必須コンポーネント

[Student Index] ページに並べ替えを追加するには、コントローラーの メソッドをIndexStudent変更し、コードを [インデックス] ビューにStudent追加します。

Index メソッドに並べ替え機能を追加する

  • Controllers\StudentController.cs で、 メソッドをIndex次のコードに置き換えます。

    public ActionResult Index(string sortOrder)
    {
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
       var students = from s in db.Students
                      select s;
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:
             students = students.OrderBy(s => s.LastName);
             break;
       }
       return View(students.ToList());
    }
    

このコードは、URL 内の文字列から sortOrder パラメーターを受け取ります。 クエリ文字列の値は、ASP.NET MVC によってアクション メソッドのパラメーターとして提供されます。 パラメーターは、"Name" または "Date" のいずれかの文字列で、必要に応じてアンダースコアと文字列 "desc" を続けて降順を指定します。 既定の並べ替え順序は昇順です。

インデックス ページが初めて要求されたときには、クエリ文字列はありません。 学生は、 によって LastName昇順に表示されます。これは、 ステートメントのフォールスルー ケース switch によって確立される既定値です。 ユーザーが列見出しハイパーリンクをクリックすると、適切な sortOrder 値がクエリ文字列で提供されます。

ビューが適切なクエリ文字列値を使用して列見出しハイパーリンクを構成できるように、2 つの ViewBag 変数が使用されます。

ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";

これらは、三項ステートメントです。 最初のパラメーターは、パラメーターが null または空の場合 sortOrder は、 ViewBag.NameSortParm "name_desc" に設定する必要があることを指定します。それ以外の場合は、空の文字列に設定する必要があります。 これらの 2 つのステートメントを使用して、次のようにビューで列見出しのハイパーリンクの設定することができます。

既定の並べ替え順 姓のハイパーリンク 日付のハイパーリンク
姓の昇順 descending ascending
姓の降順 ascending ascending
日付の昇順 ascending descending
日付の降順 ascending ascending

メソッドは、LINQ to Entitiesを使用して並べ替えの基準となる列を指定します。 このコードでは、 ステートメントのIQueryable<T>前に変数をswitch作成し、 ステートメントで変数をswitch変更し、 ステートメントの後で メソッドをToListswitch呼び出します。 IQueryable 変数を作成および変更するときに、データベースに送信されるクエリはありません。 などのToListメソッドを呼び出してオブジェクトをIQueryableコレクションに変換するまで、クエリは実行されません。 したがって、このコードでは、 ステートメントまで実行されない 1 つのクエリが return View 作成されます。

並べ替え順序ごとに異なる LINQ ステートメントを記述する代わりに、LINQ ステートメントを動的に作成できます。 動的 LINQ の詳細については、「 動的 LINQ」を参照してください。

  1. Views\Student\Index.cshtml で、見出し行の <tr> 要素と <th> 要素を強調表示されたコードに置き換えます。

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm })
            </th>
            <th>First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
    

    このコードでは、 プロパティの情報を ViewBag 使用して、適切なクエリ文字列値を含むハイパーリンクを設定します。

  2. ページを実行し、[姓] 列と [登録日] 列の見出しをクリックして、並べ替えが機能することを確認します。

    [姓] 見出しをクリックすると、学生は姓の降順で表示されます。

Students インデックス ページにフィルター処理を追加するには、テキスト ボックスと送信ボタンをビューに追加し、 メソッドで対応する変更を Index 行います。 テキスト ボックスを使用すると、名フィールドと姓フィールドで検索する文字列を入力できます。

Index メソッドにフィルター機能を追加する

  • Controllers\StudentController.cs で、 メソッドをIndex次のコードに置き換えます (変更が強調表示されています)。

    public ViewResult Index(string sortOrder, string searchString)
    {
        ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
        ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
        var students = from s in db.Students
                       select s;
        if (!String.IsNullOrEmpty(searchString))
        {
            students = students.Where(s => s.LastName.Contains(searchString)
                                   || s.FirstMidName.Contains(searchString));
        }
        switch (sortOrder)
        {
            case "name_desc":
                students = students.OrderByDescending(s => s.LastName);
                break;
            case "Date":
                students = students.OrderBy(s => s.EnrollmentDate);
                break;
            case "date_desc":
                students = students.OrderByDescending(s => s.EnrollmentDate);
                break;
            default:
                students = students.OrderBy(s => s.LastName);
                break;
        }
    
        return View(students.ToList());
    }
    

このコードでは、 パラメーターを searchString メソッドに Index 追加します。 インデックス ビューに追加するテキスト ボックスから検索する文字列値を受け取ります。 また、 where LINQ ステートメントに句が追加され、名または姓に検索文字列が含まれる学生のみが選択されます。 句を追加する Where ステートメントは、検索する値がある場合にのみ実行されます。

Note

多くの場合、Entity Framework エンティティ セットで同じメソッドを呼び出すか、メモリ内コレクションの拡張メソッドとして呼び出すことができます。 結果は通常同じですが、場合によっては異なる場合があります。

たとえば、 メソッドの.NET Framework実装では、空のContains文字列を渡すとすべての行が返されますが、SQL Server Compact 4.0 の Entity Framework プロバイダーは空の文字列に対して 0 行を返します。 したがって、例のコード (ステートメント内に Where ステートメントをif配置) により、すべてのバージョンのSQL Serverに対して同じ結果が得られます。 また、 メソッドのContains.NET Framework実装では、既定では大文字と小文字が区別される比較が実行されますが、Entity Framework SQL Server プロバイダーでは既定で大文字と小文字を区別しない比較が実行されます。 したがって、 メソッドをToUpper呼び出してテストで明示的に大文字と小文字を区別しないようにすると、後でリポジトリを使用するようにコードを変更しても結果が変わるので、オブジェクトの代わりにコレクションがIQueryableIEnumerable返されます。 (IEnumerable コレクションに対して Contains メソッドを呼び出したときには、.NET Framework の実装を取得します。IQueryable オブジェクトに対して呼び出したときには、データベース プロバイダーの実装を取得します)。

Null 処理は、データベース プロバイダーによって異なる場合や、コレクションを使用する場合と比較してオブジェクトを使用IQueryableIEnumerableする場合にも異なる場合があります。 たとえば、一部の Where シナリオでは、 のような table.Column != 0 条件は、値として を持つ null 列を返さない可能性があります。 既定では、EF によって追加の SQL 演算子が生成され、データベース内で null 値間の等価性がメモリ内で動作するように動作しますが、EF6 で UseDatabaseNullSemantics フラグを設定することも、EF Core で UseRelationalNulls メソッドを 呼び出してこの動作を構成することもできます。

Student インデックス ビューに検索ボックスを追加する

  1. Views\Student\Index.cshtml で、キャプション、テキスト ボックス、および [検索] ボタンを作成するために、開始tableタグの直前に強調表示されたコードを追加します。

    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    
    @using (Html.BeginForm())
    {
        <p>
            Find by name: @Html.TextBox("SearchString")  
            <input type="submit" value="Search" /></p>
    }
    
    <table>
        <tr>
    
  2. ページを実行し、検索文字列を入力し、[ 検索 ] をクリックしてフィルター処理が機能していることを確認します。

    URL に "an" 検索文字列が含まれていないことに注意してください。つまり、このページをブックマークした場合、ブックマークを使用してもフィルター処理された一覧は取得されません。 これは、リスト全体を並べ替える列の並べ替えリンクにも適用されます。 チュートリアルの後半でフィルター条件にクエリ文字列を使用するように [ 検索 ] ボタンを変更します。

ページングの追加

Students インデックス ページにページングを追加するには、まず PagedList.Mvc NuGet パッケージをインストールします。 次に、 メソッドに追加の変更を Index 加え、ビューにページング リンクを Index 追加します。 PagedList.Mvc は、ASP.NET MVC 用の多くの優れたページングおよび並べ替えパッケージの 1 つであり、ここでの使用は、他のオプションに対する推奨事項ではなく、例としてのみ意図されています。

PagedList.MVC NuGet パッケージをインストールする

NuGet PagedList.Mvc パッケージは、 PagedList パッケージを依存関係として自動的にインストールします。 PagedList パッケージは、 および コレクションのPagedListコレクション型と拡張メソッドをIQueryableIEnumerableインストールします。 拡張メソッドは、 または IEnumerableからコレクション内PagedListに 1 ページのデータをIQueryable作成し、PagedListコレクションにはページングを容易にする複数のプロパティとメソッドが用意されています。 PagedList.Mvc パッケージは、ページング ボタンを表示するページング ヘルパーをインストールします。

  1. [ツール] メニューの [NuGet パッケージ マネージャー] を選択し、[パッケージ マネージャー コンソール] を選択します。

  2. [ パッケージ マネージャー コンソール ] ウィンドウで、 パッケージ ソースnuget.org で、 既定のプロジェクトContosoUniversity であることを確認し、次のコマンドを入力します。

    Install-Package PagedList.Mvc
    
  3. プロジェクトをビルドします。

Note

PageList パッケージは維持されなくなりました。 そのため、現在のプロジェクトでは 、X.PagedList パッケージを使用することをお勧めします。 メイン違いは、X.PagedList は移植可能なアセンブリであるということです。 つまり、パッケージはクロスプラットフォームであり、Web プロジェクトや他の .NET プロジェクトに使用できます。 新しいパッケージはバージョン 8.4 以降で .NET 6 に移植されているため、互換性の問題を引き起こさないでください。

Index メソッドにページング機能を追加する

  1. Controllers\StudentController.cs で、名前空間の using ステートメントをPagedList追加します。

    using PagedList;
    
  2. Index メソッドを次のコードに置き換えます。

    public ViewResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    {
       ViewBag.CurrentSort = sortOrder;
       ViewBag.NameSortParm = String.IsNullOrEmpty(sortOrder) ? "name_desc" : "";
       ViewBag.DateSortParm = sortOrder == "Date" ? "date_desc" : "Date";
    
       if (searchString != null)
       {
          page = 1;
       }
       else
       {
          searchString = currentFilter;
       }
    
       ViewBag.CurrentFilter = searchString;
    
       var students = from s in db.Students
                      select s;
       if (!String.IsNullOrEmpty(searchString))
       {
          students = students.Where(s => s.LastName.Contains(searchString)
                                 || s.FirstMidName.Contains(searchString));
       }
       switch (sortOrder)
       {
          case "name_desc":
             students = students.OrderByDescending(s => s.LastName);
             break;
          case "Date":
             students = students.OrderBy(s => s.EnrollmentDate);
             break;
          case "date_desc":
             students = students.OrderByDescending(s => s.EnrollmentDate);
             break;
          default:  // Name ascending 
             students = students.OrderBy(s => s.LastName);
             break;
       }
    
       int pageSize = 3;
       int pageNumber = (page ?? 1);
       return View(students.ToPagedList(pageNumber, pageSize));
    }
    

    このコードでは、 page パラメーター、現在の並べ替え順序パラメーター、および現在のフィルター パラメーターをメソッド シグネチャに追加します。

    public ActionResult Index(string sortOrder, string currentFilter, string searchString, int? page)
    

    ページが初めて表示されるとき、またはユーザーがページングまたは並べ替えリンクをクリックしていない場合、すべてのパラメーターは null になります。 ページング リンクがクリックされた場合、 page 変数には表示するページ番号が含まれます。

    ViewBagページング中に並べ替え順序を同じに保つために、ページング リンクに含める必要があるため、プロパティはビューに現在の並べ替え順序を提供します。

    ViewBag.CurrentSort = sortOrder;
    

    もう 1 つのプロパティ である は、 ViewBag.CurrentFilterビューに現在のフィルター文字列を提供します。 ページング中にフィルターの設定を維持するために、ページングのリンクにこの値を含める必要があり、ページが再表示されるときに、この値をテキスト ボックスに復元する必要があります。 ページングの中に検索文字列を変更した場合は、新しいフィルターのために別のデータが表示されるため、ページを 1 にリセットする必要があります。 テキスト ボックスに値が入力され、[送信] ボタンが押されると、検索文字列が変更されます。 その場合、 searchString パラメーターは null ではありません。

    if (searchString != null)
    {
        page = 1;
    }
    else
    {
        searchString = currentFilter;
    }
    

    メソッドの最後にある students オブジェクトの拡張メソッドは、 ToPagedList ページング IQueryable をサポートするコレクション型の学生の 1 ページに学生クエリを変換します。 その後、学生のその 1 ページがビューに渡されます。

    int pageSize = 3;
    int pageNumber = (page ?? 1);
    return View(students.ToPagedList(pageNumber, pageSize));
    

    ToPagedList メソッドは、ページ番号を受け取ります。 2 つの疑問符は 、null 合体演算子を表します。 null 合体演算子は null 許容型の既定値を定義します。式 (page ?? 1) は、値がある場合は page の値を返し、page が null の場合は 1 を返すことを意味します。

  1. Views\Student\Index.cshtml で、既存のコードを次のコードに置き換えます。 変更が強調表示されます。

    @model PagedList.IPagedList<ContosoUniversity.Models.Student>
    @using PagedList.Mvc;
    <link href="~/Content/PagedList.css" rel="stylesheet" type="text/css" />
    
    @{
        ViewBag.Title = "Students";
    }
    
    <h2>Students</h2>
    
    <p>
        @Html.ActionLink("Create New", "Create")
    </p>
    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
            <input type="submit" value="Search" />
        </p>
    }
    <table class="table">
        <tr>
            <th>
                @Html.ActionLink("Last Name", "Index", new { sortOrder = ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th>
                First Name
            </th>
            <th>
                @Html.ActionLink("Enrollment Date", "Index", new { sortOrder = ViewBag.DateSortParm, currentFilter=ViewBag.CurrentFilter })
            </th>
            <th></th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @Html.ActionLink("Edit", "Edit", new { id=item.ID }) |
                @Html.ActionLink("Details", "Details", new { id=item.ID }) |
                @Html.ActionLink("Delete", "Delete", new { id=item.ID })
            </td>
        </tr>
    }
    
    </table>
    <br />
    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    
    @Html.PagedListPager(Model, page => Url.Action("Index", 
        new { page, sortOrder = ViewBag.CurrentSort, currentFilter = ViewBag.CurrentFilter }))
    

    ページの上部にある @model ステートメントは、ビューが List オブジェクトの代わりに PagedList オブジェクトを取得するようになったことを指定します。

    PagedList.Mvc ステートメントをusing使用すると、ページング ボタンの MVC ヘルパーにアクセスできます。

    このコードでは、FormMethod.Get を指定できる BeginForm のオーバーロードを使用します。

    @using (Html.BeginForm("Index", "Student", FormMethod.Get))
    {
        <p>
            Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)  
            <input type="submit" value="Search" />
        </p>
    }
    

    既定の BeginForm は POST を使用してフォーム データを送信します。つまり、パラメーターはクエリ文字列として URL ではなく HTTP メッセージ本文で渡されます。 HTTP GET を指定すると、フォーム データがクエリ文字列として URL で渡され、ユーザーが URL をブックマークできるようになります。 HTTP GET の使用に関する W3C ガイドラインでは、アクションが更新されない場合は GET を使用することをお勧めします。

    テキスト ボックスは現在の検索文字列で初期化されるため、新しいページをクリックすると、現在の検索文字列が表示されます。

    Find by name: @Html.TextBox("SearchString", ViewBag.CurrentFilter as string)
    

    列ヘッダーへのリンクは、フィルターの結果内でユーザーが並べ替えられるように、クエリ文字列を使用してコントローラーに現在の検索文字列を渡します。

    @Html.ActionLink("Last Name", "Index", new { sortOrder=ViewBag.NameSortParm, currentFilter=ViewBag.CurrentFilter })
    

    現在のページと合計ページ数が表示されます。

    Page @(Model.PageCount < Model.PageNumber ? 0 : Model.PageNumber) of @Model.PageCount
    

    表示するページがない場合は、"ページ 0/0" が表示されます。 (その場合、ページ番号は 1 で 0 であるため Model.PageNumberModel.PageCount ページ数がページ数より大きくなります)。

    ページング ボタンは、ヘルパーによって PagedListPager 表示されます。

    @Html.PagedListPager( Model, page => Url.Action("Index", new { page }) )
    

    ヘルパーには PagedListPager 、URL やスタイル設定など、カスタマイズできるさまざまなオプションが用意されています。 詳細については、GitHub サイト の TroyGoode/PagedList を参照してください。

  2. ページを実行します。

    異なる並べ替え順でページングのリンクをクリックし、ページングが機能することを確認します。 その後で、検索文字列を入力して、ページングをもう一度試し、並べ替えとフィルター処理を使用してもページングが正しく機能することを確認します。

About ページを作成する

Contoso 大学の Web サイトの [About] ページに、登録日付ごとに登録した受講者の数が表示されます。 これには、グループ化とグループに関する簡単な計算が必要です。 これを実行するためには、次の手順を実行します。

  • ビューに渡す必要があるデータのビュー モデル クラスを作成します。
  • コントローラーの About メソッドを Home 変更します。
  • ビューを変更します About

ビュー モデルを作成する

プロジェクト フォルダーに ViewModels フォルダーを作成します。 そのフォルダーに、クラス ファイル EnrollmentDateGroup.cs を追加し、テンプレート コードを次のコードに置き換えます。

using System;
using System.ComponentModel.DataAnnotations;

namespace ContosoUniversity.ViewModels
{
    public class EnrollmentDateGroup
    {
        [DataType(DataType.Date)]
        public DateTime? EnrollmentDate { get; set; }

        public int StudentCount { get; set; }
    }
}

Home コントローラーを変更する

  1. HomeController.cs で、ファイルの先頭に次usingのステートメントを追加します。

    using ContosoUniversity.DAL;
    using ContosoUniversity.ViewModels;
    
  2. クラスの中かっこの直後に、データベース コンテキストのクラス変数を追加します。

    public class HomeController : Controller
    {
        private SchoolContext db = new SchoolContext();
    
  3. About メソッドを次のコードに置き換えます。

    public ActionResult About()
    {
        IQueryable<EnrollmentDateGroup> data = from student in db.Students
                   group student by student.EnrollmentDate into dateGroup
                   select new EnrollmentDateGroup()
                   {
                       EnrollmentDate = dateGroup.Key,
                       StudentCount = dateGroup.Count()
                   };
        return View(data.ToList());
    }
    

    LINQ ステートメントは、登録日で受講者エンティティをグループ化し、各グループ内のエンティティの数を計算して、結果を EnrollmentDateGroup ビュー モデル オブジェクトのコレクションに格納します。

  4. メソッドを追加します Dispose

    protected override void Dispose(bool disposing)
    {
        db.Dispose();
        base.Dispose(disposing);
    }
    

About ビューを変更する

  1. Views\Home\About.cshtml ファイルのコードを次のコードに置き換えます。

    @model IEnumerable<ContosoUniversity.ViewModels.EnrollmentDateGroup>
               
    @{
        ViewBag.Title = "Student Body Statistics";
    }
    
    <h2>Student Body Statistics</h2>
    
    <table>
        <tr>
            <th>
                Enrollment Date
            </th>
            <th>
                Students
            </th>
        </tr>
    
    @foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                @item.StudentCount
            </td>
        </tr>
    }
    </table>
    
  2. アプリを実行し、[ バージョン情報 ] リンクをクリックします。

    各登録日の学生数がテーブルに表示されます。

    About_page

コードを取得する

完成したプロジェクトをダウンロードする

その他の技術情報

他の Entity Framework リソースへのリンクは、「 ASP.NET データ アクセス - 推奨リソース」にあります。

次のステップ

このチュートリアルでは、次の作業を行いました。

  • 列の並べ替えリンクを追加する
  • [検索] ボックスを追加する
  • ページングの追加
  • About ページを作成する

次の記事に進み、接続の回復性とコマンド インターセプトを使用する方法について説明します。