情報応用演習Ⅰ(2024)

【T8a】多対多のリレーションシップ(8/8)

プロジェクトタイプ(注意: 本文参照)
プロジェクト名T8a
ソリューション名PIT8
注意
  • 本ページの作業内容は 前のページまでの続き になっていることに注意せよ.
    • 先に前のページまでをすべて読み,指示されている作業を済ませてから本ページを読むこと.
    • プロジェクトの作成作業については準備作業を参照せよ.

8a-8. 「サークル所属」エンティティの追加

さて話を元に戻そう.ここでは多対多のリレーションシップの作成を試みていたのであった. ここまでで,図8a-5-2で示したエンティティのうち「学生」と「サークル」を作成することができた. つぎはこれらのあいだの関係を表すエンティティである「サークル所属」を表すモデルクラスを作成してみよう.

この「サークル所属」の情報には_に示す項目を含めることにしよう.

「サークル所属」の情報
項目名必須/任意データ型備考
学生ID必須整数主キーの一部.「学生」への外部キー.
サークルID必須整数主キーの一部.「サークル」への外部キー.

ポイントは,「学生」と「サークル」への外部キーだけを含んでおり,かつこの「学生」と「サークル」のIDの 組み合わせ自体が主キーとなっている点 である.このような複数の列の組み合わせからなる主キーを コンポジットキー (複合主キー)という. コンポジットキーを用いる理由は,例を使って説明した方が分かりやすいだろう. たとえば「学生」と「サークル」それぞれに_に示すようなデータが格納されているとしよう.

「学生」と「サークル」のレコード例

これに対してエンティティ「サークル所属」は,「 誰がどのサークルに所属するか 」を個々のレコードで表現する. たとえば「サークル所属」は_のような内容を含む.

「サークル所属」のレコード例1

このデータは_に示す所属状況を意味している.すなわち,学生ID:1の「医療太郎」さんは2つのサークル, 「テニスサークル(ID:1)」と「軽音サークル(ID:2)」に所属しており,また,学生ID:4の「山田奈緒子」さんは「漫画研究会(ID:3)」に, 学生ID:5の「山田太郎」さんは「テニスサークル(ID:1)」にそれぞれ所属している.

学生IDとサークルIDの組が主キーになっているのは,ある学生があるサークルに所属しているのであれば「サークル所属」の レコードの組み合わせは一通りしかありえないためである.たとえば_のように 「誰がどのサークルに所属するか」の情報が重複して登録されることはあり得ない.

「サークル所属」のレコード例2

このため学生IDとサークルIDの組を主キーに位置付けているわけである.

それではこの「サークル所属」に対応するモデルクラスを定義しよう.クラス名は「サークル所属」をそのまま 英訳してCircleAffiliationとする.プロジェクト内の Models フォルダを右クリックし,「追加」→「クラス」をクリックする. 作成するクラス名を訊かれるのでCircleAffiliation(.csは省略可能)と入力して「追加」ボタンをクリックする. すると空のクラス定義が作られるので_の定義を書き込もう.

「サークル所属」クラス
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
using Microsoft.EntityFrameworkCore;
using System.ComponentModel.DataAnnotations;

namespace T8a.Models
{
    // 「サークル所属」クラス
    [PrimaryKey(nameof(StudentId), nameof(CircleId))] // コンポジットキーの指定
    public class CircleAffiliation
    {
        [Display(Name = "学生ID")]
        public int StudentId { get; set; }    // 「学生」への外部キープロパティ
        public Student? Student { get; set; } // ↑のための参照ナビゲーションプロパティ

        [Display(Name = "サークルID")]
        public int CircleId { get; set; }     // 「サークル」への外部キープロパティ
        public Circle? Circle { get; set; }   // ↑のための参照ナビゲーションプロパティ
    }
}

7行目でクラス定義に付与されている[PrimaryKey]属性は, このクラスの主キーがStudentIdCircleIdのコンポジットキーであることを指定するものである.この属性には コンポジットキーを構成するプロパティ名を文字列型の値として列挙する.ここではnameof式を 使って,StudentIdプロパティとCircleIdプロパティの名前を文字列として取り出して利用している.コンポジットキーはこのように指定するということを覚えておこう.

次に, Data/T8aContext.cs に_に示す内容を追記する.

T8aContextクラスへの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
using Microsoft.EntityFrameworkCore;
using T8a.Models;

namespace T8a.Data
{
    public class T8aContext : DbContext
    {
        public T8aContext(DbContextOptions<T8aContext> contextOptions)
            : base(contextOptions)
        { }


        public DbSet<Student> Students => Set<Student>();

        public DbSet<Department> Departments => Set<Department>();

        public DbSet<Circle> Circles => Set<Circle>();

        public DbSet<CircleAffiliation> CircleAffiliations => Set<CircleAffiliation>(); // 追加

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);

            modelBuilder.Entity<Department>().HasData(
                new Department() { Id = 1, Name = "医療情報学科" },
                new Department() { Id = 2, Name = "医療栄養学科" },
                new Department() { Id = 3, Name = "医療看護学科" }
                );
        }
    }
}

19行目では「サークル所属」をデータベース上のテーブルとして反映させるためにDbSet<TEntity>型のプロパティを追加している. ここまで書けたらマイグレーションコードを生成して,データベースにこの変更を反映させてみよう. コマンドラインターミナル1_に示すコマンドを実行する(_).

マイグレーション処理の生成と適用
PS> dotnet ef migrations add AddCircleAffiliation
PS> dotnet ef database update

実行したら pgAdmin でデータベースの変化も確認しておこう. pgAdmin を起動するか,もしくは pgAdmin がすでに起動中の場合は 「 Servers 」→「 PostgreSQL 16 」→「 Databases 」を右クリックして「 Refresh 」を実行する. 「 Databases 」→「 t8a_db 」→「 Schemas 」→「 public 」→「 Tables 」に,新たなテーブル「 CircleAffiliations 」が 追加されていることを確認する(_).また「 CircleAffiliations 」を右クリックして「 Properties 」をクリックし, このテーブルの列の情報( Columns タブ)と主キー制約と外部キー制約の情報( Constrains タブの Primary Key と Foreign Key )を 確認しておこう(__).

マイグレーション処理の結果を確認

「Constrains」タブの「Primary Key」には,PK_CircleAffiliationsという名前の制約が作られており,その内容が StudentId,CircleId となっており コンポジットキーが構成されていることがわかるだろう(_).また「Constrains」タブの「Foreign Key」には, 「 Circles 」テーブルを参照する外部キーと,「 Students 」テーブルを参照する外部キー制約が表示されていることが分かるだろう(_).

前述の説明の通りこの「サークル所属」のエンティティは,「学生」や「サークル」のエンティティから見れば「多」の側,つまり従属エンティティである. C#のコード上で,「学生」からその学生が所属しているサークルの情報を手繰り寄せたり,「サークル」からそのサークルに所属している学生の情報を手繰り寄せたり するためにはコレクションナビゲーションプロパティが必要である. Models/Student.cs と Models/Circle.cs にそれぞれ __に示す内容を追記しよう.

Studentクラスへの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// 「学生」クラス
public class Student
{
    [Display(Name = "ID")]
    public int Id { get; set; }          // ID

    [Display(Name = "姓")]
    public string LastName { get; set; } = "";   // 姓

    [Display(Name = "ミドルネーム")]
    public string? MiddleName { get; set; }       // ミドルネーム

    [Display(Name = "名")]
    public string FirstName { get; set; } = "";  // 名

    [Display(Name = "性別")]
    public SexType Sex { get; set; }         // 性別

    [Phone]
    [Display(Name = "電話番号")]
    public string PhoneNumber { get; set; } = "";// 電話番号

    [EmailAddress]
    [Display(Name = "メールアドレス")]
    public string? Mail { get; set; }        // メールアドレス

    [DataType(DataType.Date)]
    [Display(Name = "誕生日")]
    public DateTime? Birthday { get; set; }    // 誕生日

    [DataType(DataType.DateTime)]
    [Display(Name = "登録日時")]
    public DateTime Registered { get; set; }  // 登録日時

    [Display(Name = "所属学科")]
    public int DepartmentId { get; set; }

    public Department? Department { get; set; }

    // コレクションナビゲーションプロパティ
    public List<CircleAffiliation>? CircleAffiliations { get; set; }
}
Circleクラスへの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
// 「サークル」クラス
public class Circle
{
    [Display(Name = "ID")]
    public int Id { get; set; }

    [Display(Name = "サークル名")]
    public string Name { get; set; } = "";

    [Display(Name = "サークル説明")]
    public string? Description { get; set; }

    // コレクションナビゲーションプロパティ
    public List<CircleAffiliation>? CircleAffiliations { get; set; }
}

次にこの「サークル所属」のレコードを操作するためのアクションやビューを作成しよう. 今回は「サークル所属」のための専用のコントローラーを作成するのでは なく ,Studentsコントローラーに 所属サークルの情報を編集するためのアクションEditCircleAffiliations(GET/POST用)を追加することにする(_).

「サークル所属」のための画面イメージと画面遷移

EditCircleAffiliationsアクションでは,すべてのサークルをチェックボックスのリストとして表示する.その学生がサークルに 所属している場合にはチェックボックスにチェックが入っている状態にし,未所属の場合にはチェックが入っていない状態で表示する. このような所属に関する情報を編集するためのUIのパターンはほかにもありうるが, 今回はサークル数がそれほど多くはならない(サークル数≪学生数)と考えられるため,存在する全サークルをこのような方法で表示・編集することにする.

この表示・編集のためには このビューでのみ使用する専用の小さなモデルクラス を用意するのが最もシンプルな方法である. この小さなクラスには, 「サークル」と「そのサークルに所属してるかいないか」 という情報を含め,このクラスのリストを ビューとアクションのあいだでやり取りする.このクラスはあくまでもデータのやり取りのためのクラスなのでデータベースに 反映させるエンティティではないことに注意せよ.

それでは実際に作成してみよう.プロジェクト内の Models フォルダを右クリックし,「追加」→「クラス」をクリックする. 作成するクラス名を訊かれるのでCircleSelection(.csは省略可能)と入力して「追加」ボタンをクリックする. すると空のクラス定義が作られるので_の定義を書き込もう.

CircleSelectionクラス
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
namespace T8a.Models
{
    // 「サークル」と「そのサークルに所属してるかいないか」の情報のためのクラス
    public class CircleSelection
    {
        public int CircleId { get; set; }            // サークルID
        public string CircleName { get; set; } = ""; // サークル名

        public bool Selected { get; set; }           // 所属(true) or 未所属(false)
    }
}

このクラスのコレクションを作成するには,リレーショナルデータベースの用語でいうところの 外部結合 が必要になる. 「サークル所属」から特定の学生の所属サークルを拾うだけでは,当然のことながらその学生の所属しているサークルしかリスト化することができない. _に示す通りEditCircleAffiliationsアクションのビューでは,所属しているかしていないかにかかわらず 全サークルをチェックボックスのリストとして表示するため,「サークル」と「サークル所属」を外部結合することでCircleSelectionクラスの コレクションを作成する必要がある(_).

「サークル」と「サークル所属」の外部結合

これを踏まえたうえで,まずGET用のEditCircleAffiliationsアクションメソッドを作成しよう. Controllers/StudentsController.cs に_に示す内容を追記する.

StudentsControllerクラスへの追記内容1
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// EditCircleAffiliationsアクション(GET用)
public async Task<IActionResult> EditCircleAffiliations(int? id) 
{
    if (id == null)                                                  // idが指定されなかったら
        return NotFound();                                           // 404 Not Found

    var student = await _context.Students.FindAsync(id);             // idで指定された学生情報を探す

    if (student == null)                                             // idで指定された学生が
        return NotFound();                                           // 見つからなかったら 404 Not Found

    var cs = (from c in _context.Circles                             // CircleとCircleAffiliationを外部結合する.
                join ca in _context.CircleAffiliations               // 
                on new { CircleId = c.Id, StudentId = student.Id }   // 結合条件はサークルIDが一致していてかつ,
                    equals new { ca.CircleId, ca.StudentId } into gj // ↑の学生のIDであること.
                from joinedCA in gj.DefaultIfEmpty()                 // 結合条件に合わないCirlceの場合はNULL
                orderby c.Id ascending                               //
                select new CircleSelection() {                       //
                    CircleId = c.Id,                                 //
                    CircleName = c.Name,                             //
                    Selected = joinedCA != null                      // joindCAがNULLかどうかをSelectedに入れる.
                });                                                  //

    ViewBag.Student = student;                                       // ビューでは学生自体の情報も必要なので
                                                                     // .ViewBag に入れておく.

    return View(await cs.ToListAsync());
}

_のようにLINQでも join というキーワードを用いて,指定した条件で複数のモデルクラスを結合することができる. LINQではこれをグループジョインという用語で呼ぶ.

この場合はCirecleCircleAffiliationをグループジョインしており,その結合条件は14~15行目のon ××× equals ○○○の部分である. ここでは「サークル」のIDとルーティングパラメーターで指定された学生IDの組(×××の部分)が, 「サークル所属」のサークルIDと学生IDの組(○○○の部分)と等しいことを結合条件としている. そしてこの結合条件に合致するCircleAffiliationオブジェクトのコレクションを範囲変数gjに代入している.

このクエリを外部結合にするためのポイントは16行目の.DefaultIfEmpty()である.これはグループジョインした被結合側のコレクションが(gj)が 空である場合はこれを null として扱う,言い換えると結合条件に合致するものがなくても結合結果に含めるという作用を持つ. そして18~22行目の select 句で,これらの情報からCircleSelectionクラスのオブジェクトを生成している.

後述するこのアクションのビューでは,編集対象の学生の情報も表示するので,24行目ではルーティングパラメーターidで指定されたStudentオブジェクトをViewBagにセットしている. またメソッドの最後の return文 では前述のLINQの結果をリスト化してビューに渡している.

次にこのアクションのためのビューを作成しよう. Views/Students フォルダにEditCircleAffiliationsという名前のビューを追加する. Views/Students/EditCircleAffiliations.cshtml を_のように変更する.

Views/Students/EditCircleAffiliations.cshtmlの内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@model List<CircleSelection>

@{
    ViewData["Title"] = "所属サークル編集";

    if (Model is null) throw new ArgumentNullException(nameof(Model)); // ad-hoc! 非null保証のための回避策
}

@* 編集対象の学生情報の表示 *@
@{ var s = (Student)ViewBag.Student; }
<p>ID:@s.Id, @s.LastName @s.FirstName</p>

@* 所属サークルの編集のためのフォーム *@
<form asp-action="EditCircleAffiliations">
    @for (int i = 0; i < Model.Count; ++i)
    {
        <input asp-for="@Model[i].Selected" />
        <input type="hidden" asp-for="@Model[i].CircleId" />        
        <label asp-for="@Model[i].Selected">@Model[i].CircleName</label>        
        <br />
    }
    <input type="submit" value="送信" />
</form>

<a asp-action="Details" asp-route-id="@(s.Id)">学生詳細に戻る</a>

ここではあえて foreach ではなく for 文を用いてリストを反復しているが,これは必須である. 特定のデータのコレクションを送信させる場合は, asp-for タグヘルパーにインデックスが表れる形で記述する必要がある.

さらに学生詳細画面(Details)から,このビューへのリンクを貼るため Views/Students/Details.cshtml に_に示す内容を追記する.

Views/Students/Details.cshtmlの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
@model Student

@{
    ViewData["Title"] = "学生詳細";

    if (Model is null) throw new ArgumentNullException(nameof(Model)); // ad-hoc! 非null保証のための回避策
}

<table>
  <tr><th>項目</th><th></th></tr>

  @* IDの表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Id)</td>
     <td>@Html.DisplayFor(s => s.Id)</td>
  </tr>

  @* 姓(LastName)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.LastName)</td>
     <td>@Html.DisplayFor(s => s.LastName)</td>
  </tr>

  @* 名(FirstName)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.FirstName)</td>
     <td>@Html.DisplayFor(s => s.FirstName)</td>
  </tr>

  @* 性別(Sex)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Sex)</td>
     <td>@Html.DisplayFor(s => s.Sex)</td>
  </tr>

  @* 所属学科(DepartmentId)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.DepartmentId)</td>
     <td>@Html.DisplayFor(s => s.Department.Name)</td>
  </tr>

  @* 電話番号(PhoneNumber)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.PhoneNumber)</td>
     <td>@Html.DisplayFor(s => s.PhoneNumber)</td>
  </tr>

  @* メールアドレス(Mail)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Mail)</td>
     <td>@Html.DisplayFor(s => s.Mail)</td>
  </tr>

  @* 誕生日(Birthday)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Birthday)</td>
     <td>@Html.DisplayFor(s => s.Birthday)</td>
  </tr>  
  
  @* 登録日時(Registered)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Registered)</td>
     <td>@Html.DisplayFor(s => s.Registered)</td>
  </tr>

  @* 所属サークル編集のためのリンク *@
  <tr>
      <td>所属サークル</td>
      <td>
        <a asp-action="EditCircleAffiliations" asp-route-id="@Model.Id">編集</a>
      </td>
  </tr>

</table>

<a asp-action="Index">一覧に戻る</a>
| <a asp-action="Edit" asp-route-id="@Model.Id">編集</a>
| <a asp-action="Delete" asp-route-id="@Model.Id">削除</a>

ここまで書けたら実行してみよう.「学生管理」のリンクをクリックして適当な学生の学生詳細画面(Details)を表示させ, 「所属サークル」の行の「編集」のリンクをクリックする(__).すると全サークルがチェックボックスのリストとして 表示されるはずである(_).いまのところこの送信を受け付けるためのアクションメソッドを作っていないので まだ「送信」ボタンは機能しない

実行結果

つぎに,この送信を受け付けるためのアクションメソッドを作成しよう. Controllers/StudentsController.cs に_に示す内容を追記する.

StudentsControllerクラスへの追記内容2
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// EditCircleAffiliationsアクション(POST用)
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> EditCircleAffiliations(int? id, List<CircleSelection> circleSelections)
{
    if (id == null)                                           // idが指定されなかったら
        return NotFound();                                    // 404 Not Found

    var student = await _context.Students.FindAsync(id);      // idで指定された学生情報を探す
                                                              //
    if (student == null)                                      // idで指定された学生が
        return NotFound();                                    // 見つからなかったら 404 Not Found
                                                              
    if (ModelState.IsValid)                                   //
    {                                                         //
        foreach (var cs in circleSelections)                  // 送信されたCircleSelectionごとに処理する
        {                                                     //
            var sca = (from ca in _context.CircleAffiliations // 指定された学生IDと,
                        where ca.StudentId == student.Id      // CircleSelectionのサークルIDをもつ
                            && ca.CircleId == cs.CircleId     // CircleAffiliationのデータを探す.
                        select ca).FirstOrDefault();          //
                                                              
            if (sca != null && !cs.Selected)                  // 既存のサークル所属のチェックが解除された場合
            {                                                 //
                _context.Remove(sca);                         //   その所属情報を削除
            }                                                 //     
            else if (sca == null && cs.Selected)              // 新たなサークルにチェックが入れられた場合
            {                                                 //
                _context.Add(new CircleAffiliation()          // 
                {                                             //   所属情報を作って追加する
                    CircleId = cs.CircleId,                   // 
                    StudentId = student.Id                    //
                });                                           //
            }                                                 //
        }//foreach                                            //

        await _context.SaveChangesAsync();                    // データベースに変更を反映

        return RedirectToAction(nameof(Details), new { id = id }); // Detailsに遷移
    }
    else
    {
        return View(circleSelections);
    }
}

ここまで書けたら実行してみよう.「学生管理」のリンクをクリックして適当な学生の学生詳細画面(Details)を表示させ, 「所属サークル」の行の「編集」のリンクをクリックする(__).そして適当なサークルにチェックを入れて「送信」ボタンをクリックする(_). すると学生詳細画面(Details)に戻るが,もう一度「所属サークル」の行の「編集」のリンクをクリックしてみよう. 先ほどチェックを入れたサークルがチェックされた状態で「所属サークル編集」の画面が表示されるはずである(_).

実行結果

このままでもよいが,学生詳細画面(Details)にも所属するサークルのリストが表示されるようにしてみよう. Controllers/StudentsController.cs のDetailsメソッドに_に示す内容を追記する.

StudentsControllerクラスへの追記内容3
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Detailsアクション(GETのみ)
public IActionResult Details(int? id)
{
    if (id == null)
        return NotFound();

    var student = (from s in _context.Students
                    where s.Id == id
                    select s)
                    .Include(s => s.Department)
                    .FirstOrDefault();

    if (student == null)
        return NotFound();

    ViewBag.AffiliatedCircles = (from ca in _context.CircleAffiliations        // StudentIdが↑の学生IDに
                                 where ca.StudentId == student.Id              // 一致するレコードをとりだして
                                 orderby ca.CircleId ascending                 // リスト化して ViewBag に
                                 select ca).Include(ca => ca.Circle).ToList(); // 格納する.

    return View(student);
}

16~19行目ではLINQを使って,指定された学生と同じStudentIdをもつ「サークル所属」の レコードを取り出してViewBagに格納している.つぎにビューを変更して格納したデータを表示するようにしてみよう. Views/Students/Details.cshtml を_に示す内容を追記する.

Views/Students/Details.cshtmlの追記内容
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
@model Student

@{
    ViewData["Title"] = "学生詳細";

    if (Model is null) throw new ArgumentNullException(nameof(Model)); // ad-hoc! 非null保証のための回避策
}

<table>
  <tr><th>項目</th><th></th></tr>

  @* IDの表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Id)</td>
     <td>@Html.DisplayFor(s => s.Id)</td>
  </tr>

  @* 姓(LastName)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.LastName)</td>
     <td>@Html.DisplayFor(s => s.LastName)</td>
  </tr>

  @* 名(FirstName)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.FirstName)</td>
     <td>@Html.DisplayFor(s => s.FirstName)</td>
  </tr>

  @* 性別(Sex)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Sex)</td>
     <td>@Html.DisplayFor(s => s.Sex)</td>
  </tr>

  @* 所属学科(DepartmentId)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.DepartmentId)</td>
     <td>@Html.DisplayFor(s => s.Department.Name)</td>
  </tr>

  @* 電話番号(PhoneNumber)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.PhoneNumber)</td>
     <td>@Html.DisplayFor(s => s.PhoneNumber)</td>
  </tr>

  @* メールアドレス(Mail)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Mail)</td>
     <td>@Html.DisplayFor(s => s.Mail)</td>
  </tr>

  @* 誕生日(Birthday)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Birthday)</td>
     <td>@Html.DisplayFor(s => s.Birthday)</td>
  </tr>  
  
  @* 登録日時(Registered)の表示 *@
  <tr>
     <td>@Html.DisplayNameFor(s => s.Registered)</td>
     <td>@Html.DisplayFor(s => s.Registered)</td>
  </tr>

  @* 所属サークル編集のためのリンク *@
  <tr>
      <td>所属サークル</td>
      <td>
        @{
            var affiliatedCircles = (List<CircleAffiliation>)ViewBag.AffiliatedCircles;
            if (affiliatedCircles.Count != 0)
            {
                <ul>
                    @foreach (var ca in affiliatedCircles)
                    {
                        <li>@ca.Circle?.Name</li>
                    }
                </ul>
            }
        }
        <a asp-action="EditCircleAffiliations" asp-route-id="@Model.Id">編集</a>
      </td>
  </tr>

</table>

<a asp-action="Index">一覧に戻る</a>
| <a asp-action="Edit" asp-route-id="@Model.Id">編集</a>
| <a asp-action="Delete" asp-route-id="@Model.Id">削除</a>

ここまで書けたら実行してみよう.「学生管理」のリンクをクリックして 先ほどサークルの所属情報を編集した 学生の学生詳細画面(Details)を表示させてみよう.所属するサークルの情報が表示されるはずである(__). また「所属サークル」の行の「編集」のリンクをクリックしてサークルの所属情報を編集してみよう.学生詳細画面(Details)に 戻ると編集結果が反映されているはずである(__).

実行結果

ここまでの動作を確認したら1つ目のチュートリアルは完了である. 次に進む前に,混乱を防ぐため Visual Studio のエディタをすべて閉じておこう.Visual Studio のいずれかのエディタのタブを右クリックして 「すべててのドキュメントを閉じる」をクリックすれば,エディタをすべて閉じることができる


  1. タブのタイトルは「開発用PowerShell」もしくは「Developer PowerShell」となっている. ↩︎

Last updated on 2024-06-10
Published on 2024-06-10

Powered by Hugo. Theme by TechDoc. Designed by Thingsym.