第27章 MVC で DB を扱う:部署一覧
この章の目的
Section titled “この章の目的”この章では、ASP.NET Core MVC アプリから SQLServer のデータベース に接続して、departments テーブルの内容を一覧表示します。
題材は 部署一覧(departments テーブル)。列が少なく JOIN もない、最もシンプルな表です。これで「Web アプリ + DB」のひな型を体得します。
第 23〜25 章では Windows フォームから DB を扱いました。
この章では同じ DB(TrainingDB)に Web アプリから 接続します。コードの組み立て方は Windows 編とそっくりですが、認証方式と画面の作り方 が違います。
Windows フォーム編(第 23〜25 章) Web 編(第 27〜30 章)
[Windows フォーム] [ブラウザ] ↓ ↓EmployeeRepository.GetAll() DepartmentsController.Index() ↓ ↓SqlConnection(Integrated Security) DepartmentRepository.GetAll() ↓ ↓SQLServer (TrainingDB) SqlConnection(SQL 認証) ↓ SQLServer (TrainingDB) ↓ View (HTML) ↓ ブラウザに表示この章で扱う Repository パターン(DB アクセス用のクラスを分離する考え方)は、Windows フォーム編で導入したものと同じです。Web の Controller も、Repository を呼ぶだけで DB を意識せずに動かせます。
この章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- SQLServer に SQL 認証用のユーザー(
app_user) を作成できる - ASP.NET Core MVC プロジェクトに
Microsoft.Data.SqlClientを追加できる - SQL 認証の接続文字列(
User Id=...;Password=...;)を読める - Repository パターン の役割を自分の言葉で説明できる
- Controller から Repository を呼んで、結果を View に渡せる
- View で
@model List<...>を宣言し、@foreachで表(<table>)を組み立てられる - 値が
nullの場合に「未設定」のような代替表示を出せる - 現場で使われる「appsettings.json + DI」のパターンを概要レベルで知っている
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | ASP.NET Core Web アプリ(Model-View-Controller) |
| 対象フレームワーク | .NET 8 |
| ソリューション名 | Chapter27 |
| プロジェクト名 | Ch27_MvcDepartmentList |
| 認証の種類(プロジェクト) | なし |
| データベース | SQLServer 2022(TrainingDB) |
| 認証方式(DB) | SQL 認証(app_user) |
| NuGet パッケージ | Microsoft.Data.SqlClient |
csproj の Nullable は disable に変更してください(第 1 章「1-1」参照)
Web アプリの動かし方は第 26 章 26-4 参照
作業前チェック
Section titled “作業前チェック”- 第 26 章を Git に提出済みである
-
TrainingDBのdepartments/employeesテーブルが第 16 章の手順で作成済み - SSMS で
TrainingDBに Windows 統合認証で接続できる - ASP.NET Core MVC のテンプレートでプロジェクトを作って動かせる(第 26 章)
-
Microsoft.Data.SqlClientを使った DB アクセスの基本コードを読める(第 17 章・第 23 章)
27-1 Windows フォーム編からの変化点
Section titled “27-1 Windows フォーム編からの変化点”| 観点 | Windows フォーム(第 23〜25 章) | Web MVC(この章以降) |
|---|---|---|
| アプリの起動 | .exe を直接起動 | Web サーバー(Kestrel)を起動してブラウザで開く |
| 画面 | フォームデザイナで作る | .cshtml(HTML + Razor)を書く |
| 入力部品 | TextBox、Button、DataGridView など | <input>、<button>、<table> などの HTML |
| データの渡し方 | フォームのフィールド変数 | Model を View(model) で渡す |
| DB 認証 | Windows 統合認証 | SQL 認証(専用ユーザー) |
DB アクセス側のコード(SqlConnection、SqlCommand、SqlDataReader)は ほとんど同じ です。
違うのは 接続文字列 と、「誰が DB に接続するか」 という認証方式だけです。
なぜ Web では SQL 認証なのか
Section titled “なぜ Web では SQL 認証なのか”Windows 統合認証は「いま PC にログインしている Windows ユーザーで DB に接続する」仕組みです。 これは Windows フォームアプリのように PC で動くアプリ に向きます。
しかし Web アプリは サーバー側で動く ので、「アプリを使っている人の Windows ユーザー」は使えません(ブラウザのユーザーは、サーバー側からは Windows ユーザーとして見えないため)。
そこで、Web アプリ用に 専用の DB ユーザー を 1 つ作って、それで接続するのが現場の標準パターンです。
Windows フォーム: PC ログイン中の Windows ユーザー → SQLServerWeb アプリ: アプリ専用 DB ユーザー(app_user) → SQLServer27-2 SQL 認証ユーザー(app_user)を作る
Section titled “27-2 SQL 認証ユーザー(app_user)を作る”SSMS で TrainingDB に Windows 統合認証で接続して、以下の手順を実施します。
この作業は SSMS で一度だけ 行えば OK です(プロジェクトを作り直しても、ユーザーは残ります)。
手順 1:Mixed Mode(SQL Server 認証)を有効にする
Section titled “手順 1:Mixed Mode(SQL Server 認証)を有効にする”既定の SQLServer は Windows 認証のみ で動くことがあります。SQL 認証を使うには Mixed Mode に切り替えます。
- SSMS で
localhostに Windows 認証で接続 - オブジェクトエクスプローラー で
localhostを 右クリック → プロパティ - 左メニューの 「セキュリティ」 を選ぶ
- サーバー認証 を 「SQL Server 認証モードと Windows 認証モード」 に変更
- OK を押すと「SQL Server サービスの再起動が必要です」とメッセージが出る
- もう一度
localhostを 右クリック → 再起動 で SQL Server サービスを再起動
手順 2:ログインとユーザーを作る
Section titled “手順 2:ログインとユーザーを作る”SSMS の 新しいクエリ ウィンドウで、以下の SQL を順に実行します。
-- ① サーバーレベルでログインを作る(SQLServer 全体の認証情報)CREATE LOGIN app_user WITH PASSWORD = 'AppUserPass1!';GO
-- ② TrainingDB に切り替えるUSE TrainingDB;GO
-- ③ TrainingDB 内にデータベースユーザーを作る(ログインと結びつける)CREATE USER app_user FOR LOGIN app_user;GO
-- ④ TrainingDB 内での権限を付与(読み書き両方)ALTER ROLE db_datareader ADD MEMBER app_user;ALTER ROLE db_datawriter ADD MEMBER app_user;GO| 段階 | やっていること |
|---|---|
① CREATE LOGIN | サーバー全体で「app_user という名前 + パスワードでログインできる」と登録 |
③ CREATE USER | そのログインを TrainingDB の「ユーザー」として紐付け(DB ごとに必要) |
④ ALTER ROLE | app_user に 読み取り(db_datareader)・書き込み(db_datawriter) の権限を与える |
db_datareaderとdb_datawriterSQLServer に最初から用意されている データベース ロール です。
db_datareader:そのデータベースの全テーブルをSELECTできるdb_datawriter:そのデータベースの全テーブルをINSERT/UPDATE/DELETEできる第 28〜30 章で
INSERT/UPDATE/DELETEも使うので、最初から両方付けておきます。
手順 3:接続確認
Section titled “手順 3:接続確認”SSMS で 新しい接続 を開き、次の設定で接続できることを確認します。
| 項目 | 値 |
|---|---|
| サーバー名 | localhost |
| 認証 | SQL Server 認証 |
| ログイン | app_user |
| パスワード | AppUserPass1! |
接続できたら、次の SQL で departments の中身を確認できることをチェックします。
USE TrainingDB;SELECT * FROM departments ORDER BY department_id;5 件の部署(総務・営業・開発・マーケティング・品質管理)が見えれば OK です。
パスワードの強度
SQLServer は SQL 認証ログインに「複雑性ポリシー」を求めます。 8 文字以上で、英大文字 / 英小文字 / 数字 / 記号のうち 3 種類以上を含む必要があります。 本テキストでは練習用に
AppUserPass1!を使いますが、本番環境では当然違うパスワードを使ってください。
27-3 プロジェクトを作成・準備する
Section titled “27-3 プロジェクトを作成・準備する”新しいプロジェクトを作ります。
| 項目 | 値 |
|---|---|
| テンプレート | ASP.NET Core Web アプリ(Model-View-Controller) |
| ソリューション名 | Chapter27 |
| プロジェクト名 | Ch27_MvcDepartmentList |
| フレームワーク | .NET 8.0 |
| 認証の種類 | なし |
| HTTPS 用の構成 | 既定のままチェックを入れる |
| Docker の有効化 | チェックを外す |
作成したら、Ch27_MvcDepartmentList.csproj を開いて <Nullable>disable</Nullable> に変更します。
NuGet で Microsoft.Data.SqlClient を追加する
Section titled “NuGet で Microsoft.Data.SqlClient を追加する”第 17 章・第 23 章と同じ手順です。
- ソリューションエクスプローラーで プロジェクト名を右クリック → NuGet パッケージの管理
- 参照 タブを開き、検索欄に
Microsoft.Data.SqlClientと入力 - 最新版を選んで インストール
フォルダを作る
Section titled “フォルダを作る”Controllers / Models / Views フォルダはテンプレートにあるので、新しく Data フォルダ を追加します。Repository クラスをここに置きます。
- ソリューションエクスプローラーで プロジェクト名を右クリック → 追加 → 新しいフォルダー
- フォルダ名を
Dataとする
最終的に、研修の他の章と同じ「ソリューションフォルダの中にプロジェクトフォルダ」階層で次のような構成になります。
Chapter27/ ← ソリューションフォルダ├─ Chapter27.sln└─ Ch27_MvcDepartmentList/ ← プロジェクトフォルダ ├─ Controllers/ │ ├─ HomeController.cs │ └─ DepartmentsController.cs ← この章で作る ├─ Data/ ← この章で新規追加 │ └─ DepartmentRepository.cs ← この章で作る ├─ Models/ │ └─ Department.cs ← この章で作る ├─ Views/ │ ├─ Home/ │ ├─ Shared/ │ └─ Departments/ ← この章で新規追加 │ └─ Index.cshtml ← この章で作る ├─ wwwroot/ ← CSS・JavaScript・画像 ├─ Properties/ │ └─ launchSettings.json ← 起動 URL・ポート番号などの設定 ├─ Program.cs ← アプリ起動の入口 ├─ appsettings.json ← 設定情報(27-11 で接続文字列を置く先) └─ Ch27_MvcDepartmentList.csproj ← プロジェクトファイル(Nullable 設定もここ)第 26 章と同じく、Controllers・Models・Views に加えて、Repository を置く Data フォルダの 4 つを意識すれば OK です。
27-4 Department モデルクラスを作る
Section titled “27-4 Department モデルクラスを作る”Models フォルダに Department.cs を追加します。
namespace Ch27_MvcDepartmentList.Models;
public class Department{ public int DepartmentId { get; set; } public string DepartmentName { get; set; } = ""; public int? ManagerId { get; set; }}departments テーブル(章間整合ルール 1-2 参照)と対応します。
| テーブル列 | C# プロパティ | 型 |
|---|---|---|
department_id | DepartmentId | int |
department_name | DepartmentName | string |
manager_id | ManagerId | int?(NULL あり) |
第 11 章で学んだとおり、ManagerId は NULL があり得る列 なので int? を使います。
27-5 Repository パターンの考え方
Section titled “27-5 Repository パターンの考え方”Web アプリでも、DB アクセスのコード(SqlConnection、SqlCommand、SqlDataReader)を Controller に直接書くと、すぐにごちゃごちゃします。
そこで、「DB アクセスを担当するクラス」 = Repository を別に作り、Controller はそれを呼び出すだけにします。Windows フォーム編で導入したのと同じ考え方です。
Controller ← 画面の流れを決める ↓Repository ← DB アクセスを担当 ↓SqlConnection / SqlDataReader ↓SQLServer (TrainingDB)| クラスの役割 | 担当 |
|---|---|
Controller(DepartmentsController) | URL を受け取って Repository を呼び、View にデータを渡す |
Repository(DepartmentRepository) | SQL を投げて結果を List<Department> で返す |
Model(Department) | 1 行分のデータの入れ物 |
役割を分けるメリット:
- Controller がスッキリ読める(SQL がないので「何の処理か」が一目でわかる)
- DB アクセスを後から差し替えやすい(別 DB に変えるときに Controller を触らなくて済む)
- Windows フォームと Web で同じ Repository を共有できる(将来の発展用)
27-6 DepartmentRepository を作る
Section titled “27-6 DepartmentRepository を作る”Data フォルダに DepartmentRepository.cs を追加します。
using Microsoft.Data.SqlClient;using Ch27_MvcDepartmentList.Models;
namespace Ch27_MvcDepartmentList.Data;
public class DepartmentRepository{ private const string ConnectionString = "Server=localhost;Database=TrainingDB;User Id=app_user;Password=AppUserPass1!;TrustServerCertificate=true;";
public List<Department> GetAll() { List<Department> list = new List<Department>();
using SqlConnection conn = new SqlConnection(ConnectionString); conn.Open();
using SqlCommand cmd = conn.CreateCommand(); cmd.CommandText = "SELECT department_id, department_name, manager_id " + "FROM departments " + "ORDER BY department_id";
using SqlDataReader reader = cmd.ExecuteReader(); while (reader.Read()) { Department dept = new Department(); dept.DepartmentId = reader.GetInt32(0); dept.DepartmentName = reader.GetString(1); dept.ManagerId = reader.IsDBNull(2) ? null : reader.GetInt32(2); list.Add(dept); }
return list; }}このコードの中心は while (reader.Read()) のループです。reader.Read() は 次の行があれば true を返し、その行を読めるようにする メソッドで、行がなくなると false を返してループが終わります。これで「DB から返ってきた結果を 1 行ずつ取り出して Department に詰める」処理になります(第 23 章の EmployeeRepository でも同じパターンを使いました)。
ここで注目したいポイントが 4 つあります。
① 接続文字列が User Id=app_user;Password=...;
Section titled “① 接続文字列が User Id=app_user;Password=...;”Windows フォーム編は Integrated Security=true; でした。Web 編では 作成した SQL 認証ユーザー で接続します。
TrustServerCertificate=true; は、開発環境で SQLServer の SSL 証明書を信用するための指定です(本番では正しく証明書を準備します)。
② private const string で接続文字列を保持
Section titled “② private const string で接続文字列を保持”Windows フォーム編と同じスタイルです。Repository の中に閉じ込めて、Controller からは「new DepartmentRepository() するだけ」にします。
現場では appsettings.json で管理する のが一般的ですが、その方法は 27-11 で補足します。
③ using 宣言で接続を自動クローズ
Section titled “③ using 宣言で接続を自動クローズ”第 17 章で学んだ using SqlConnection conn = ...; です。Repository のメソッドを抜けると自動で conn.Dispose() が呼ばれ、接続が閉じられます。
④ IsDBNull(2) で NULL を判定
Section titled “④ IsDBNull(2) で NULL を判定”manager_id 列は NULL があり得るので、reader.IsDBNull(2) でチェックしてから値を取ります。NULL なら null を、値があるなら reader.GetInt32(2) をセットします。
チェックせずに reader.GetInt32(2) を呼ぶと、NULL の行で SqlNullValueException が発生します。
補足:列番号と列名、どちらで取り出すか
本文では
reader.GetInt32(0)のように 列番号(インデックス) を指定して値を取り出しています。短く書けるのが利点ですが、SELECT の列順を入れ替えたり列を追加したりすると番号がずれて、「動くけれど結果がおかしい」という気付きにくいバグになります。 実務では、列順の変化に強い 列名指定 を使う場面も多くあります。reader.GetInt32(reader.GetOrdinal("department_id"))のように書くと、列名から位置を求めて取り出せます。ただし値を 1 つ取り出すごとに記述が増え、少しソースが長くなります。 本研修では分かりやすさを優先して列番号で統一していますが、「列名でも書ける」ことは頭の片隅に置いておきましょう(第 23 章でも列名指定に触れています)。
ここで一度ビルド確認(まだ実行はできません)
この時点では画面(View)も Controller もまだ無いので、ブラウザでは動かせません。
Ctrl + Shift + Bで ビルドが通ること だけ確認しておきましょう(Model と Repository が正しく書けていれば通ります)。次の Controller・View をそろえてから、27-9 で実行します。
27-7 DepartmentsController を作る
Section titled “27-7 DepartmentsController を作る”Controllers フォルダに DepartmentsController.cs を追加します。
using Microsoft.AspNetCore.Mvc;using Ch27_MvcDepartmentList.Data;using Ch27_MvcDepartmentList.Models;
namespace Ch27_MvcDepartmentList.Controllers;
public class DepartmentsController : Controller{ public IActionResult Index() { DepartmentRepository repository = new DepartmentRepository(); List<Department> departments = repository.GetAll(); return View(departments); }}非常にシンプルです。やっていることは 3 行だけ。
| 行 | やっていること |
|---|---|
new DepartmentRepository() | Repository を作る |
repository.GetAll() | DB から全件取得 |
return View(departments) | List を View に渡してレンダリング |
第 26 章の FortuneController と比べると、Random の代わりに Repository を呼んでいるだけ、と読み取れます。
URL との対応
Section titled “URL との対応”| URL | Controller | Action | View |
|---|---|---|---|
/Departments/Index | DepartmentsController | Index() | Views/Departments/Index.cshtml |
/Departments | 同上(Index は既定アクション) | 同上 | 同上 |
/Departments でアクセスすると Index が呼ばれる仕組みは第 26 章 26-6 で学んだ規約どおりです。
27-8 View を作る:部署一覧画面
Section titled “27-8 View を作る:部署一覧画面”まず Views フォルダの中に Departments フォルダ を作ります(Controller 名と同じ名前)。
その中に Razor ビュー - 空 で Index.cshtml を追加します。
@model List<Ch27_MvcDepartmentList.Models.Department>
@{ ViewData["Title"] = "部署一覧";}
<h1>部署一覧</h1>
<p>@Model.Count 件の部署が登録されています。</p>
<table class="table"> <thead> <tr> <th>部署ID</th> <th>部署名</th> <th>マネージャーID</th> </tr> </thead> <tbody> @foreach (Ch27_MvcDepartmentList.Models.Department dept in Model) { <tr> <td>@dept.DepartmentId</td> <td>@dept.DepartmentName</td> <td> @if (dept.ManagerId.HasValue) { @dept.ManagerId.Value } else { <span class="text-muted">未設定</span> } </td> </tr> } </tbody></table>各部分のポイント:
@model List<...>
Section titled “@model List<...>”第 26 章では @model FortuneViewModel(単一の Model)でしたが、今回は List<Department> を受け取ります。
これで View 内の Model が List<Department> 型として扱えるようになります。
@Model.Count
Section titled “@Model.Count”List<T>.Count プロパティ(第 12 章)。Razor の中で C# のプロパティをそのまま呼べます。
@foreach
Section titled “@foreach”@foreach (Ch27_MvcDepartmentList.Models.Department dept in Model){ <tr>...</tr>}Razor の @foreach は HTML の中に C# の繰り返しを書く方法です(第 5 章の foreach と同じ書き方)。
中括弧の中の HTML が、リストの件数分だけ繰り返されます。
@if で NULL を別表示
Section titled “@if で NULL を別表示”@if (dept.ManagerId.HasValue){ @dept.ManagerId.Value}else{ <span class="text-muted">未設定</span>}ManagerId が null のとき、そのまま @dept.ManagerId を書くと空欄になるか、HasValue == false を直接表示してしまいます。
ここでは 第 26 章でも使った @if で、NULL の場合は「未設定」と表示しています。
@model・@foreach・@if・class="..."の役割は早見表にこれらの「決まり書き(おまじない)」の役割は、第 26 章 26-12「MVC のおまじない早見表」 にまとめてあります。迷ったらそこへ戻ってください。
class="table"やtext-mutedは Bootstrap の見た目クラスで、付けると表が見やすくなる だけです(中身は気にしなくて OK)。
27-9 動かしてみる
Section titled “27-9 動かしてみる”Controller(27-7)と View(27-8)までそろったので、いよいよ実行します(これが先ほどのビルド確認に続く 2 回目=動作確認です)。 F5 で実行し、ブラウザのアドレスバーに次の URL を入れて開いてください。
https://localhost:xxxx/Departmentsxxxx は環境によって違います。次のような画面が表示されれば成功です。
部署一覧
5 件の部署が登録されています。
| 部署ID | 部署名 | マネージャーID ||--------|---------------|----------------|| 1 | 総務 | 未設定 || 2 | 営業 | 未設定 || 3 | 開発 | 未設定 || 4 | マーケティング | 未設定 || 5 | 品質管理 | 未設定 |マネージャーIDが 未設定 になるのは、departments テーブルの manager_id 列がまだ全行 NULL のためです(第 16 章でサンプルデータを INSERT する際に NULL のまま投入しています)。
処理の流れを振り返る(ブラウザ → Controller → Repository → DB → View)
Section titled “処理の流れを振り返る(ブラウザ → Controller → Repository → DB → View)”いま動かしたアプリで、ブラウザの 1 リクエストがどう処理されて画面に戻ってくるかを、時系列で並べると次のようになります。
- Controller は「Repository を呼んで、返ってきたリストを View に渡す」だけ(27-7)
- Repository は DB に SQL を投げ、
reader.Read()のループで 1 行ずつDepartmentに詰めて返す(27-6) - View は受け取ったリストを
@foreachで<table>に展開する(27-8)
「DB アクセスは Repository の中だけ」「Controller は流れを決めるだけ」「View は表示だけ」── この役割分担が、1 本のリクエストの流れの中でどう分かれているかを意識すると、第 28 章以降の社員アプリも同じ目で読めます。
おすすめ:第 21 章のデバッグ操作で観察
DepartmentsController.Index()の最初の行と、DepartmentRepository.GetAll()のwhile (reader.Read())の中にブレークポイントを置いて F5 で実行すると、Controller → Repository → SqlDataReader の流れが目で見えます。
27-10 Windows フォーム編との比較
Section titled “27-10 Windows フォーム編との比較”第 23 章(Windows フォーム社員管理アプリ:一覧)と、この章の構造を並べてみます。
| 観点 | Windows フォーム(第 23 章) | Web MVC(この章) |
|---|---|---|
| Repository | EmployeeRepository.GetAll() | DepartmentRepository.GetAll() |
| Repository の呼び出し場所 | Form_Load イベント | Controller.Index() アクション |
| 取得結果の渡し先 | DataGridView.DataSource = list; | return View(list); で View へ |
| 画面の作り方 | フォームデザイナで DataGridView を配置 | .cshtml で <table> と @foreach を書く |
| 接続文字列 | Integrated Security=true | User Id=app_user;Password=...; |
| 画面の更新方法 | コントロールのプロパティ更新 | HTML 全体を返してブラウザがレンダリング |
Repository の中身(SqlConnection・SqlDataReader)はほぼ同じ です。違うのは:
- 呼び出し元:Form のイベント → Controller のアクション
- データの渡し方:DataGridView の DataSource → View に渡す
- 画面の組み立て:フォームデザイナ → HTML + Razor
「DB アクセス層を分けておくと、上の層(Windows / Web)を変えやすい」 ── これが Repository パターンの大きなメリットです。
27-11 補足:現場のお作法(appsettings.json と DI)
Section titled “27-11 補足:現場のお作法(appsettings.json と DI)”本文では シンプルさを優先 して、次のように書きました。
- 接続文字列を Repository の中に
private const stringで持つ - Controller で
new DepartmentRepository()する
研修ではこの形で進めて十分ですが、現場の Web アプリ では別のお作法が広く使われています。本節で概要を見ておくと、業務で出会ったときに「これか」と気付けます。
お作法 ①:接続文字列を appsettings.json に置く
Section titled “お作法 ①:接続文字列を appsettings.json に置く”ソースコードにパスワードを書くと、Git に上げたときに丸見えになります。
そこで、appsettings.json(設定ファイル)に書いて、コードからは読み出すのが定石です。
appsettings.json:
{ "ConnectionStrings": { "TrainingDb": "Server=localhost;Database=TrainingDB;User Id=app_user;Password=AppUserPass1!;TrustServerCertificate=true;" }, "Logging": { ... }, "AllowedHosts": "*"}Repository は IConfiguration を受け取って、そこから接続文字列を取り出します。
public class DepartmentRepository{ private readonly string connectionString;
public DepartmentRepository(IConfiguration configuration) { connectionString = configuration.GetConnectionString("TrainingDb"); } // ... GetAll() は同じ}本番運用ではさらに
本番環境では
appsettings.jsonにもパスワードを書かず、環境変数や Azure Key Vault、AWS Secrets Manager などのシークレット管理サービスから読み込むのが推奨です。
お作法 ②:DI(依存性注入)で Repository を渡す
Section titled “お作法 ②:DI(依存性注入)で Repository を渡す”new DepartmentRepository() の代わりに、ASP.NET Core の DI コンテナ に Repository を登録しておくと、Controller のコンストラクタで自動的に受け取れます。
Program.cs(抜粋):
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllersWithViews();builder.Services.AddScoped<DepartmentRepository>(); // ← 追加Controller 側:
public class DepartmentsController : Controller{ private readonly DepartmentRepository repository;
public DepartmentsController(DepartmentRepository repository) { this.repository = repository; // DI コンテナが自動で渡してくれる }
public IActionResult Index() { List<Department> departments = repository.GetAll(); return View(departments); }}| キーワード | 意味 |
|---|---|
| DI(Dependency Injection、依存性注入) | 必要なオブジェクトを「外から渡してもらう」考え方 |
AddScoped | リクエスト 1 回ごとに 1 つインスタンスを作る登録方法 |
| コンストラクタの引数 | DI コンテナが自動で対応する型を渡してくれる |
DI を使うと、テスト時に Repository を モック(偽物) に差し替えやすくなる、というメリットがあります。
この研修で DI を使わなかった理由
Section titled “この研修で DI を使わなかった理由”- Windows フォーム編で
new EmployeeRepository()だったので、書き方を揃えて頭の負荷を下げる - 「必要なオブジェクトを自分で
newする」のは C# として素直で読みやすい - DI は 概念を理解してからでないとデバッグが難しい(エラーが「登録忘れ」など独特な形で出る)
業務で appsettings.json + DI のコードを見かけたら、「ここに接続文字列があるんだな」「ここで Repository を受け取っているんだな」と読み解ければ十分です。
よくあるつまずき
Section titled “よくあるつまずき”| つまずき | 原因 | 対応 |
|---|---|---|
Login failed for user 'app_user' | パスワード違い、SQL 認証モードが無効 | 27-2 手順 1〜2 を再確認 |
Cannot open database "TrainingDB" | app_user が TrainingDB のユーザーになっていない | 27-2 手順 2 の CREATE USER ... FOR LOGIN ... を実行 |
SELECT permission was denied | db_datareader ロール未付与 | ALTER ROLE db_datareader ADD MEMBER app_user; を再実行 |
/Departments で 404 | コントローラー名が違う / View ファイルが無い | DepartmentsController + Views/Departments/Index.cshtml を確認 |
ビルドエラー Microsoft.Data.SqlClient 未解決 | NuGet 未追加 | 27-3「NuGet で Microsoft.Data.SqlClient を追加する」を実施 |
SqlNullValueException | NULL の列で GetInt32 を呼んだ | IsDBNull(列インデックス) で先にチェック |
@foreach の型が解決できない | @model の宣言ミス | View 先頭の @model List<Ch27_MvcDepartmentList.Models.Department> を確認 |
| 一覧が空っぽで表示される | departments テーブルが空 | SSMS で SELECT * FROM departments を実行して中身を確認(第 16 章) |
| ブラウザに古い画面が出続ける | キャッシュ | Ctrl + F5 でリロード、または「停止 → F5」で再起動 |
学んだことチェック
Section titled “学んだことチェック”- SQLServer で SQL 認証ユーザーを作成し、
TrainingDBへの読み書き権限を付与できる - Windows 統合認証と SQL 認証の使い分け(Windows / Web)を説明できる
- ASP.NET Core MVC プロジェクトに
Microsoft.Data.SqlClientを NuGet で追加できる - Repository パターンの役割(Controller / Repository / Model の分担)を説明できる
-
using SqlConnection、SqlCommand、SqlDataReaderを使った基本パターンを書ける -
while (reader.Read())が結果を 1 行ずつ取り出すループだと説明できる -
IsDBNullで NULL を判定し、int?プロパティにマッピングできる - Controller から Repository を呼び、結果を
View(model)で渡せる - ブラウザ → Controller → Repository → DB → View の一連の流れを説明できる
- View で
@model List<...>、@foreach、@ifを使った一覧表示が書ける - NULL のときに代替表示(例:「未設定」)を出せる
- 「現場では appsettings.json + DI を使うことが多い」ことを概要レベルで知っている
研修の進め方によっては、隣の人またはチーム内で説明確認を行います。
次の内容を、自分の言葉で説明してください。
- なぜ Web アプリでは Windows 統合認証ではなく SQL 認証を使うのですか。
app_userを作るときに「ログイン」と「ユーザー」を別々に作っているのはなぜですか。- Repository パターンとは何ですか。Controller と Repository の役割の違いを述べてください。
IsDBNullを呼ばずにGetInt32した場合、何が起きますか。- View の
@model List<Department>を書かないと、@foreachで何が起きますか。 - 「
appsettings.json+ DI」を採用すると、本文の書き方と比べて何が変わりますか。 - Windows フォーム編(第 23 章)と、この章の Web 編で、Repository の中身はどこが違いますか。
この章の演習課題に取り組みます。
本章では、第 4〜15 章のコンソール課題と同じ タイマー方式 で進めます。次の 3 段階で取り組んでください。
| 段階 | 時間 | 内容 |
|---|---|---|
| ① 準備 | 10 分(目安) | 上の「ペア確認」と、必須課題(仕様)の読み込み。ペアや講師に質問してよい |
| ② ソロ作業 | 30 分(タイマーで計測) | 一人で必須課題に取り組む。タイマーが鳴ったら、完成・未完成にかかわらず作業を止めて提出する |
| ③ チーム時間 | 講師が指定する発表開始時刻まで | チーム内で 教え合い(分かった人が詰まった人に説明)とコードレビューを行い、発表者を決める。発展課題や実装の続行も可。時間配分はチームで管理する |
評価対象はタイマー時点で提出されたコードです(タイマー後に書き足した分は評価には含まれません)。 発表開始時刻は厳守 です。チーム時間中も、その時刻が来たら全員手を止めて発表に移ります。
必須課題の土台は先に作っておきます
必須課題は「本文 27-3〜27-9 で完成させた部署一覧アプリに マネージャー名表示を追加する」課題です。 部署一覧アプリ本体(
Ch27_MvcDepartmentList相当)は本文どおり コピーで作って構いません(ここは評価の主眼ではありません)。 ソロ作業のタイマーで測るのは、そこに自力で足すマネージャー名の JOIN 実装 です。本文には完成コードがないので、ヒントを見ながら自分で組み立てます。
演習の進め方の詳細は、付録A「演習の進め方」 を参照してください。
この章の演習の進め方
Section titled “この章の演習の進め方”課題はソリューション Kadai27 の中に作成してください。課題ごとに別のプロジェクトを作成 し、指定されたプロジェクト名を使います(基本は本文と同じ TrainingDB を使います)。
| 課題 | 必須/発展 | プロジェクト名 | 内容 |
|---|---|---|---|
| 課題27-1 | 必須 | Kd27_01_MvcDepartmentList | 部署一覧 + マネージャー名表示(JOIN) |
| 課題27-2 | 発展 | Kd27_02_DepartmentListCustom | 一覧の改造(並び順・件数表示・行の色) |
| 課題27-3 | 発展 | Kd27_03_DepartmentListDi | appsettings.json + DI に書き換え |
提出ルール(タイマー方式)
30 分のタイマーが鳴ったら手を止め、
git add→commit→pushを行います。評価対象はタイマー時点のコミットのみです(タイマー後の追加は評価外)。Git が使えないときは付録 C のコピー提出に従い、提出メモ.txtに「どこまで完成 / 詰まったポイント」を書きます。コミットメッセージの形式:
Chapter27 タイマー提出: <どこまで完成> / <詰まったポイント>例:
Chapter27 タイマー提出: マネージャー名のJOINまで完成 / 列インデックスのずれで詰まった
タイマー後のチーム時間
発表開始時刻まで、教え合い(分かった人が詰まった人に説明)/ コードレビュー / 発表者選出 / 発展課題や実装続行(任意・評価外)。時間配分はチームの判断、発表開始時刻は厳守。
課題 27-1 部署一覧にマネージャー名を表示する
Section titled “課題 27-1 部署一覧にマネージャー名を表示する”本文 27-3 〜 27-9 にそって Ch27_MvcDepartmentList(部署一覧アプリ)を完成させたら、その一覧に 各部署のマネージャー名 を表示できるようにします。
departments テーブルの manager_id を employees の主キーに紐付け(JOIN)、マネージャーの姓名を画面に出します。ここがソロ作業(タイマー)の中心 です。本文に完成コードはないので、下の手順とヒントを手がかりに 自力で 実装してください。
前提(タイマー前・コピーOK)
- 本文どおり部署一覧アプリが動いている(
/Departmentsで 5 件表示)
ソロ作業でやること
-
SSMS で
departmentsの数件にマネージャーを設定する(データの用意)USE TrainingDB;UPDATE departments SET manager_id = 1006 WHERE department_id = 1;UPDATE departments SET manager_id = 1002 WHERE department_id = 2; -
DepartmentRepository.GetAllの SQL をLEFT JOINで書き換え、マネージャーの姓名を取得するSELECT d.department_id,d.department_name,d.manager_id,e.last_name + ' ' + e.first_name AS manager_nameFROM departments dLEFT JOIN employees e ON d.manager_id = e.employee_idORDER BY d.department_id -
DepartmentクラスにManagerNameプロパティを追加する -
View で「マネージャーID」列の隣に マネージャー名 を表示。
NULL(未設定)のときは「未設定」と出す
ヒント:列インデックスがずれる点に注意
本文の Repository は
reader.GetInt32(0)・reader.GetString(1)・reader.IsDBNull(2)のように 列番号 で値を取り出しています。 SQL にmanager_name列を追加すると、4 列目(インデックス3)になります。reader.IsDBNull(3) ? null : reader.GetString(3)を追記してManagerNameに代入してください。 列を追加した際にインデックスを更新し忘れると、「動くけど結果がおかしい」という気付きにくいバグになります。第 23 章のようにreader.GetOrdinal("manager_name")で 列名指定 にすると、列順が変わっても壊れません(27-6 補足参照)。余裕があれば列名指定でも書いてみましょう。
確認すること
- マネージャーを設定した部署で、姓名が画面に表示される
- マネージャー未設定の部署では「未設定」と表示される
-
LEFT JOINとINNER JOINの違いを自分の言葉で説明できる - 第 23 章の
EmployeeRepositoryでも同じ JOIN パターンを使ったことを思い出せる - タイマー時点で commit(または
提出メモ.txt)を済ませた
発展課題はチーム時間に取り組みます(評価対象外・任意)。Kd27_02_DepartmentListCustom / Kd27_03_DepartmentListDi として、必須課題のプロジェクトをコピーして実験すると安全です。
課題 27-2 一覧の見た目を改造する
Section titled “課題 27-2 一覧の見た目を改造する”部署一覧の表示を、下から 1 つ以上 選んで改造してみましょう。
| 選択肢 | 内容 | ヒント |
|---|---|---|
| A:並び順を逆にする | 部署IDの降順で表示 | SQL の ORDER BY department_id DESC |
| B:件数表示の文言を変える | 「全 N 部署を取り扱っています」のように変更 | View の @Model.Count 行を修正 |
| C:行に背景色を交互につける | 偶数行と奇数行で色を変える | @foreach 内でインデックスを使う(Model.Select((d, i) => ...))か、CSS の tr:nth-child(even) |
確認すること
- 選んだ改造が正しく動く
- 改造を入れた場所(Controller / Repository / View のどれか)を自分で指せる
- 改造によって動作がどう変わったかを説明できる
課題 27-3 appsettings.json と DI に書き換えてみる
Section titled “課題 27-3 appsettings.json と DI に書き換えてみる”本文 27-11 の「お作法 ①・②」を実際に適用してみましょう。
手順イメージ
appsettings.jsonの"ConnectionStrings"セクションにTrainingDbを追加するDepartmentRepositoryのコンストラクタをIConfiguration受け取りに変えるProgram.csにbuilder.Services.AddScoped<DepartmentRepository>();を追加DepartmentsControllerのコンストラクタでDepartmentRepositoryを受け取る形に書き換える- ブラウザで
/Departmentsが変わらず動くことを確認
確認すること
- 接続文字列がソースコードから消え、
appsettings.jsonだけに残っている - Controller に
new DepartmentRepository(...)が登場しなくなった -
Program.csのAddScopedを消すと、画面アクセス時にエラーになることを観察した - 「DI なし」「DI あり」の書き方を比較して、それぞれの長所を 1 つずつ挙げられる
提出前チェックリスト
Section titled “提出前チェックリスト”- ソリューション名が
Chapter27、プロジェクト名がCh27_MvcDepartmentList - csproj が
<Nullable>disable</Nullable> -
Microsoft.Data.SqlClientが NuGet で追加されている - SQL 認証ユーザー
app_userでTrainingDBに接続できる(SSMS で確認済み) -
Models/Department.csが作成されている -
Data/DepartmentRepository.csが作成されている -
Controllers/DepartmentsController.csのIndexで Repository を呼んでいる -
Views/Departments/Index.cshtmlが作成されている -
/Departmentsにアクセスすると 5 件の部署が表示される - 課題 27-1 でマネージャー名が表示される(未設定の部署は「未設定」)
- タイマー時点で commit(または
提出メモ.txt)を済ませた - (発展 27-2 を実施した場合)一覧の改造が動く
- (発展 27-3 を実施した場合)
appsettings.json+ DI に書き換わっている -
bin・obj・.vsフォルダが Git 管理に入っていない -
app_userのパスワードを本物の機密情報に変えていない(練習用のAppUserPass1!のまま)
Git への提出
Section titled “Git への提出”git statusgit add .git commit -m "Chapter27 タイマー提出: <どこまで完成> / <詰まったポイント>"git push origin mainGit の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。
この章のまとめ
Section titled “この章のまとめ”- Web アプリは SQL 認証 で DB に接続する(Windows フォームは Windows 統合認証)
- SQL 認証には ログイン + データベースユーザー + ロール(権限) の 3 段階の設定が必要
Microsoft.Data.SqlClientを NuGet で追加すれば、Windows フォーム編と同じSqlConnection/SqlCommand/SqlDataReaderが使える- Repository パターン で DB アクセスを Controller から分離する(Windows フォーム編と同じ考え方)
- Controller は「Repository を呼んで View に渡す」だけのシンプルなコードに保てる
- View では
@model List<...>、@foreach、@ifで表形式の一覧画面を組み立てられる - 現場では
appsettings.jsonで接続文字列を管理し、DI(依存性注入) で Repository を Controller に渡すパターンが広く使われる - 本研修では学習負荷を抑えるため
newで生成しているが、業務で DI に出会ったときに読み解ける状態を目指す
次章では、いよいよ Web 版 社員管理アプリ の本体に入ります。 題材は 社員一覧表示 ── 第 23 章で作った Windows 版社員一覧の Web 版 です。
employees テーブルから JOIN で部署名も取得し、DataGridView の代わりに HTML の <table> で表示します。
この章で作った Repository パターンと、Web アプリの基本(Controller・View)がそのまま土台になります。
第 28 章以降は、第 23〜25 章の Windows 版社員管理アプリと 同じ機能を Web で再構築する 流れになります。Windows 版とコードを見比べながら進めると、「同じ業務をプラットフォームに合わせてどう実装するか」の感覚がつかめます。