第23章 Windowsフォーム社員管理アプリ:一覧表示
この章の目的
Section titled “この章の目的”この章では、本研修のオリジナル題材となる 社員管理アプリ の最初の章として、SQLServer の employees テーブルを Windows フォームで一覧表示する ところまでを作ります。
これまでの章で、次の道具が手元に揃っています。
- 第 16 章で構築した
TrainingDB - 第 17 章で書いた
SqlConnection+SqlCommandのコード - Windows フォームの基本操作とイベント(ボタンの
Clickなど) - 表形式でデータを見せる
DataGridView(本章で改めて基礎から触れるので、初めてでも大丈夫です)
第 23〜25 章では、これらを組み合わせて、社員管理アプリ を 3 段階で作っていきます。
| 章 | テーマ | 主な機能 |
|---|---|---|
| 第 23 章(本章) | 一覧表示 | employees 全件を DataGridView に表示 |
| 第 24 章 | 検索 | 名前・部署で絞り込み(WHERE + パラメータ化) |
| 第 25 章 | 編集・更新 | 行を選んで編集、UPDATE。新規登録・削除も実装 |
本章では「一覧表示」までを完成させ、新規登録・削除のボタンは 画面に置くだけ(枠のみ) にしておきます。これらは第 25 章で実装します。
この章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- Windows フォームアプリで SQLServer に接続できる
SqlCommand.ExecuteReaderで複数行の結果を取得できるSqlDataReaderのループでList<Employee>を組み立てられる- DTO クラス(
Employee)の役割を説明できる - Repository パターン(
EmployeeRepository)の役割を説明できる DataGridViewのDataSourceにList<Employee>を渡して表を表示できる- JOIN で
departments.department_nameを一緒に取得できる - 未実装機能のボタンを「枠だけ」置く理由を説明できる
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | Windows フォーム アプリ |
| 対象フレームワーク | .NET 8 |
| ソリューション名 | KadaiWinFormsApp(第 23〜25 章で使い続ける) |
| プロジェクト名 | EmployeeApp(第 23 章で作成 → 第 24・25 章で育てる) |
| データベース | SQLServer 2022(TrainingDB) |
| 認証方式 | Windows 統合認証 |
| NuGet パッケージ | Microsoft.Data.SqlClient |
csproj の Nullable は disable に変更してください(第 1 章「1-1」参照)
第 23〜25 章は 1 つのプロジェクトを育てていきます
社員管理アプリは、第 23 章で作る
EmployeeAppプロジェクト 1 つ を、第 24 章(検索)・第 25 章(編集・更新)でも 開いて機能を足していく 形で進めます。章ごとに作り直しません。各章の区切りでコミットして提出します(章ごとに別フォルダは作りません)。
作業前チェック
Section titled “作業前チェック”- 直前までの課題が一通り手元にある(または提出済み)
- SSMS で
TrainingDBに接続でき、employeesとdepartmentsにデータが入っている - 第 17 章の
Kd17_01_HelloSqlServerがローカルで動作する - Windows フォームアプリのプロジェクトを作成して実行できる(第 18・19 章)
23-1 この章で作るアプリ
Section titled “23-1 この章で作るアプリ”この章で作る画面のイメージは次のとおりです。
┌──────────────────────────────────────────────────────────────┐│ 社員一覧 [再読込]│├──────────────────────────────────────────────────────────────┤│ EmployeeId │ LastName │ FirstName │ Email │ Salary │├────────────┼──────────┼───────────┼─────────────────┼────────┤│ 1001 │ 山田 │ 二郎 │ yamada.jiro@... │ 500000 ││ 1002 │ 佐藤 │ 昭夫 │ sato.akio@... │ 500000 ││ ... │├──────────────────────────────────────────────────────────────┤│ [新規登録] [削除] │└──────────────────────────────────────────────────────────────┘機能の範囲:
| 機能 | 本章での扱い |
|---|---|
一覧表示(employees 全件) | ✅ 実装する |
| 再読み込み | ✅ 実装する |
部署名表示(departments JOIN) | ✅ 実装する |
| 検索 | 第 24 章 |
| 編集・更新 | 第 25 章 |
| 新規登録 | 第 25 章(本章ではボタンの枠だけ) |
| 削除 | 第 25 章(本章ではボタンの枠だけ) |
23-2 プロジェクトを作成する
Section titled “23-2 プロジェクトを作成する”Windows フォームアプリを新規作成
Section titled “Windows フォームアプリを新規作成”ソリューション名 KadaiWinFormsApp、プロジェクト名 EmployeeApp、対象フレームワーク .NET 8.0 で Windows フォームアプリを作成します(第 18 章参照)。このソリューション・プロジェクトは、第 24・25 章でも開いて使い続けます。
Nullable を disable に変更
Section titled “Nullable を disable に変更”EmployeeApp.csproj を開き、<Nullable> を disable に変更します(第 1 章「1-1」参照)。
Microsoft.Data.SqlClient を NuGet で追加
Section titled “Microsoft.Data.SqlClient を NuGet で追加”第 17 章と同じ手順で、Microsoft.Data.SqlClient を NuGet パッケージとして追加します。
- ソリューションエクスプローラーで
EmployeeAppプロジェクトを右クリック - NuGet パッケージの管理
Microsoft.Data.SqlClientを検索してインストール
23-3 画面を作る
Section titled “23-3 画面を作る”Form1 の Form Designer を開き、次のコントロールを配置します。
位置は Location プロパティで指定し、ウィンドウサイズ変更に追従させたいコントロールは Anchor を併用します。
| コントロール | (Name) | プロパティ |
|---|---|---|
Form | Form1 | Text = 社員一覧、Size = 900, 540 |
Button | buttonReload | Text = 再読込、Location = 780, 12、Size = 90, 28、Anchor = Top, Right |
DataGridView | dataGridViewEmployees | Location = 12, 50、Size = 860, 400、Anchor = Top, Bottom, Left, Right、ReadOnly = true、AllowUserToAddRows = false、AllowUserToDeleteRows = false、SelectionMode = FullRowSelect、AutoSizeColumnsMode = Fill |
Button | buttonNew | Text = 新規登録、Location = 680, 462、Size = 90, 28、Anchor = Bottom, Right、Enabled = false |
Button | buttonDelete | Text = 削除、Location = 780, 462、Size = 90, 28、Anchor = Bottom, Right、Enabled = false |
DataGridViewとは(初めて本格的に使う人へ)
DataGridViewは、データを 表(グリッド)形式 で表示するためのコントロールです。Excel のシートのように、行と列でデータを並べて見せられます。 使い方の中心はDataSourceプロパティ です。ここにList<Employee>のようなリストを渡すと、Employeeの各プロパティ(EmployeeId/LastNameなど)が 自動的に列 になり、リストの 1 要素が 1 行ずつ表示されます(この自動生成のしくみは 23-6・23-7 で詳しく扱います)。 本章では「読み取り専用の一覧」として使うため、ReadOnlyをtrue、行の追加・削除をfalseにしてあります。ユーザーが直接セルを書き換えられないようにするためです。
buttonNew と buttonDelete は 枠だけ(Enabled を false) にしておきます。第 25 章で有効にして実装します。
Anchorで何が変わるか
AnchorをTop, Bottom, Left, Rightにすると、ウィンドウの上下左右からの距離を一定に保ちながらコントロールが伸縮します。DataGridViewをこの設定にすることで、ユーザーがフォームをリサイズしても一覧が画面いっぱいに広がります。buttonReloadはTop, Rightで「右上に張り付く」、buttonNew/buttonDeleteはBottom, Rightで「右下に張り付く」動きになります。
23-4 接続文字列を設定する
Section titled “23-4 接続文字列を設定する”接続文字列は、Form1 クラスの private const フィールド に書きます。
namespace EmployeeApp;
using Microsoft.Data.SqlClient;
public partial class Form1 : Form{ private const string ConnectionString = "Server=localhost;Database=TrainingDB;Integrated Security=true;TrustServerCertificate=true;";
public Form1() { InitializeComponent(); }}この Form1.cs を 出発点 に、23-6 で一覧表示の機能を 1 つずつ足していきます(いまの状態では、実行しても画面に何も表示されません)。
本格的なアプリでの接続文字列
実際の業務アプリでは、接続文字列をコードに直接書かず、設定ファイル(
appsettings.jsonなど)から読み込むのが一般的です。本研修では Web MVC 章(第 27〜30 章)でappsettings.jsonを扱います。 本章では「DB 接続の流れを画面と一緒に体験する」ことに集中するため、constで書いておきます。
Server=localhostで接続できないときSQLServer のインスタンス名によっては、
Server=localhostではなくServer=localhost\SQLEXPRESSやServer=.\SQLEXPRESSなどの指定が必要なことがあります。 第 16 章で SSMS から接続したときの サーバー名 をそのまま使うのが確実です。 接続でエラーになるときは、SSMS の「サーバー名」欄をコピーして接続文字列のServer=...;部分を置き換えてみてください。
23-5 Employee クラスと EmployeeRepository クラスを作る
Section titled “23-5 Employee クラスと EmployeeRepository クラスを作る”Employee クラス(DTO)
Section titled “Employee クラス(DTO)”employees テーブル 1 行分のデータを保持するクラスです。DTO(Data Transfer Object、データを運ぶための入れ物)と呼ばれます。
プロジェクトに新しいクラスファイル Employee.cs を追加し、次の内容にします。
namespace EmployeeApp;
public class Employee{ public int EmployeeId { get; set; } public string LastName { get; set; } public string FirstName { get; set; } public string Email { get; set; } public DateTime HireDate { get; set; } public decimal Salary { get; set; } public int DepartmentId { get; set; } public string DepartmentName { get; set; }
public string FullName => $"{LastName} {FirstName}";}ポイント:
LastName/FirstNameの 姓名分離 はテーブル定義と同じDepartmentNameはemployeesテーブル自体には存在せず、JOIN でdepartmentsから取得した値を入れるFullNameは読み取り専用のプロパティで、姓と名を結合した文字列を返す(後の検索や表示で便利)
EmployeeRepository クラス
Section titled “EmployeeRepository クラス”DB 操作をまとめたクラスです。フォーム側からは「GetAll() で社員リストが返ってくる」と見えるようにします。Repository パターン と呼ばれる設計で、フォームのコードと DB のコードを切り分けるのが目的です。
EmployeeRepository.cs を追加します。
namespace EmployeeApp;
using Microsoft.Data.SqlClient;using System.Collections.Generic;
public class EmployeeRepository{ private readonly string _connectionString;
public EmployeeRepository(string connectionString) { _connectionString = connectionString; }
public List<Employee> GetAll() { List<Employee> list = new List<Employee>();
const string sql = @" SELECT e.employee_id, e.last_name, e.first_name, e.email, e.hire_date, e.salary, e.department_id, d.department_name FROM employees e LEFT JOIN departments d ON e.department_id = d.department_id ORDER BY e.employee_id";
using SqlConnection connection = new SqlConnection(_connectionString); connection.Open();
using SqlCommand command = new SqlCommand(sql, connection); using SqlDataReader reader = command.ExecuteReader();
while (reader.Read()) { Employee employee = new Employee { EmployeeId = reader.GetInt32(reader.GetOrdinal("employee_id")), LastName = reader.GetString(reader.GetOrdinal("last_name")), FirstName = reader.GetString(reader.GetOrdinal("first_name")), Email = reader.IsDBNull(reader.GetOrdinal("email")) ? string.Empty : reader.GetString(reader.GetOrdinal("email")), HireDate = reader.GetDateTime(reader.GetOrdinal("hire_date")), Salary = reader.IsDBNull(reader.GetOrdinal("salary")) ? 0m : reader.GetDecimal(reader.GetOrdinal("salary")), DepartmentId = reader.IsDBNull(reader.GetOrdinal("department_id")) ? 0 : reader.GetInt32(reader.GetOrdinal("department_id")), DepartmentName = reader.IsDBNull(reader.GetOrdinal("department_name")) ? string.Empty : reader.GetString(reader.GetOrdinal("department_name")) }; list.Add(employee); }
return list; }
public int Insert(Employee employee) { // 第25章で実装します throw new NotImplementedException("Insert は第25章で実装します。"); }
public int Delete(int employeeId) { // 第25章で実装します throw new NotImplementedException("Delete は第25章で実装します。"); }}コードのポイント
Section titled “コードのポイント”| ポイント | 説明 |
|---|---|
| 接続文字列はコンストラクタで受け取る | フォーム側で const を持ち、Repository に渡す形に分離 |
SqlCommand.ExecuteReader() | 複数行を返す SELECT で使う(第 17 章の ExecuteScalar は単一値) |
while (reader.Read()) | 行があるかぎりループ。Read() は次の行があれば true を返し、現在行を読めるようにする |
reader.GetOrdinal("列名") | 列名から列番号を取得。列名でアクセスすると順序変更に強い |
reader.IsDBNull(列番号) | NULL かどうかを判定。NULL のときに GetString などを呼ぶと例外になるため必要 |
Insert / Delete の枠 | 本章では NotImplementedException を投げるだけ。第 25 章で実装 |
はじめて出てくる書き方:条件演算子(三項演算子)
? :Repository のコードに
reader.IsDBNull(idxEmail) ? string.Empty : reader.GetString(idxEmail)という見慣れない書き方が出てきました。これは 条件演算子(三項演算子とも呼ぶ)で、条件 ? 真のときの値 : 偽のときの値という形で 「条件によって値を出し分ける」 ものです。この 1 行は、
if-elseで書くと次と同じ意味です。string email;if (reader.IsDBNull(idxEmail)) // email 列が NULL ならemail = string.Empty; // 空文字にするelse // NULL でなければemail = reader.GetString(idxEmail); // 実際の値を読む
if-elseを 1 行に縮めて「値」を返す書き方 と考えれば大丈夫です。Salary/DepartmentId/DepartmentNameも同じパターンで、「NULL なら既定値、そうでなければ読む」を表しています。データベースの列は NULL のことがあるため、読み出しでこの書き方がよく登場します。
Repository パターンの利点
フォーム側に SQL を直接書くと、フォームのコードが肥大化し、テストや変更がしづらくなります。 Repository に DB 操作をまとめておくと、フォームからは
_repository.GetAll()のように 業務的な意味で呼べる ようになり、見通しがよくなります。 第 27〜30 章の Web MVC でも、同じ Repository パターンを使います。
23-6 フォームに一覧表示の機能を組み込む
Section titled “23-6 フォームに一覧表示の機能を組み込む”ここからは、23-4 で作った Form1.cs の骨組みに、機能を 1 つずつ足していきます。実際の開発では「画面に部品を置く → その部品の イベントを選ぶ → ハンドラーの中身を書く」という作業の繰り返しです。本章でもその流れをそのまま体験します。
この節の進め方
ソースを丸ごと貼り付けるのではなく、ステップごとに「どこに・何を・なぜ」書くか を確認しながら、自分の手で組み立てます。各ステップのあとでビルド(
Ctrl+Shift+B)を試すと、どこで間違えたか分かりやすくなります。 どうしても詰まったときの 完成形(答え合わせ用) は 23-9 に置いてあります。先に見ず、まず自分で積み上げてみてください。
ステップ1 Repository をフォームに持たせる
Section titled “ステップ1 Repository をフォームに持たせる”Form1 から DB 操作を呼べるように、EmployeeRepository を フィールド に持ち、コンストラクタで生成します。23-4 で書いた Form1.cs に、コメント ← 追加 の 2 行を足します。
namespace EmployeeApp;
using Microsoft.Data.SqlClient;
public partial class Form1 : Form{ private const string ConnectionString = "Server=localhost;Database=TrainingDB;Integrated Security=true;TrustServerCertificate=true;";
private readonly EmployeeRepository _repository; // ← 追加
public Form1() { InitializeComponent(); _repository = new EmployeeRepository(ConnectionString); // ← 追加 }}| 書いたもの | 意図 |
|---|---|
_repository フィールド | フォームが開いている間ずっと使い回す DB アクセスの窓口 |
| コンストラクタでの生成 | 接続文字列を渡して Repository を 1 つ用意する |
ビルドの状態:通ります(
EmployeeRepositoryは 23-5 で作成済み)。
ステップ2 一覧を読み込むメソッドを書く(自分で呼ぶ部品)
Section titled “ステップ2 一覧を読み込むメソッドを書く(自分で呼ぶ部品)”「DB から全件取って DataGridView に表示する」処理は、起動時にも再読込ボタンでも使います。共通の LoadEmployees メソッド にまとめておきます。これは イベントではなく、自分のコードから呼ぶための部品 です。コンストラクタの後ろに、次のメソッドを追加します。
private void LoadEmployees(){ try { List<Employee> list = _repository.GetAll(); dataGridViewEmployees.DataSource = list; } catch (SqlException ex) { MessageBox.Show( $"SQLServer 関連のエラーが発生しました。\nエラー番号:{ex.Number}\nメッセージ:{ex.Message}", "DB エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception ex) { MessageBox.Show( $"予期しないエラーが発生しました。\n内容:{ex.Message}", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); }}| 書いたもの | 意図 |
|---|---|
_repository.GetAll() | DB から List<Employee> を取得する(SQL は Repository 側) |
dataGridViewEmployees.DataSource = list | リストを渡すだけで Employee の各プロパティが列になる |
try-catch + MessageBox.Show | コンソールではないので、エラーは画面のダイアログで知らせる |
ビルドの状態:通ります(まだどこからも呼んでいませんが、メソッドが存在するだけなら問題ありません)。
ステップ3 起動時に一覧を出す(Form の Load イベント)
Section titled “ステップ3 起動時に一覧を出す(Form の Load イベント)”フォームが最初に表示される瞬間に一覧を読み込みたいので、Form1 の Load イベント を使います。
イベントの紐付け(デザイナ操作)
- Form Designer でフォームの 空白部分を 1 回クリック して選択する
- プロパティウィンドウの 稲妻アイコン(イベント一覧)をクリック
Loadの行を ダブルクリック → 空のForm1_Loadメソッドが自動生成される
自動生成された Form1_Load の 中 に、次の 1 行だけを書きます。
private void Form1_Load(object sender, EventArgs e){ LoadEmployees();}意図:起動時の一覧表示は、ステップ2で作った LoadEmployees を呼ぶだけ。
ステップ4 再読込ボタンを動かす(buttonReload の Click イベント)
Section titled “ステップ4 再読込ボタンを動かす(buttonReload の Click イベント)”イベントの紐付け(デザイナ操作)
- Form Designer で
buttonReloadを ダブルクリック する →Clickが既定イベントなので、これだけで空のbuttonReload_Clickが生成される
自動生成された buttonReload_Click の 中 に、次の 1 行を書きます。
private void buttonReload_Click(object sender, EventArgs e){ LoadEmployees();}意図:再読込も「一覧を取り直して表示する」だけなので、同じ LoadEmployees を呼ぶ。
紐付けが正しくできたかの確認
プロパティウィンドウの稲妻アイコンを押すと、紐付け済みイベントの右にメソッド名が出ます。
Loadの右にForm1_Load、buttonReloadのClickの右にbuttonReload_Clickが表示されていれば OK です。 「実行しても一覧が出ない」「ボタンを押しても反応しない」ときの大半は、この 紐付け忘れ が原因です。
ここまでで、起動時に一覧が表示され、再読込ボタンも動くようになります。実行(F5)して確かめてください。
23-7 列の見せ方を整える
Section titled “23-7 列の見せ方を整える”このまま動かすと、DataGridView には Employee クラスのプロパティ名(EmployeeId、LastName、FullName、DepartmentName 等)が列として出てきます。
順番もアルファベット順だったり、Email 列の幅が狭くて読みづらかったりします。
最小限の整え方として、次の 1 行を dataGridViewEmployees のプロパティで設定しておきます(Designer の値設定でも、コードでも可)。
| プロパティ | 値 |
|---|---|
AutoSizeColumnsMode | Fill |
これで、列がフォーム幅に合わせて自動調整されます。
列の名前を日本語にしたり、表示しない列を隠したりは、演習課題で扱います。
23-8 新規・削除ボタンの「枠」について
Section titled “23-8 新規・削除ボタンの「枠」について”buttonNew、buttonDelete は Enabled = false で押せない状態にしてあります。
これらは第 25 章で EmployeeRepository.Insert / Delete を実装すると同時に有効化します。
なぜ未実装でも枠を置くのか
一覧画面の上に 追加・削除のボタンが将来並ぶ ことを、最初の段階で見えるようにしておくと、設計の見通しがよくなります。 Repository に
Insert、Deleteメソッドの枠を用意したのも同じ意図です。後の章で実装する予定の機能は、骨組みだけ先に作っておくと、変更がスムーズになります。これはアジャイル開発でよく使われる「ウォーキングスケルトン(歩く骨組み)」の考え方です。
23-9 完成形の確認(答え合わせ用)
Section titled “23-9 完成形の確認(答え合わせ用)”まずは 23-6 のステップを自分で積み上げてから見てください
これは、23-6 のステップ1〜4 をすべて終えたあとの
Form1.csの 完成形 です。詰まったときの 答え合わせ に使ってください。最初からこれを貼り付けると、イベントの紐付けやメソッドの置き場所が身につきません。
namespace EmployeeApp;
using Microsoft.Data.SqlClient;
public partial class Form1 : Form{ private const string ConnectionString = "Server=localhost;Database=TrainingDB;Integrated Security=true;TrustServerCertificate=true;";
private readonly EmployeeRepository _repository;
public Form1() { InitializeComponent(); _repository = new EmployeeRepository(ConnectionString); }
private void Form1_Load(object sender, EventArgs e) { LoadEmployees(); }
private void buttonReload_Click(object sender, EventArgs e) { LoadEmployees(); }
private void LoadEmployees() { try { List<Employee> list = _repository.GetAll(); dataGridViewEmployees.DataSource = list; } catch (SqlException ex) { MessageBox.Show( $"SQLServer 関連のエラーが発生しました。\nエラー番号:{ex.Number}\nメッセージ:{ex.Message}", "DB エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } catch (Exception ex) { MessageBox.Show( $"予期しないエラーが発生しました。\n内容:{ex.Message}", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } }}よくあるつまずき
Section titled “よくあるつまずき”| 症状 | 原因 | 対処 |
|---|---|---|
Form1 が真っ白で何も表示されない | Form1_Load の紐付けがされていない | プロパティウィンドウのイベント欄から Load を選んでメソッドを紐付ける |
SqlException (Number=4060) | TrainingDB が存在しない、または接続文字列の typo | 第 16 章を参照して TrainingDB を作成、または接続文字列を確認 |
SqlException (Number=208) | テーブル名や列名の typo | SQL を SSMS で実行してみる |
DataGridView に表示されない | DataSource への代入が抜けている | dataGridViewEmployees.DataSource = list; を確認 |
NotImplementedException がいきなり出る | 「新規登録」「削除」ボタンを押した | 本章では未実装。第 25 章で実装するまで Enabled = false を維持 |
| 列が変な順番で並ぶ | プロパティ名のアルファベット順で並ぶ | 演習課題 23-2 で列定義を手動で行う |
日本語が ??? になる | last_name / first_name 列が VARCHAR のまま | テーブル定義が NVARCHAR か確認(第 16 章) |
学んだことチェック
Section titled “学んだことチェック”- Windows フォームアプリで SQLServer に接続するための NuGet パッケージを追加できる
-
SqlDataReaderで複数行を取得し、List<Employee>を組み立てられる - DTO クラス(
Employee)の役割を説明できる - Repository パターン(
EmployeeRepository)の役割を説明できる -
DataGridView.DataSourceにList<Employee>を設定して画面に表示できる - JOIN を含む SQL で複数テーブルの情報を取得できる
-
reader.IsDBNullで NULL の安全な扱いができる - 条件演算子(
条件 ? A : B)を読める - 未実装機能のボタンを「枠だけ」置く意味を説明できる
- DTO とは何のためのクラスですか。
- Repository パターンの利点を 2 つ挙げてください。
ExecuteScalarとExecuteReaderの使い分けを説明してください。reader.IsDBNullを呼ぶ理由は何ですか。LEFT JOINを使う理由は何ですか(INNER JOINではなく)。buttonNew.Enabled = falseにしている理由は何ですか。- 接続文字列を
constでフォームに直接書く方法の 長所と短所 を 1 つずつ挙げてください。
この章からの社員管理アプリ(第 23〜25 章)は、チームで自走しながら進めるハンズオン形式 で取り組みます。 講師の細かい指示を待たず、本文を手順書として読みながら、ペアやチームで確認し合い、自分たちのペースでアプリを組み上げます(提供されたソースを使うので、できあがるアプリは全員ほぼ同じ動作になります)。
アプリが動いたら、演習課題に進みます。本章の 必須課題は、できあがったソースを読み解いて「なぜそう書くのか」をコメントとして書き残す 作業です。コードを書くこと以上に、コードを読んで意味を理解すること を大切にします。発展課題として、機能のカスタマイズにも挑戦できます。
チームの役割分担
Section titled “チームの役割分担”チームでの進め方と役割分担(リーダー / 技術部長 / タイムキーパー)、「自走のすすめ」は、第 17 章「ここからはチームで進める」 で説明したとおりです。同じ役割分担で、この章のアプリ開発を進めます。
この章の進め方
Section titled “この章の進め方”- チームで本文 23-2〜23-9 の 実装ステップ に沿って、社員一覧アプリを組み上げる(= アプリが動く)。23-6 のステップ1〜4 は自分の手で。詰まったら 23-9 の完成形で答え合わせ
- 「課題 23-1 の確認すること」で動作を確かめる(提供ソースなので全員ほぼ同じ動作になる)
- 【必須課題 23-1】 できあがったソースに「なぜ」コメントを付ける
- 【発展課題 23-2 / 23-3】 余裕があれば機能をカスタマイズする
- タイムキーパーの合図で手を止め、チーム内で ミニ発表(下記)を行う
必須課題は、本文で育てている EmployeeApp プロジェクト にそのまま書き込みます(「なぜ」コメントも同じプロジェクト)。発展課題だけは、本体を壊さないよう 同じ KadaiWinFormsApp ソリューション内に別プロジェクト として作ります。
発展用プロジェクトでも Microsoft.Data.SqlClient を NuGet で追加し、Nullable を disable にしてから始めてください。
| 課題 | プロジェクト | 内容 |
|---|---|---|
| 課題 23-1(必須) | EmployeeApp(本文の続き) | 一覧アプリ本体 + 「なぜ」コメント |
| 課題 23-2(発展) | Ext_ColumnCustom(新規) | 列の表示をカスタマイズ |
| 課題 23-3(発展) | Ext_DepartmentList(新規) | 部署一覧画面を作る |
フォルダ構成は次のようになります。
KadaiWinFormsApp/ ← 第 23〜25 章で使うソリューション KadaiWinFormsApp.sln EmployeeApp/ ← 本体(必須・章をまたいで育てる) Ext_ColumnCustom/ ← 発展 23-2(任意) Ext_DepartmentList/ ← 発展 23-3(任意)ミニ発表(成果の共有)
Section titled “ミニ発表(成果の共有)”ひととおり動いたら、チーム内で一人ずつ ごく簡単に発表 します。難しく考えず、次の 3 つを話すだけで十分です。
- デモ:自分のアプリを実際に動かして見せる(一覧表示・再読込)
- 1 問説明:自分が付けた「なぜ」コメントから 1 つ、または上の「ペア確認」から 1 つを選び、自分の言葉で説明する
- 一言:コメントを書いていて一番「なるほど」と思った点、または詰まったポイントを一言
発表は「説明できる = 理解できている」の確認です
点数をつけるためのものではありません。うまく説明できなかった項目があれば、それが「まだ自分のものになっていない」サインです。その場でチームで埋め合わせましょう。 リーダーは全員が一度は話せるように、タイムキーパーは一人あたりの持ち時間を見ながら進行を助けてください。
課題 23-1 ソースを読み解いて「なぜ」コメントを付ける
Section titled “課題 23-1 ソースを読み解いて「なぜ」コメントを付ける”本文 23-2〜23-9 の実装ステップで組み上げた社員一覧アプリ(EmployeeApp)の Form1.cs と EmployeeRepository.cs を読み返し、次の 2 種類のコメントを書き込んでください。
- (A) メソッドの役割:各メソッドの 上の行 に、そのメソッドが何をするかを 1 行(
//)で書く - (B) 難所の「なぜ」:下の表の各箇所に、「なぜそう書くのか」「何のためか」 を自分の言葉で書く
これは、書いたコードを丸暗記するのではなく、1 行ずつ意味を理解する ための課題です。提供ソースをそのまま使うので、アプリの動作は全員ほぼ同じになります。読み解きに集中しましょう。
コメントは「前の行」に
//で書くコメントは、説明したいコードの すぐ上の行 に置きます。行末に書くと、長い理由を書いたときに行が伸びて折り返し、読みにくくなるためです(
// ← 追加のような短い覚え書きだけ行末でも構いません)。
書くのは「意図」。コードの言い換えは NG
コードを日本語に置き換えただけのコメント(言い換えコメント)は評価対象になりません(→ 第 7 章「コラム:コメントの書き方」)。「何をしているか」はコードを読めば分かります。書いてほしいのは 「なぜ」 です。
(A) メソッドの役割(例)
// 起動時に呼ばれ、社員一覧を読み込んで表示するprivate void Form1_Load(object sender, EventArgs e){ LoadEmployees();}(B) 難所の「なぜ」(良い例・悪い例)
// ❌ 言い換え(コードをなぞっただけ)// IsDBNull が true なら空文字、それ以外は GetStringEmail = reader.IsDBNull(idxEmail) ? string.Empty : reader.GetString(idxEmail);
// ✅ なぜ・何のため(理解していないと書けない)// email は NULL 可。NULL のまま GetString を呼ぶと例外になるので、// 先に NULL か確かめて空文字に置き換えるEmail = reader.IsDBNull(idxEmail) ? string.Empty : reader.GetString(idxEmail);(B) 「なぜ」コメントを付ける箇所
| ファイル | 箇所 | 説明する観点(= ここに「なぜ」を書く) |
|---|---|---|
EmployeeRepository.cs | using SqlConnection connection = ... | なぜ using を付けるのか |
EmployeeRepository.cs | while (reader.Read()) | このループは何をしているか |
EmployeeRepository.cs | reader.GetOrdinal("...") | なぜ列名から番号を取るのか(番号直書きとの違い) |
EmployeeRepository.cs | reader.IsDBNull(...) の三項演算子 | なぜ NULL チェックが必要なのか |
EmployeeRepository.cs | LEFT JOIN departments | なぜ INNER JOIN ではなく LEFT JOIN か |
EmployeeRepository.cs | Insert / Delete の NotImplementedException | なぜ中身を書かず「枠」にしておくのか |
Form1.cs | _repository フィールド + コンストラクタでの生成 | なぜローカル変数ではなくフィールドに持つのか |
Form1.cs | Form1_Load で LoadEmployees() を呼ぶ | なぜ起動時のこのタイミングで読むのか |
Form1.cs | dataGridViewEmployees.DataSource = list; | なぜ List を渡すだけで表になるのか |
Form1.cs | LoadEmployees を別メソッドに切り出した点 | なぜイベントに直接書かず、共通メソッドにしたのか |
確認すること
- (A) 各メソッドの上に「役割」を 1 行コメントした
- (B) 表のすべての箇所に「なぜ/何のため」のコメントを書いた
- コメントは説明する行の 前の行 に書いた
- 言い換えコメント(コードの動作をなぞっただけ)になっていない
- 自分の言葉で書いた(本文の解説をそのまま写していない)
- アプリは起動して一覧が表示される(動作は提供ソースのまま、全員同じ)
「なぜか分からない」箇所こそ宝
コメントを書こうとして手が止まった箇所が、理解の穴です。チームで相談したり、本文の「書いたもの/意図」表や補足ブロックを読み返したりして埋めましょう。埋まったら、それがミニ発表で話せるネタになります。
課題 23-2 列の表示をカスタマイズする
Section titled “課題 23-2 列の表示をカスタマイズする”KadaiWinFormsApp ソリューションに新しいプロジェクト Ext_ColumnCustom を作成し、本文で完成させた EmployeeApp のコードをコピーした上で、DataGridView の 列の見え方 を次のとおりカスタマイズしてください。
仕様
- 列の表示順を:
EmployeeId→FullName→DepartmentName→Email→HireDate→Salaryの順にする - 列ヘッダーを日本語に:
社員番号/氏名/部署/メール/入社日/給与 LastName/FirstName/DepartmentId列は 非表示(Visible = false)Salary列の表示形式を 3 桁区切り(例:500,000)に
ヒント
DataSourceを設定する前にdataGridViewEmployees.AutoGenerateColumns = false;にして、Columnsに手動でDataGridViewTextBoxColumnを追加する方法がきれい- もしくは、
DataSourceを設定した後にdataGridViewEmployees.Columns["LastName"].Visible = false;のように個別に設定する方法もある(両方どちらでも可) - 3 桁区切りは
DefaultCellStyle.Format = "N0"
課題 23-3 部署一覧画面を作る
Section titled “課題 23-3 部署一覧画面を作る”KadaiWinFormsApp ソリューションに新しいプロジェクト Ext_DepartmentList を作成し、departments テーブルを表示する画面を作ってください。
仕様
Departmentクラス(DTO):DepartmentId、DepartmentName、ManagerId(NULL 可)DepartmentRepository:GetAll()メソッドでdepartments全件を取得DataGridViewに表示- 接続文字列は
Form1のconstに持つ - ManagerId が NULL の場合はその列が空欄になる
確認すること
-
Departmentクラスを作成した -
DepartmentRepository.GetAll()を実装した -
DataGridViewで 5 件の部署が表示される -
ManagerId列が NULL の行は空欄になる(IsDBNullのハンドリング)
提出前チェックリスト
Section titled “提出前チェックリスト”- 全プロジェクトが
KadaiWinFormsAppソリューションに入っている - 各プロジェクトで Nullable を disable にしている
-
Microsoft.Data.SqlClientを NuGet で追加した - 起動時に一覧が表示される
- 各メソッドの上に「役割」を 1 行コメントした
- 指定の難所すべてに「なぜ」コメントを前行で書いた
- コメントが言い換えではなく「なぜ/何のため」になっている
-
SqlExceptionをキャッチしてMessageBoxでエラーを表示する -
bin・obj・.vsフォルダが Git 管理に入っていない - チーム内でミニ発表(デモ + 1 問説明 + 一言)を行った
Git への提出
Section titled “Git への提出”完成したところまでを保存して提出します(タイマーはありません。自分のペースで区切りのよいところまで)。
git statusgit add .git commit -m "Chapter23: 一覧アプリ完成+なぜコメント / <一番なるほどと思った点>"git push origin main提出方法:Git が使えないときはサーバへコピー
Git の調子が悪いときは、講師の指示で
pushの代わりにKadaiWinFormsAppフォルダをサーバ上の自分のフォルダへコピーして提出します。 その場合は、コミットメッセージの代わりに、提出先へエクスプローラーの右クリック →「新規作成」→「テキスト ドキュメント」で提出メモ.txtを作り、「どこまで完成したか」「詰まったポイント」を書いておいてください。
Git の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。
この章のまとめ
Section titled “この章のまとめ”- Windows フォームアプリから SQLServer に接続するときは
Microsoft.Data.SqlClientを NuGet で追加する - 複数行を取得するときは
SqlCommand.ExecuteReader()とSqlDataReaderのループを使う - DTO(
Employee)はテーブル 1 行分のデータを運ぶ入れ物 - Repository(
EmployeeRepository)は DB 操作をフォームから切り出すクラス DataGridView.DataSource = listでList<T>を画面に表示できる- JOIN で関連テーブル(
departments)の情報も一緒に取得できる - 後の章で実装する機能は、ボタンの枠やメソッドの枠を先に置いておくと見通しがよい
次章 第 24 章「Windowsフォーム社員管理アプリ:検索」 では、本章で作った一覧画面に 検索機能 を追加します。 名前(姓 or 名)の部分一致検索と、部署プルダウンでの絞り込みを実装します。
ここで重要になるのが パラメータ化クエリ(SqlParameter)です。
ユーザー入力をそのまま SQL に結合すると SQL インジェクション という重大なセキュリティ問題を引き起こすため、@param の形でパラメータを渡す方法を学びます。