Webアプリ CRUD操作のメニュー画面の作成方法で掲載したコードを改変してソート機能を追加しました。
以下の順番に記述します。
① ページモデルクラスの作成ポイント
② Razorページの作成ポイント
③ ソート列とソートキー別のソートオーダー
④ 画面サンプル
⑤ ページモデルクラス(Index.cshtml.cs)のコード例
⑥ Razor ページ(Index.cshtml.cs)のコード例
⑦ データモデルクラス(Value.cs)のコード例
⑧ コンテキストクラス(BGCContext.cs)のコード例
※ソート機能の追加においてコードを改変したのは⑤ページモデルクラスと⑥Razorページです。
⑦データモデルクラスと⑧コンテキストクラスは参考のために掲載しました。
① ページモデルクラスの作成ポイント
■ソートはクエリ式(LINQ)で記述
クエリ式は IQueryable<Value> valueIQ = from s in _context.Value select s; のように記述します。
IQueryable<Value> valueIQ 変数にクエリ式を構築していきます。
IQueryable は、ToListAsync などのメソッドを呼び出すことで、コレクションに変換されます。 ToListAsyncメソッドを呼び出す前であればIQueryable に 1 つのクエリを構築することができます。
■複数のソートの指定方法
ソートキーは、
日付でソートする場合、第一キー : Date 、第二キー : Timeになります。
血糖値でソートする場合は第一キー : BloodGlucoseValue、第二キー: Date 、第三キー : Timeになります。
複数キーのソートのクエリ式の構築は次のようになります。
第一キーを OrderBy または OrderByDescending で指定し、第二キー以降はThenByで指定します。
valueIQ = valueIQ.OrderBy(s => EF.Property<object>(s, sortOrder)).ThenBy(s => s.Date).ThenBy(s => s.Time);
※降順は、OrderByDescendingを指定します。
■昇順、降順の切換え方法
画面上の見出し名(日付、血糖値)をクリックするとソートします。
昇順 ⇒ 降順 ⇒ 昇順 ・・・・・というように、クリックするごとにソートオーダーが切り替わります。
切り替えは、次のように三項演算子で行います。
BloodGlucoseValueSort = sortOrder == “BloodGlucoseValue” ? “BloodGlucoseValue_desc” : “BloodGlucoseValue”;
DateSort = sortOrder == “Date” ? “Date_desc” : “Date”;
昇順、降順はdescending変数(bool)で判定します。
ソート対象の見出し列名はsortOrder変数が保持します。
■ブラウザからクエリ文字列でソート列、ソートオーダーを受取る
ブラウザで見出しをクリックするとリクエストのクエリ文字列として要求を受取ります。
https://localhost:7176/?sortOrder=Date_desc
https://localhost:7176/?sortOrder=Datec
https://localhost:7176/?sortOrder=BloodGlucoseValue_desc
https://localhost:7176/?sortOrder= BloodGlucoseValue
■クエリ式の実行
構築したクエリ式は Values = await valueIQ.AsNoTracking().ToListAsync(); のように記述し、ToListAsync()メソッドで実行します。
■動的にプロパティ名を取得
クエリ式構築をするときにキーのプロパティ名をEF.Property<object>(s, sortOrder)で動的に得ています。
valueIQ = valueIQ.OrderBy(s => EF.Property<object>(s, sortOrder)).ThenBy(s => s.Time);
上記EF.Property<object>(s, sortOrder)部分を例えば、sortOrderがDateなら下記のように変更しても内容は同じです。
valueIQ = valueIQ.OrderBy(s => s.Date).ThenBy(s => s.Time);
② Razorページの作成ポイント
次のようにアンカータグを記述します。
<a asp-page=”./Index” asp-route-sortOrder=”@Model.DateSort”>
@Html.DisplayFor(modelItem => item.Date)
</a>
@Model.DateSortの値は、Date または Date_desc がセットされています。
@Model.DateSortの値は、前回クリックしたものがDateであれば、Date_desc がセットされています。
前回クリックしたものがDate_descであれば、Date がセットされています。
<a asp-page=”./Index” asp-route-sortOrder=”@Model.BloodGlucoseValueSort”>
@Html.DisplayFor(modelItem => item.BloodGlucoseValue)
</a>
@Model.BloodGlucoseValueSort の値は、 BloodGlucoseValue または BloodGlucoseValue_desc がセットされています。
@Model.BloodGlucoseValueSort の値は、前回クリックしたものがBloodGlucoseValueであれば、BloodGlucoseValue _desc がセットされています。
前回クリックしたものがBloodGlucoseValue _descであれば、BloodGlucoseValue がセットされています。
③ ソート列とソートキー別のソートオーダー
見出し列の下記項目でソートします。
初回の表示は、日付の昇順で表示します。
ソート列 | 第一ソートキー | 第二ソートキー | 第三ソートキー |
日付 | Date : 昇順 | Time : 昇順 | ———- |
日付 | Date : 降順 | Time : 降順 | ———- |
血糖値 | BloodGlucoseValue : 昇順 | Date : 昇順 | Time : 昇順 |
血糖値 | BloodGlucoseValue : 降順 | Date : 昇順 | Time : 昇順 |
③ 画面サンプル
■画面サンプル 日付でソート
■画面サンプル 血糖値でソート
⑤ ページモデルクラス(Index.cshtml.cs)のコード例
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.EntityFrameworkCore; using BGC.Data; using BGC.Models; namespace BGC.Pages { public class IndexModel : PageModel { private readonly BGC.Data.BGCContext _context; public IndexModel(BGC.Data.BGCContext context) { _context = context; } public string BloodGlucoseValueSort { get; set; } public string DateSort { get; set; } public IList<Value> Values { get;set; } = default!; public async Task OnGetAsync(string sortOrder) { BloodGlucoseValueSort = sortOrder == “BloodGlucoseValue” ? “BloodGlucoseValue_desc” : “BloodGlucoseValue”; DateSort = sortOrder == “Date” ? “Date_desc” : “Date”; IQueryable<Value> valueIQ = from s in _context.Value select s; if (string.IsNullOrEmpty(sortOrder)) { sortOrder = “Date”; } bool descending = false; if (sortOrder.EndsWith(“_desc”)) { sortOrder = sortOrder.Substring(0, sortOrder.Length – 5); descending = true; } if (descending) { if (sortOrder.Equals(“Date”)) { valueIQ = valueIQ.OrderByDescending(s => EF.Property<object>(s, sortOrder)).ThenBy(s => s.Time); } else if ( sortOrder.Equals(“BloodGlucoseValue”)) { valueIQ = valueIQ.OrderByDescending(s => EF.Property<object>(s, sortOrder)).ThenBy(s => s.Date).ThenBy(s => s.Time); } } else { if (sortOrder.Equals(“Date”)) { valueIQ = valueIQ.OrderBy(s => EF.Property<object>(s, sortOrder)).ThenBy(s => s.Time); } else if (sortOrder.Equals(“BloodGlucoseValue”)) { valueIQ = valueIQ.OrderBy(s => s.BloodGlucoseValue).ThenBy(s => s.Date).ThenBy(s => s.Time); } } Values = await valueIQ.AsNoTracking().ToListAsync(); } } } |
⑥ Razor ページ(Index.cshtml.cs)のコード例
@page @model BGC.Pages.IndexModel @{ ViewData[“Title”] = “Index”; } <h1>Index</h1> <p> <a asp-page=”Create”>Create New</a> </p> <table class=”table”> <thead> <tr> <th> <a asp-page=”/Index” asp-route-sortOrder=”@Model.DateSort”> @Html.DisplayNameFor(model => model.Values[0].Date) </a> </th> <th> @Html.DisplayNameFor(model => model.Values[0].Time) </th> <th> <a asp-page=”/Index” asp-route-sortOrder=”@Model.BloodGlucoseValueSort”> @Html.DisplayNameFor(model => model.Values[0].BloodGlucoseValue) </a> </th> <th> @Html.DisplayNameFor(model => model.Values[0].TimeZone) </th> <th> @Html.DisplayNameFor(model => model.Values[0].Comment) </th> <th> @Html.DisplayNameFor(model => model.Values[0].WorkFlg) </th> <th> @Html.DisplayNameFor(model => model.Values[0].FastingFlg) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model.Values) { <tr> <td> @Html.DisplayFor(modelItem => item.Date) </td> <td> @Html.DisplayFor(modelItem => item.Time) </td> <td> @Html.DisplayFor(modelItem => item.BloodGlucoseValue) </td> <td> @Html.DisplayFor(modelItem => item.TimeZone) </td> <td> @Html.DisplayFor(modelItem => item.Comment) </td> <td> @Html.DisplayFor(modelItem => item.WorkFlg) </td> <td> @Html.DisplayFor(modelItem => item.FastingFlg) </td> <td> <a asp-page=”./Edit” asp-route-id=”@item.id”>Edit</a> | <a asp-page=”./Details” asp-route-id=”@item.id”>Details</a> | <a asp-page=”./Delete” asp-route-id=”@item.id”>Delete</a> </td> </tr> } </tbody> </table> |
⑦ データモデルクラス(Value.cs)のコード例
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; namespace BGC.Models { public class Value { public int id { get; set; } [Display(Name = “日付”)] [DataType(DataType.Date)] public DateTime Date { get; set; } [Display(Name = “時分”)] [DataType(DataType.Time)] public DateTime Time { get; set; } [Required] [Column(TypeName =”decimal(18,0)”)] [Display(Name = “血糖値”)] public decimal BloodGlucoseValue { get; set; } [Range(1,23)] [Display(Name = “時間帯”)] public int TimeZone { get; set; } [Display(Name = “備考”)] public string Comment { get; set; } [Display(Name = “仕事Flg”)] public bool WorkFlg { get; set; } public bool FastingFlg { get;set; } } } |
⑧ コンテキストクラス(BGCContext.cs)のコード例
using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using BGC.Models; namespace BGC.Data { public class BGCContext : DbContext { public BGCContext (DbContextOptions<BGCContext> options) : base(options) { } public DbSet<BGC.Models.Value> Value { get; set; } = default!; } } |
追記
次回は検索機能を追加したいと思っています。
日付、血糖値、時間帯を検索対象とします。
その次にページング機能を追加したいと思っています。
最後にCSVファイル出力機能を追加する予定です。
ここまでくると、ほぼ完成になります。
さらなる機能アップ、例えばブラウザでグラフを作成するとか、をするかどうか・・
完成したら考えましょう。