Skip to content

付録F 発展課題集(本編を走り切った人向け)

この付録は、本編(第 0〜31 章)を最後まで走り切った人 のために、もう一段深いテーマを集めたものです。

研修期間中に余裕のある人、早く終わった人、配属先で必要になりそうな技術を先取りしたい人が手を伸ばすための、任意の課題集 として用意しています。

観点本編(第 1〜31 章)本付録 F
提出必須任意(やった人だけ提出)
完全なコード掲載あり(本文に手順あり)なし(仕様・ヒント・骨格コードのみ)
つまずいたとき本文に答えがある自分で調べる前提(StackOverflow、公式ドキュメント)
質問の仕方講師に「分かりません」で OK「ここまで調べました、ここで詰まっています」を整理してから

本編は「正しい型を覚える」場でした。発展課題は「自分で型を選び取る」場です。やり方は 1 つではありません。動くものができたら、最初の答えとは違う方向に作り直してみてください。


本付録には完全なコードを載せません。掲載しているのは:

  • 仕様(何を作るか)
  • ヒント(詰まりやすい箇所への指針)
  • 骨格コード(メソッドシグネチャ、SQL の雛形、画面項目)

だけです。本編のように「コピペで動く」状態にはしていません。自分で組み立てる練習 のための場だと割り切ってください。

1 課題 = 1 ソリューション or 1 ブランチ

Section titled “1 課題 = 1 ソリューション or 1 ブランチ”

複数の発展課題に手を出す場合、本編のソリューションを上書きせずに別物として残す ことを推奨します。 理由は本編と同じで、「前の到達点を消さない」ためです。

やり方
ソリューションを分けるKadai25 とは別に KadaiF_03_01_BulkSalaryUp を作る
Git ブランチを分けるfeature/appendix-f-bulksalary のように切る
プロジェクトを追加するKadaiF ソリューションを 1 つ作り、各課題を KdF_XX_YY_* プロジェクトで追加

ファイル命名は本編に厳密に揃える必要はありません。自分が後で見直したとき判別できる こと、講師にレビューを頼むときに位置が指せる ことを優先してください。

1 周目は 動かすこと を最優先にしてください。エラーハンドリングや見た目の調整は後回しで構いません。 動くものができたら、次の観点で 1〜2 周目を回すと現場感に近づきます。

  • パラメータ化クエリになっているか(SQL インジェクション対策)
  • 例外処理が抜けていないか(SqlException のキャッチ、MessageBox / Razor での表示)
  • 確認ダイアログ・PRG パターン・ModelState が省略されていないか
  • 文字列補間、var の使い分け、using 宣言が雑になっていないか
  • 重複コードを Repository に寄せられないか

本付録の課題で詰まったときは、講師に丸投げせず、次の 3 点をメモしてから 相談してください。

  1. 何をしようとしているか(課題と狙い)
  2. どこまで動いているか(画面ショット or 入力したコード)
  3. 何が起きているか(エラーメッセージ、期待した動きと実際の差)

これは配属後にも効く、現場の質問の型 です。


本付録は、本編の到達点(第 25 章 Win CRUD / 第 30 章 Web CRUD)を 下地として再利用 します。 始める前に、次のいずれかが手元にあることを確認してください。

必要なもの用途
Chapter25 ソリューション(Win CRUD)F-3 系の出発点第 25 章
Chapter30 ソリューション(Web CRUD)F-4 系の出発点第 30 章
Chapter26 ソリューション(占いアプリ)F-5 系の出発点第 26 章
TrainingDB(SQLServer)全 DB 系課題第 16 章
第 17 章で接続できた状態全 DB 系課題第 17 章

F-3〜F-5 はそれぞれ独立しているので、どこから手を付けても構いません。


F-3 Windows フォーム編 発展課題

Section titled “F-3 Windows フォーム編 発展課題”

第 23〜25 章の社員管理アプリ(Chapter25)を出発点にした発展課題です。

F-3-1 部署一括の給与アップ(トランザクション付き)

Section titled “F-3-1 部署一括の給与アップ(トランザクション付き)”
  • フォームに 「選択部署を一律 1 万円アップ」 ボタンを追加
  • 部署プルダウン(本編で使ったもの)で部署を選択
  • 「(すべての部署)」が選択されているときは、確認ダイアログで 強く警告(「全社員に適用されます」)
  • 実行は トランザクション で行い、想定外の例外が起きたらロールバック
  • 完了後、影響行数を MessageBox で表示
  • EmployeeRepositoryBulkSalaryUp(int departmentId) を追加(部署 = -1 のとき WHERE を外す)
  • トランザクションは SqlConnection.BeginTransaction()SqlCommand.Transaction = tx を必ずセット
  • tryCommit()catchRollback() → 例外は throw で呼び元に伝える
  • 「自分が今書いている SQL を一度 SSMS で打ってみる」と頭が整理される

骨格コード(EmployeeRepository に追加)

Section titled “骨格コード(EmployeeRepository に追加)”
public int BulkSalaryUp(int departmentId, decimal amount)
{
using SqlConnection conn = new SqlConnection(_connectionString);
conn.Open();
using SqlTransaction tx = conn.BeginTransaction();
try
{
// TODO: departmentId == -1 のときは WHERE 句を外す
// TODO: パラメータ化クエリで UPDATE
// TODO: tx.Commit();
// return 影響行数;
}
catch
{
tx.Rollback();
throw;
}
}
  • BeginTransaction / Commit / Rollback が組になっている
  • cmd.Transaction = tx; を忘れていない
  • 「(すべての部署)」時の確認文言が他の確認より明らかに強い
  • 影響行数を画面で確認できる

F-3-2 社員一覧の CSV エクスポート

Section titled “F-3-2 社員一覧の CSV エクスポート”
  • フォームに「CSV 出力」ボタンを追加
  • 押すと SaveFileDialog でファイル名を選ばせ、現在の DataGridView の内容を CSV で書き出す
  • 文字コードは UTF-8 with BOM(Excel で文字化けさせないため)
  • 列順は画面と一致、ヘッダー行を 1 行目に出す
  • カンマ・改行・ダブルクォートを含む値は、ダブルクォートで囲んで内部のダブルクォートをエスケープ(""")
  • 第 22 章の StreamWriter + 自前 CSV 整形の延長
  • using var writer = new StreamWriter(path, false, new UTF8Encoding(true)); で BOM 付き
  • 「カンマ / 改行 / ダブルクォートのいずれかを含む」値は引用するロジックを 1 メソッドに切り出す:
private static string Escape(string value)
{
if (value == null) return "";
bool needsQuote = value.Contains(',') || value.Contains('"') || value.Contains('\n');
string escaped = value.Replace("\"", "\"\"");
return needsQuote ? $"\"{escaped}\"" : escaped;
}
  • 出力対象は dataGridViewEmployees.DataSourceList<Employee> にキャストして取り出すと簡単
  • Excel で開いて文字化けしない
  • 検索結果のみがエクスポートされる(全件ではない)
  • 1 行目がヘッダー
  • カンマを含む last_name を作って試して、列がずれない

  • メインフォームに「部署マスタ」ボタンを追加
  • 押すと DepartmentMasterForm がモーダルで開き、departments テーブルの CRUD ができる
  • 画面構成:DataGridView + 編集ダイアログ(社員編集と同じパターン)
  • 注意点:外部キーで参照されている部署を削除しようとすると SqlException(Number=547)が出るMessageBox でユーザーに知らせる
  • 第 25 章の EmployeeRepository パターンをそのまま DepartmentRepository に複製
  • departmentsmanager_id という列があるので、編集画面では manager_id をどう入力させるか考える(プルダウン? 数値入力?)
  • 部署マスタを更新したら、メインフォームの部署プルダウンの再読み込みを忘れない(Form_Activated で再読み込み or 戻り値で判定)
  • 部署の追加・編集・削除がパラメータ化クエリで動く
  • 参照されている部署を削除しようとしたとき、SqlException.Number == 547 をキャッチして分かりやすいメッセージを出す
  • メインフォームに戻ったとき、部署プルダウンが最新になる

第 27〜30 章の Web 社員管理アプリ(Chapter30)を出発点にした発展課題です。

  • 1 ページ 10 件で /Employees?page=1 のように指定できる
  • 画面下部に「前へ / 1 / 2 / 3 / 次へ」のページャを出す
  • 現在ページは強調、page が範囲外なら 1 ページ目に丸める
  • 検索条件と併用できる(/Employees?keyword=山&page=2)
  • SQL は SQLServer 2012+ の OFFSET ... FETCH NEXT ... ROWS ONLY が定番
SELECT ...
FROM employees
ORDER BY employee_id
OFFSET @skip ROWS
FETCH NEXT @take ROWS ONLY
  • 総件数は別途 SELECT COUNT(*) で取得(ExecuteScalar)
  • EmployeeSearchViewModelPagePageCountTotalCount を追加
  • ページャの View は for (int p = 1; p <= Model.PageCount; p++) { ... } の繰り返し
  • ?page=0?page=999 でも崩れない
  • 検索条件と併用できる(リンクに keyword も含めている)
  • ORDER BY を必ず付ける(OFFSET は ORDER BY が無いと SQLServer がエラーを返す)

F-4-2 列ヘッダクリックでソート

Section titled “F-4-2 列ヘッダクリックでソート”
  • <th> をクリックすると、その列で昇順 → クリックするたびに降順 → 昇順 と切り替わる
  • URL 例:/Employees?sort=salary&desc=true&page=1
  • ソート列名は ホワイトリストで検証(任意の文字列を直接 SQL に挿入しない ← SQL インジェクション対策)
  • ソート対象は string sortColumn で受け取り、Repository 内で許可リストと突き合わせる
private static readonly HashSet<string> AllowedSortColumns = new()
{
"employee_id", "last_name", "first_name", "salary", "hire_date"
};
  • 一覧 <th><a href> で sort パラメータを切り替える
  • ページングと組み合わせる場合、ソート変更時は page=1 にリセットしたほうがユーザーに優しい
  • 許可リスト外の列名を URL に入れても、エラーや SQL インジェクションが起きない
  • 同じ列を 2 回クリックすると昇順 ⇄ 降順が入れ替わる
  • ページングと両立する(F-4-1 をやっている場合)

  • /Employees/Csv現在の検索条件に一致する全件 を CSV で返す
  • Content-Type は text/csv、ファイル名は employees_yyyyMMdd_HHmmss.csv
  • 一覧画面のボタン「CSV ダウンロード」から飛ぶ
  • Controller のアクションは FileContentResult を返すと簡単
public IActionResult Csv(string keyword, int departmentId)
{
List<Employee> list = _employeeRepository.Search(keyword ?? "", departmentId);
byte[] bytes = ...; // UTF-8 with BOM で組み立てた CSV
string fileName = $"employees_{DateTime.Now:yyyyMMdd_HHmmss}.csv";
return File(bytes, "text/csv", fileName);
}
  • StringBuilder で組み立てて Encoding.UTF8.GetBytes(...) でバイト列に
  • BOM を入れるには先頭に 0xEF, 0xBB, 0xBF を書き出す(new UTF8Encoding(true).GetPreamble())
  • F-3-2 の Escape ロジックは Web 版でもそのまま流用できる
  • ダウンロードしたファイルを Excel で開いて文字化けしない
  • 検索条件が反映されている(URL のクエリ文字列を Controller で受け取れている)
  • ファイル名にタイムスタンプが入っている

第 26 章の占いアプリ(Chapter26)を出発点にした発展課題です。

  • 結果画面に「あなたの干支」と「あなたの星座」を追加表示
  • 干支:生年から計算((year - 4) % 12)
  • 星座:生月日から判定(12 種類、境界日に注意)
  • ロジックは Models/FortuneCalculator.cs のような ロジック専用クラス に切り出す
  • 干支は string[] zodiac = { "子", "丑", "寅", "卯", "辰", "巳", "午", "未", "申", "酉", "戌", "亥" };zodiac[(year - 4) % 12]
  • 星座は「月日 → 星座名」を返すメソッドを 1 つ書く。境界の月日(例:3 月 21 日 → 牡羊座)に注意
  • Controller・View はロジックを持たない。FortuneCalculator.GetChineseZodiac(year) のような呼び方に揃える
  • 生年月日に対して干支・星座が正しく出る
  • 境界(例:4/19 と 4/20)で星座が切り替わる
  • Controller が if 文の塊にならず、ロジッククラスに切り出されている

  • 結果画面に「あなたが生まれてから今日まで:○○日」を表示
  • 計算は (DateTime.Today - HireDate).Days のような単純な引き算
  • ついでに「○○時間 / ○○分」も表示できる(TimeSpan.TotalHours / TotalMinutes)
  • TimeSpanDaysTotalHoursTotalMinutesTotalSeconds の違いを確認しておく
  • 未来の日付を入れたときに負の数を出さないよう、入力チェックも添える
  • 自分の生年月日で計算して、感覚と一致する
  • 未来の日付を入れたときの挙動が「分かりやすい」(エラーメッセージ or 0 で固定)

F-5-3 決定論的ランダム(同じ人は同じ結果)

Section titled “F-5-3 決定論的ランダム(同じ人は同じ結果)”
  • 現在は 押すたびに結果が変わる(new Random() の既定挙動)
  • これを「同じ人(同じ名前 + 生年月日)は、同じ日にやれば同じ結果」に変える
  • 翌日になったら結果が変わる(「今日の運勢」っぽくなる)
  • Random(seed) のコンストラクタにシード値を渡すと、同じシードからは同じ乱数列が得られる
  • シード値は「名前のハッシュ + 生年月日の Ticks + 今日の Ticks」を合算するなど
int seed = (name + birthDate.ToString("yyyyMMdd") + DateTime.Today.ToString("yyyyMMdd")).GetHashCode();
Random random = new Random(seed);
  • ハッシュ値は実行ごとに変わる(同一プロセス内では安定)ことに注意。本格運用するなら SHA256 をバイト列にして int に丸める のが安定する
  • 同じ入力を 5 回繰り返して、すべて同じ結果になる
  • 違う名前を入れると、結果が違う
  • 翌日になると結果が変わる(時刻を変えて確認するか、シードに今日を入れていることを目視確認)

F-6 横断テーマ(現場で出会うもの)

Section titled “F-6 横断テーマ(現場で出会うもの)”

ここからは「Win か Web かに依らず、現場で出会う技術」をいくつか紹介します。 本編では研修負荷を抑えるために省略してきたものを、ここで体験できます。

  • 第 27〜30 章で const string ConnectionString = "..." と直書きしていた接続文字列を、appsettings.json から読み込む形に変える
  • パスワードを コードからソース管理外に追い出す ことが目的
  • Web プロジェクトには既に appsettings.json がある。ConnectionStrings セクションを追加:
{
"ConnectionStrings": {
"TrainingDB": "Server=localhost;Database=TrainingDB;User Id=app_user;Password=...;TrustServerCertificate=true;"
}
}
  • Program.csbuilder.Configuration.GetConnectionString("TrainingDB") で読める
  • 本来は DI コンテナで Repository に渡す(F-6-2 と合わせるとキレイ)が、まずは Program.cs で取って EmployeeRepository.ConnectionString の static フィールドに入れるだけでも体験になる
  • 本番運用では appsettings.Development.jsonユーザーシークレット で開発者ごとに上書きする
  • appsettings.json を変更すると、再起動だけで接続先が切り替わる
  • .gitignore で本物の接続文字列を含むファイルが Git に入らないようにする(本物のパスワードはコミットしない)

F-6-2 DI コンテナで Repository を渡す

Section titled “F-6-2 DI コンテナで Repository を渡す”
  • 第 27〜30 章では new EmployeeRepository() を Controller の中で 直接 new していた
  • これを ASP.NET Core の DI コンテナ に登録し、Controller のコンストラクタで受け取る形に変える
  • Program.cs で:
builder.Services.AddScoped<EmployeeRepository>();
builder.Services.AddScoped<DepartmentRepository>();
  • Controller を:
public class EmployeesController : Controller
{
private readonly EmployeeRepository _employeeRepository;
private readonly DepartmentRepository _departmentRepository;
public EmployeesController(EmployeeRepository employeeRepository, DepartmentRepository departmentRepository)
{
_employeeRepository = employeeRepository;
_departmentRepository = departmentRepository;
}
// ...
}
  • F-6-1 と組み合わせて、Repository が IConfiguration から接続文字列を受け取る形にすると、業務 Web アプリの典型構成になる
  • 第 27 章 27-11 で紹介した「現場のお作法」を、自分の手で実装することになる
  • Controller から new EmployeeRepository() が消えている
  • 接続文字列が appsettings.json から読まれている(F-6-1 とセット)
  • Repository をコンストラクタで差し替えられる(=単体テストを書くときに差し込める)構造になっている

テストフレームワーク(xUnit など)について

本来であれば、ここに「xUnit で Repository をテストする」課題を置きたいところです。ただし、xUnit テストプロジェクトを追加すると xunit / xunit.runner.visualstudio / Microsoft.NET.Test.Sdk などの NuGet パッケージが自動で取り込まれます。

研修環境では 追加 NuGet パッケージは事前に管理者の検査・申請が必要 なため、本付録ではテスト導入の課題は外しています。配属後、自分のチームで使えるテストフレームワーク(xUnit、NUnit、MSTest)が決まったら、そこに合わせて学んでください。

なお、F-6-2 の DI 化を済ませておくと、テストで Repository を差し替えやすくなります。先んじてやっておいて損はない準備です。


本付録の課題は 任意提出 です。提出するなら、自分のリポジトリに次のような構成で残してください。

Kadai_AppendixF/
├── F-3-1_BulkSalaryUp/ ← Windows フォーム編 F-3-1
├── F-3-2_CsvExport/ ← Windows フォーム編 F-3-2
├── F-4-1_Paging/ ← Web 編 F-4-1
├── F-5-1_Zodiac/ ← 占いアプリ編 F-5-1
└── README.md ← どの課題に取り組んだか、感想・詰まった点

README.md には、最低限:

  • 取り組んだ課題の一覧
  • 各課題で 詰まった点どう解決したか
  • 「次に作るとしたら何を変えるか」のメモ

を書くと、配属後にも振り返る価値のあるドキュメントになります。

レビューを希望する場合は、特定の 1〜2 課題に絞って 講師・先輩に依頼してください。複数同時のレビュー依頼は、お互いに焦点がぼやけます。


本付録は 本編から少し離れる練習 をするための場でした。 さらに先へ進みたい場合の指針は、第 31 章「11節 次に学ぶなら」 にまとめてあります。

  • Windows アプリ深掘り(WPF、WinUI、MAUI、MVVM)
  • Web 深掘り(Razor Pages、Blazor、認証認可)
  • DB 深掘り(EF Core、Dapper、クエリチューニング)
  • 現場の基礎(Git ブランチ運用、テスト、設計と読みやすさ)

本付録の課題で「もっと深くやりたい」と感じたテーマがあれば、それが次に学ぶべきテーマです。

たとえば:

本付録で楽しかった課題次に学ぶと深まるテーマ
F-3-1 トランザクショントランザクション分離レベル、デッドロック、楽観/悲観ロック
F-3-2 / F-4-3 CSV エクスポート大量データのストリーム処理、メモリ効率、SqlDataReader の早期解放
F-3-3 部署マスタ管理マスタ管理画面の設計パターン、コードの再利用、UserControl
F-4-1 ページング / F-4-2 ソートクエリチューニング、複合インデックス、実行計画
F-5-1 干支・星座計算ロジックの切り出し、テーブル駆動でのデータ管理
F-6-1 / F-6-2 設定 + DIOptions パターン、IOptions<T>、ライフサイクル(Singleton/Scoped/Transient)、テスト戦略全般

何から手を付けるか迷ったときは、配属先のリポジトリで実際に使われている技術 を優先してください。本編で身に付けた Repository / SqlParameter / 入力チェック / PRG パターン は、どの道を選んでも土台になります。


本付録は、研修のゴールテープを切ったあとに、もう一段スピードを上げて走りたい人のために置いた 練習トラック です。 1 つも手を付けなくても研修は完成しています。1 つでも完成させたら、それは配属先で語れる成果になります。

無理せず、興味のあるところから始めてください。