WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

各グループ内でレコードに連番を振る方法

by WebSurfer 2023年2月28日 15:48

Linq の GroupBy を使ってグループ分けを行い、グループに属するレコード一覧を取得する際に 1, 2, 3 ... という連番を振る方法を書きます。

ASP.NET Core MVC アプリ

上の画像は ASP.NET Core MVC アプリのもので、Northwind サンプルデータベースの Products テーブルのレコードを Supplier でグループ分けし、各グループに含まれるレコードの ProductName と UnitPrice を取得するとともに ProductId 順に 1, 2, 3 ... という Index (連番) を振って表示したものです。

先の記事「Entity Framework で ROW_NUMBER」で、Linq to Objects で SQL 文の ROW_NUMBER を使った場合と同様な連番を振る方法を紹介しました。それを GroupBy でグループ分けした各グループ内で行うものです。

Linq の GroupBy メソッドを使ってグループ分けすると IEnumerable<IGrouping<TKey, TElement>> オブジェクトが得られます。IGrouping<TKey, TElement> オブジェクトは共通のキー TKey を持つ TElement のコレクションになります。

Linq の Select メソッドを使って IGrouping<TKey, TElement> オブジェクトを反復処理する際に TElement にアクセスして必要な値を取得できますが、それにオーバーロードの一つである Select<TSource,TResult>(IEnumerable<TSource>, Func<TSource,Int32,TResult>) を使うと、同時に 0 番から始まる連番の index を取得することができます。

上の画像の ASP.NET Core MVC アプリのコードを下に載せておきます。使用したコンテキストクラスとエンティティクラスは先の記事「Linq の GroupBy と Aggregate」のものと同じです。上に述べた連番を振る具体例は下のコードの最後の方の Controller / Action Method のコードを見てください。

Model

namespace MvcNet7App.Models
{
    public class ProductDTO
    {
        public int ProductId { get; set; }
        public string ProductName { get; set; } = null!;
        public string Supplier { get; set; } = null!;
        public decimal? UnitPrice { get; set; }
        public int Index { get; set; } // 連番用
    }
}

View

@model Dictionary<string, IEnumerable<ProductDTO>>

@{
    ViewData["Title"] = "GroupBy";
}

@foreach (var item in Model)
{
    <h4>Supplier: @Html.DisplayFor(m => item.Value.First().Supplier)</h4>
    <table class="table">
        <thead>
            <tr>
                <th>
                    @Html.DisplayNameFor(m => item.Value.First().ProductId)
                </th>
                <th>
                    @Html.DisplayNameFor(m => item.Value.First().Index)
                </th>
                <th>
                    @Html.DisplayNameFor(m => item.Value.First().ProductName)
                </th>
                <th>
                    @Html.DisplayNameFor(m => item.Value.First().UnitPrice)
                </th>
            </tr>
        </thead>
        <tbody>
            @foreach (var product in item.Value)
            {
                <tr>
                    <td>
                        @Html.DisplayFor(m => product.ProductId)
                    </td>
                    <td>
                        @Html.DisplayFor(m => product.Index)
                    </td>
                    <td>
                        @Html.DisplayFor(m => product.ProductName)
                    </td>
                    <td>
                        @Html.DisplayFor(m => product.UnitPrice)
                    </td>
                </tr>
            }
        </tbody>
    </table>
}

Controller / Action Method

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Rendering;
using Microsoft.EntityFrameworkCore;
using MvcNet7App.Data;
using MvcNet7App.Models;
using System.ComponentModel.DataAnnotations;

namespace MvcNet7App.Controllers
{
    public class ProductsController : Controller
    {
        private readonly NorthwindContext _context;

        public ProductsController(NorthwindContext context)
        {
            _context = context;
        }

        public async Task<IActionResult> GroupBy()
        {
            // いきなり _context.Products.GroupBy(p => p.SupplierId) と
            // はできないので一旦 List<Product> 型オブジェクトを作り、            
            List<Product> productList = await _context.Products
                                              .Include(p => p.Supplier)
                                              .ToListAsync();

            // Linq to Objects として GroupBy を適用する
            IEnumerable<IGrouping<string, Product>> groups = 
                productList.GroupBy(p => p.Supplier!.CompanyName)
                .OrderBy(g => g.Key);

            // グループごとに連番を取得して IEnumerable<ProductDTO> オブ
            // ジェクトを作り、Dictionary<string, IEnumerable<ProductDTO>>
            // に詰め替えて View にモデルとして渡す
            var dic = new Dictionary<string, IEnumerable<ProductDTO>>();
            foreach (IGrouping<string, Product> group in groups)
            {                
                var groupDTO = group
                       .OrderBy(p => p.ProductId)
                       .Select((p, index) => new ProductDTO
                       {
                           Supplier = p.Supplier!.CompanyName,
                           ProductId = p.ProductId,
                           Index = index + 1,
                           ProductName = p.ProductName,                           
                           UnitPrice = p.UnitPrice                           
                       });

                dic.Add(group.Key, groupDTO);
            }

            return View(dic);
        }
    }
}

Tags: , , ,

ADO.NET

DropDownList への SelectList の渡し方 (CORE)

by WebSurfer 2021年1月22日 18:10

.NET Framework 版 MVC の話は先の記事「DropDownList への SelectList の渡し方」に書きましたが、その Core 版の話を聞きます。

DropDownList で複数項目を選択

Core 版 MVC アプリでは従前から提供されている Html ヘルパーの DropDownList, DropDownListFor メソッドに加えて、タグヘルパーというものが使えますのでそれを使った場合の例も書いておきます。

タグヘルパーに関する詳細は以下の Microsoft のドキュメントが参考になると思いますので、そちらを見てください(手抜きでスミマセン)。

SelectList のコンストラクタの第 4 引数に設定した selectedValue の通りに html にレンダリングされた時の option 要素に selected 属性を設定するにはどうするかという点がポイントでし���が、Html ヘルパーの DropDownList, DropDownListFor メソッドについてはそのあたりは .NET Framework 版と同様でした。

例えば ViewBag.CategoryID で SelectList を DropDownList メソッドに渡す場合は、第 1 引数は "CategoryID" という文字列、第 2 引数は null にします。詳しい説明は先の記事「DropDownList への SelectList の渡し方」を見てください。

select タグヘルパーを使う場合ですが、SelectList を ViewBag (または ViewData) 経由で渡す場合でかつ Model を使用しない場合は asp-for は使えませんので、select タグヘルパーに id, name 属性を設定して asp-items 属性に ViewBag (または ViewData) を渡します。

さらに、上に紹介した記事「ASP.NET Core のフォームのタグ ヘルパー」の「選択タグ ヘルパー」のセクションに Model で渡すサンプルコードが書いてあって、そのようにしても option 要素に selected 属性の設定が可能です。

その記事によると "We don't recommend using ViewBag or ViewData with the Select Tag Helper. A view model is more robust at providing MVC metadata and generally less problematic." とのことで、可能ならば SelectList を渡すには Model を使った方が良さそうです。(Model を使えないケースも多々ありそうですが)

以下に、(1) 1 項目だけ選択できるドロップダウンリスト、(2) 複数項目が選択可能なドロップダウンリスト、(3) 初期表示の際複数項目を指定して選択状態にするドロップダウンリストのサンプルコードを書いておきます。

(1) 1 項目だけ選択可

先の記事「DropDownList への SelectList の渡し方」と同じ条件です。

Model

select タグヘルパーに Model 経由で SelectList と初期状態の選択項目を渡すために使います。上に紹介した 3 番目の記事によると "asp-for 属性に指定されているプロパティが IEnumerable の場合、選択タグ ヘルパーは multiple = "multiple" 属性を自動的に生成します" とのことですので注意してください。

public class CategoryViewModel
{
    public int Category { get; set; }

    public SelectList Categories { get; set; }
}

Controller / Action Method

public async Task<IActionResult> Edit()
{
    int selected = 3;
    var categoryList = await _context.Categories.ToListAsync();

    ViewBag.CategoryId = new SelectList(categoryList, 
                                        "CategoryId", 
                                        "CategoryName", 
                                        selected);

    var model = new CategoryViewModel
    {
        Category = selected,
        Categories = new SelectList(categoryList,
                                    "CategoryId",
                                    "CategoryName")
    };

    return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit(int categoryid, 
                          int categoryid2, 
                          int category)
{
    if (ModelState.IsValid)
    {
        return RedirectToAction("Index");
    }

    return View();
}

View

@model MvcCore5App.Controllers.CategoryViewModel

@{
    ViewData["Title"] = "Edit";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Edit</h1>

<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit">
            <div class="form-group">
                <label class="control-label" for="CategoryId">
                    @@Html.DropDownList 利用
                </label>
                @Html.DropDownList("CategoryId", null,
                    htmlAttributes: new { @class = "form-control" })
            </div>
            <div class="form-group">
                <label class="control-label" for="CategoryId2">
                    タグヘルパー &lt;select&gt; + ViewBag 利用
                </label>
                <select id="categoryid2" name="categoryid2"
                        asp-items="@ViewBag.CategoryId"
                        class="form-control">
                </select>
            </div>
            <div class="form-group">
                <label class="control-label" for="CategoryId2">
                    タグヘルパー &lt;select&gt; + Model 利用
                </label>
                <select asp-for="Category"
                        asp-items="Model.Categories"
                        class="form-control">
                </select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create"
                       class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

(2) 複数項目の選択可

Model

上記 (1) の Model と同じ。

Controller / Action Method

[HttpPost] 側のアクションメソッドの引数が int[] 型になっている点に注目してください。int[] 型にすることにより、ユーザーが複数項目を選択した結果を受け取れます。

public async Task<IActionResult> Edit2()
{
    int selected = 3;
    var categoryList = await _context.Categories.ToListAsync();

    ViewBag.CategoryId = new SelectList(categoryList,
                                        "CategoryId",
                                        "CategoryName",
                                        selected);

    var model = new CategoryViewModel
    {
        Category = selected,
        Categories = new SelectList(categoryList,
                                    "CategoryId",
                                    "CategoryName")
    };

    return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit2(int[] categoryid, 
                           int[] categoryid2, 
                           int[] category)
{
    if (ModelState.IsValid)
    {
        return RedirectToAction("Index");
    }

    return View();
}

View

ユーザーが複数項目を選択できるよう multiple = "multiple" を付与しているところに注目してください。上にも書きましたが、asp-for 属性に指定されるプロパティが IEnumerable の場合、選択タグ ヘルパーは multiple = "multiple" 属性を自動的に生成しますとのことです。

@model MvcCore5App.Controllers.CategoryViewModel

@{
    ViewData["Title"] = "Edit2";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Edit2</h1>

<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit2">
            <div class="form-group">
                <label class="control-label" for="CategoryId">
                    @@Html.DropDownList 利用
                </label>
                @Html.DropDownList("CategoryId", null,
                    htmlAttributes: new
                    {
                        @class = "form-control",
                        multiple = "multiple"
                    })
            </div>
            <div class="form-group">
                <label class="control-label" for="CategoryId2">
                    タグヘルパー &lt;select&gt; + ViewBag 利用
                </label>
                <select id="categoryid2" name="categoryid2"
                        asp-items="@ViewBag.CategoryId"
                        multiple="multiple"
                        class="form-control">
                </select>
            </div>
            <div class="form-group">
                <label class="control-label" for="CategoryId2">
                    タグヘルパー &lt;select&gt; + Model 利用
                </label>
                <select asp-for="Category"
                        asp-items="Model.Categories"
                        multiple="multiple"
                        class="form-control">
                </select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create"
                       class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

(3) 初期表示の際複数項目を選択状態にする

上の画像を表示したのがこれです。

Model

SelectList に代えて MultiSelectList を使っているところに注目してください。

public class MultiCategoryViewModel
{
    public int[] Category { get; set; }

    public MultiSelectList Categories { get; set; }
}

Controller / Action Method

初期表示の時点で複数項目が選択されるようにするため、int selected を int[] selected に代えて、それを MultiSelectList の第 4 引数に渡している点に注目してください。View に渡す Model も CategoryViewModel から MultiCategoryViewModel に変えています。

public async Task<IActionResult> Edit3()
{
    int[] selected = new int[] { 1, 3, 5 };
    var categoryList = await _context.Categories.ToListAsync();

    ViewBag.CategoryId = new MultiSelectList(categoryList,
                                             "CategoryId",
                                             "CategoryName",
                                             selected);

    var model = new MultiCategoryViewModel
    {
        Category = selected,
        Categories = new MultiSelectList(categoryList,
                                         "CategoryId",
                                         "CategoryName")
    };

    return View(model);
}

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Edit3(int[] categoryid, 
                           int[] categoryid2, 
                           int[] category)
{
    if (ModelState.IsValid)
    {
        return RedirectToAction("Index");
    }

    return View();
}

View

Model を CategoryViewModel から MultiCategoryViewModel に変えたこと、下のタグヘルパー <select> + Model 利用のコードには multiple = "multiple" を設定してないこと以外は上記 (2) の View と同じです。上の Model のコードの通り Category プロパティを int[] 型にしてそれを asp-for 属性に指定していますので、html には multiple = "multiple" 属性が自動的に生成されます。

@model MvcCore5App.Controllers.MultiCategoryViewModel

@{
    ViewData["Title"] = "Edit3";
    Layout = "~/Views/Shared/_Layout.cshtml";
}

<h1>Edit3</h1>

<div class="row">
    <div class="col-md-4">
        <form asp-action="Edit3">
            <div class="form-group">
                <label class="control-label" for="CategoryId">
                    @@Html.DropDownList 利用
                </label>
                @Html.DropDownList("CategoryId", null,
                    htmlAttributes: new
                    {
                        @class = "form-control",
                        multiple = "multiple"
                    })
            </div>
            <div class="form-group">
                <label class="control-label" for="CategoryId2">
                    タグヘルパー &lt;select&gt; + ViewBag 利用
                </label>
                <select id="categoryid2" name="categoryid2"
                        asp-items="@ViewBag.CategoryId"
                        multiple="multiple"
                        class="form-control">
                </select>
            </div>
            <div class="form-group">
                <label class="control-label" for="CategoryId2">
                    タグヘルパー &lt;select&gt; + Model 利用
                </label>
                <select asp-for="Category"
                        asp-items="Model.Categories"
                        class="form-control">
                </select>
            </div>
            <div class="form-group">
                <input type="submit" value="Create"
                       class="btn btn-primary" />
            </div>
        </form>
    </div>
</div>

Tags: , , ,

CORE

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar