Skip to content

第23章 Windowsフォーム社員管理アプリ:一覧表示

この章では、本研修のオリジナル題材となる 社員管理アプリ の最初の章として、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)の役割を説明できる
  • DataGridViewDataSourceList<Employee> を渡して表を表示できる
  • JOIN で departments.department_name を一緒に取得できる
  • 未実装機能のボタンを「枠だけ」置く理由を説明できる

項目内容
開発環境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 章(編集・更新)でも 開いて機能を足していく 形で進めます。章ごとに作り直しません。各章の区切りでコミットして提出します(章ごとに別フォルダは作りません)。


  • 直前までの課題が一通り手元にある(または提出済み)
  • SSMS で TrainingDB に接続でき、employeesdepartments にデータが入っている
  • 第 17 章の Kd17_01_HelloSqlServer がローカルで動作する
  • Windows フォームアプリのプロジェクトを作成して実行できる(第 18・19 章)

この章で作る画面のイメージは次のとおりです。

┌──────────────────────────────────────────────────────────────┐
│ 社員一覧 [再読込]│
├──────────────────────────────────────────────────────────────┤
│ EmployeeId │ LastName │ FirstName │ Email │ Salary │
├────────────┼──────────┼───────────┼─────────────────┼────────┤
│ 1001 │ 山田 │ 二郎 │ yamada.jiro@... │ 500000 │
│ 1002 │ 佐藤 │ 昭夫 │ sato.akio@... │ 500000 │
│ ... │
├──────────────────────────────────────────────────────────────┤
│ [新規登録] [削除] │
└──────────────────────────────────────────────────────────────┘

機能の範囲:

機能本章での扱い
一覧表示(employees 全件)✅ 実装する
再読み込み✅ 実装する
部署名表示(departments JOIN)✅ 実装する
検索第 24 章
編集・更新第 25 章
新規登録第 25 章(本章ではボタンの枠だけ)
削除第 25 章(本章ではボタンの枠だけ)

Windows フォームアプリを新規作成

Section titled “Windows フォームアプリを新規作成”

ソリューション名 KadaiWinFormsApp、プロジェクト名 EmployeeApp、対象フレームワーク .NET 8.0 で Windows フォームアプリを作成します(第 18 章参照)。このソリューション・プロジェクトは、第 24・25 章でも開いて使い続けます。

EmployeeApp.csproj を開き、<Nullable>disable に変更します(第 1 章「1-1」参照)。

Microsoft.Data.SqlClient を NuGet で追加

Section titled “Microsoft.Data.SqlClient を NuGet で追加”

第 17 章と同じ手順で、Microsoft.Data.SqlClient を NuGet パッケージとして追加します。

  1. ソリューションエクスプローラーで EmployeeApp プロジェクトを右クリック
  2. NuGet パッケージの管理
  3. Microsoft.Data.SqlClient を検索してインストール

Form1Form Designer を開き、次のコントロールを配置します。 位置は Location プロパティで指定し、ウィンドウサイズ変更に追従させたいコントロールは Anchor を併用します。

コントロール(Name)プロパティ
FormForm1Text = 社員一覧Size = 900, 540
ButtonbuttonReloadText = 再読込Location = 780, 12Size = 90, 28Anchor = Top, Right
DataGridViewdataGridViewEmployeesLocation = 12, 50Size = 860, 400Anchor = Top, Bottom, Left, RightReadOnly = trueAllowUserToAddRows = falseAllowUserToDeleteRows = falseSelectionMode = FullRowSelectAutoSizeColumnsMode = Fill
ButtonbuttonNewText = 新規登録Location = 680, 462Size = 90, 28Anchor = Bottom, RightEnabled = false
ButtonbuttonDeleteText = 削除Location = 780, 462Size = 90, 28Anchor = Bottom, RightEnabled = false

DataGridView とは(初めて本格的に使う人へ)

DataGridView は、データを 表(グリッド)形式 で表示するためのコントロールです。Excel のシートのように、行と列でデータを並べて見せられます。 使い方の中心は DataSource プロパティ です。ここに List<Employee> のようなリストを渡すと、Employee の各プロパティ(EmployeeId / LastName など)が 自動的に列 になり、リストの 1 要素が 1 行ずつ表示されます(この自動生成のしくみは 23-6・23-7 で詳しく扱います)。 本章では「読み取り専用の一覧」として使うため、ReadOnlytrue、行の追加・削除を false にしてあります。ユーザーが直接セルを書き換えられないようにするためです。

buttonNewbuttonDelete枠だけ(Enabledfalse) にしておきます。第 25 章で有効にして実装します。

Anchor で何が変わるか

AnchorTop, Bottom, Left, Right にすると、ウィンドウの上下左右からの距離を一定に保ちながらコントロールが伸縮します。DataGridView をこの設定にすることで、ユーザーがフォームをリサイズしても一覧が画面いっぱいに広がります。 buttonReloadTop, Right で「右上に張り付く」、buttonNew / buttonDeleteBottom, Right で「右下に張り付く」動きになります。


接続文字列は、Form1 クラスの private const フィールド に書きます。

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;";
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\SQLEXPRESSServer=.\SQLEXPRESS などの指定が必要なことがあります。 第 16 章で SSMS から接続したときの サーバー名 をそのまま使うのが確実です。 接続でエラーになるときは、SSMS の「サーバー名」欄をコピーして接続文字列の Server=...; 部分を置き換えてみてください。


23-5 Employee クラスと EmployeeRepository クラスを作る

Section titled “23-5 Employee クラスと EmployeeRepository クラスを作る”

employees テーブル 1 行分のデータを保持するクラスです。DTO(Data Transfer Object、データを運ぶための入れ物)と呼ばれます。

プロジェクトに新しいクラスファイル Employee.cs を追加し、次の内容にします。

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姓名分離 はテーブル定義と同じ
  • DepartmentNameemployees テーブル自体には存在せず、JOIN で departments から取得した値を入れる
  • FullName は読み取り専用のプロパティで、姓と名を結合した文字列を返す(後の検索や表示で便利)

DB 操作をまとめたクラスです。フォーム側からは「GetAll() で社員リストが返ってくる」と見えるようにします。Repository パターン と呼ばれる設計で、フォームのコードと DB のコードを切り分けるのが目的です。

EmployeeRepository.cs を追加します。

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章で実装します。");
}
}
ポイント説明
接続文字列はコンストラクタで受け取るフォーム側で 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 行を足します。

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); // ← 追加
}
}
書いたもの意図
_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 イベント)”

フォームが最初に表示される瞬間に一覧を読み込みたいので、Form1Load イベント を使います。

イベントの紐付け(デザイナ操作)

  1. Form Designer でフォームの 空白部分を 1 回クリック して選択する
  2. プロパティウィンドウの 稲妻アイコン(イベント一覧)をクリック
  3. 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_LoadbuttonReloadClick の右に buttonReload_Click が表示されていれば OK です。 「実行しても一覧が出ない」「ボタンを押しても反応しない」ときの大半は、この 紐付け忘れ が原因です。

ここまでで、起動時に一覧が表示され、再読込ボタンも動くようになります。実行(F5)して確かめてください


このまま動かすと、DataGridView には Employee クラスのプロパティ名(EmployeeIdLastNameFullNameDepartmentName 等)が列として出てきます。 順番もアルファベット順だったり、Email 列の幅が狭くて読みづらかったりします。

最小限の整え方として、次の 1 行を dataGridViewEmployees のプロパティで設定しておきます(Designer の値設定でも、コードでも可)。

プロパティ
AutoSizeColumnsModeFill

これで、列がフォーム幅に合わせて自動調整されます。

列の名前を日本語にしたり、表示しない列を隠したりは、演習課題で扱います。


23-8 新規・削除ボタンの「枠」について

Section titled “23-8 新規・削除ボタンの「枠」について”

buttonNewbuttonDeleteEnabled = false で押せない状態にしてあります。 これらは第 25 章で EmployeeRepository.Insert / Delete を実装すると同時に有効化します。

なぜ未実装でも枠を置くのか

一覧画面の上に 追加・削除のボタンが将来並ぶ ことを、最初の段階で見えるようにしておくと、設計の見通しがよくなります。 Repository に InsertDelete メソッドの枠を用意したのも同じ意図です。後の章で実装する予定の機能は、骨組みだけ先に作っておくと、変更がスムーズになります。

これはアジャイル開発でよく使われる「ウォーキングスケルトン(歩く骨組み)」の考え方です。


23-9 完成形の確認(答え合わせ用)

Section titled “23-9 完成形の確認(答え合わせ用)”

まずは 23-6 のステップを自分で積み上げてから見てください

これは、23-6 のステップ1〜4 をすべて終えたあとの Form1.cs完成形 です。詰まったときの 答え合わせ に使ってください。最初からこれを貼り付けると、イベントの紐付けやメソッドの置き場所が身につきません。

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);
}
}
}

症状原因対処
Form1 が真っ白で何も表示されないForm1_Load の紐付けがされていないプロパティウィンドウのイベント欄から Load を選んでメソッドを紐付ける
SqlException (Number=4060)TrainingDB が存在しない、または接続文字列の typo第 16 章を参照して TrainingDB を作成、または接続文字列を確認
SqlException (Number=208)テーブル名や列名の typoSQL を SSMS で実行してみる
DataGridView に表示されないDataSource への代入が抜けているdataGridViewEmployees.DataSource = list; を確認
NotImplementedException がいきなり出る「新規登録」「削除」ボタンを押した本章では未実装。第 25 章で実装するまで Enabled = false を維持
列が変な順番で並ぶプロパティ名のアルファベット順で並ぶ演習課題 23-2 で列定義を手動で行う
日本語が ??? になるlast_name / first_name 列が VARCHAR のままテーブル定義が NVARCHAR か確認(第 16 章)

  • Windows フォームアプリで SQLServer に接続するための NuGet パッケージを追加できる
  • SqlDataReader で複数行を取得し、List<Employee> を組み立てられる
  • DTO クラス(Employee)の役割を説明できる
  • Repository パターン(EmployeeRepository)の役割を説明できる
  • DataGridView.DataSourceList<Employee> を設定して画面に表示できる
  • JOIN を含む SQL で複数テーブルの情報を取得できる
  • reader.IsDBNull で NULL の安全な扱いができる
  • 条件演算子(条件 ? A : B)を読める
  • 未実装機能のボタンを「枠だけ」置く意味を説明できる

  1. DTO とは何のためのクラスですか。
  2. Repository パターンの利点を 2 つ挙げてください。
  3. ExecuteScalarExecuteReader の使い分けを説明してください。
  4. reader.IsDBNull を呼ぶ理由は何ですか。
  5. LEFT JOIN を使う理由は何ですか(INNER JOIN ではなく)。
  6. buttonNew.Enabled = false にしている理由は何ですか。
  7. 接続文字列を const でフォームに直接書く方法の 長所と短所 を 1 つずつ挙げてください。

この章からの社員管理アプリ(第 23〜25 章)は、チームで自走しながら進めるハンズオン形式 で取り組みます。 講師の細かい指示を待たず、本文を手順書として読みながら、ペアやチームで確認し合い、自分たちのペースでアプリを組み上げます(提供されたソースを使うので、できあがるアプリは全員ほぼ同じ動作になります)。

アプリが動いたら、演習課題に進みます。本章の 必須課題は、できあがったソースを読み解いて「なぜそう書くのか」をコメントとして書き残す 作業です。コードを書くこと以上に、コードを読んで意味を理解すること を大切にします。発展課題として、機能のカスタマイズにも挑戦できます。


チームでの進め方と役割分担(リーダー / 技術部長 / タイムキーパー)、「自走のすすめ」は、第 17 章「ここからはチームで進める」 で説明したとおりです。同じ役割分担で、この章のアプリ開発を進めます。


  1. チームで本文 23-2〜23-9 の 実装ステップ に沿って、社員一覧アプリを組み上げる(= アプリが動く)。23-6 のステップ1〜4 は自分の手で。詰まったら 23-9 の完成形で答え合わせ
  2. 「課題 23-1 の確認すること」で動作を確かめる(提供ソースなので全員ほぼ同じ動作になる)
  3. 【必須課題 23-1】 できあがったソースに「なぜ」コメントを付ける
  4. 【発展課題 23-2 / 23-3】 余裕があれば機能をカスタマイズする
  5. タイムキーパーの合図で手を止め、チーム内で ミニ発表(下記)を行う

必須課題は、本文で育てている 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(任意)

ひととおり動いたら、チーム内で一人ずつ ごく簡単に発表 します。難しく考えず、次の 3 つを話すだけで十分です。

  1. デモ:自分のアプリを実際に動かして見せる(一覧表示・再読込)
  2. 1 問説明:自分が付けた「なぜ」コメントから 1 つ、または上の「ペア確認」から 1 つを選び、自分の言葉で説明する
  3. 一言:コメントを書いていて一番「なるほど」と思った点、または詰まったポイントを一言

発表は「説明できる = 理解できている」の確認です

点数をつけるためのものではありません。うまく説明できなかった項目があれば、それが「まだ自分のものになっていない」サインです。その場でチームで埋め合わせましょう。 リーダーは全員が一度は話せるように、タイムキーパーは一人あたりの持ち時間を見ながら進行を助けてください。



課題 23-1 ソースを読み解いて「なぜ」コメントを付ける

Section titled “課題 23-1 ソースを読み解いて「なぜ」コメントを付ける”

本文 23-2〜23-9 の実装ステップで組み上げた社員一覧アプリ(EmployeeApp)の Form1.csEmployeeRepository.cs を読み返し、次の 2 種類のコメントを書き込んでください。

  • (A) メソッドの役割:各メソッドの 上の行 に、そのメソッドが何をするかを 1 行(//)で書く
  • (B) 難所の「なぜ」:下の表の各箇所に、「なぜそう書くのか」「何のためか」 を自分の言葉で書く

これは、書いたコードを丸暗記するのではなく、1 行ずつ意味を理解する ための課題です。提供ソースをそのまま使うので、アプリの動作は全員ほぼ同じになります。読み解きに集中しましょう。

コメントは「前の行」に // で書く

コメントは、説明したいコードの すぐ上の行 に置きます。行末に書くと、長い理由を書いたときに行が伸びて折り返し、読みにくくなるためです(// ← 追加 のような短い覚え書きだけ行末でも構いません)。

書くのは「意図」。コードの言い換えは NG

コードを日本語に置き換えただけのコメント(言い換えコメント)は評価対象になりません(→ 第 7 章「コラム:コメントの書き方」)。「何をしているか」はコードを読めば分かります。書いてほしいのは 「なぜ」 です。

(A) メソッドの役割(例)

// 起動時に呼ばれ、社員一覧を読み込んで表示する
private void Form1_Load(object sender, EventArgs e)
{
LoadEmployees();
}

(B) 難所の「なぜ」(良い例・悪い例)

// ❌ 言い換え(コードをなぞっただけ)
// IsDBNull が true なら空文字、それ以外は GetString
Email = reader.IsDBNull(idxEmail) ? string.Empty : reader.GetString(idxEmail);
// ✅ なぜ・何のため(理解していないと書けない)
// email は NULL 可。NULL のまま GetString を呼ぶと例外になるので、
// 先に NULL か確かめて空文字に置き換える
Email = reader.IsDBNull(idxEmail) ? string.Empty : reader.GetString(idxEmail);

(B) 「なぜ」コメントを付ける箇所

ファイル箇所説明する観点(= ここに「なぜ」を書く)
EmployeeRepository.csusing SqlConnection connection = ...なぜ using を付けるのか
EmployeeRepository.cswhile (reader.Read())このループは何をしているか
EmployeeRepository.csreader.GetOrdinal("...")なぜ列名から番号を取るのか(番号直書きとの違い)
EmployeeRepository.csreader.IsDBNull(...) の三項演算子なぜ NULL チェックが必要なのか
EmployeeRepository.csLEFT JOIN departmentsなぜ INNER JOIN ではなく LEFT JOIN
EmployeeRepository.csInsert / DeleteNotImplementedExceptionなぜ中身を書かず「枠」にしておくのか
Form1.cs_repository フィールド + コンストラクタでの生成なぜローカル変数ではなくフィールドに持つのか
Form1.csForm1_LoadLoadEmployees() を呼ぶなぜ起動時のこのタイミングで読むのか
Form1.csdataGridViewEmployees.DataSource = list;なぜ List を渡すだけで表になるのか
Form1.csLoadEmployees を別メソッドに切り出した点なぜイベントに直接書かず、共通メソッドにしたのか

確認すること

  • (A) 各メソッドの上に「役割」を 1 行コメントした
  • (B) 表のすべての箇所に「なぜ/何のため」のコメントを書いた
  • コメントは説明する行の 前の行 に書いた
  • 言い換えコメント(コードの動作をなぞっただけ)になっていない
  • 自分の言葉で書いた(本文の解説をそのまま写していない)
  • アプリは起動して一覧が表示される(動作は提供ソースのまま、全員同じ)

「なぜか分からない」箇所こそ宝

コメントを書こうとして手が止まった箇所が、理解の穴です。チームで相談したり、本文の「書いたもの/意図」表や補足ブロックを読み返したりして埋めましょう。埋まったら、それがミニ発表で話せるネタになります。



課題 23-2 列の表示をカスタマイズする

Section titled “課題 23-2 列の表示をカスタマイズする”

KadaiWinFormsApp ソリューションに新しいプロジェクト Ext_ColumnCustom を作成し、本文で完成させた EmployeeApp のコードをコピーした上で、DataGridView列の見え方 を次のとおりカスタマイズしてください。

仕様

  • 列の表示順を:EmployeeIdFullNameDepartmentNameEmailHireDateSalary の順にする
  • 列ヘッダーを日本語に:社員番号 / 氏名 / 部署 / メール / 入社日 / 給与
  • 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"

KadaiWinFormsApp ソリューションに新しいプロジェクト Ext_DepartmentList を作成し、departments テーブルを表示する画面を作ってください。

仕様

  • Department クラス(DTO):DepartmentIdDepartmentNameManagerId(NULL 可)
  • DepartmentRepository:GetAll() メソッドで departments 全件を取得
  • DataGridView に表示
  • 接続文字列は Form1const に持つ
  • ManagerId が NULL の場合はその列が空欄になる

確認すること

  • Department クラスを作成した
  • DepartmentRepository.GetAll() を実装した
  • DataGridView で 5 件の部署が表示される
  • ManagerId 列が NULL の行は空欄になる(IsDBNull のハンドリング)

  • 全プロジェクトが KadaiWinFormsApp ソリューションに入っている
  • 各プロジェクトで Nullable を disable にしている
  • Microsoft.Data.SqlClient を NuGet で追加した
  • 起動時に一覧が表示される
  • 各メソッドの上に「役割」を 1 行コメントした
  • 指定の難所すべてに「なぜ」コメントを前行で書いた
  • コメントが言い換えではなく「なぜ/何のため」になっている
  • SqlException をキャッチして MessageBox でエラーを表示する
  • binobj.vs フォルダが Git 管理に入っていない
  • チーム内でミニ発表(デモ + 1 問説明 + 一言)を行った

完成したところまでを保存して提出します(タイマーはありません。自分のペースで区切りのよいところまで)。

Terminal window
git status
git add .
git commit -m "Chapter23: 一覧アプリ完成+なぜコメント / <一番なるほどと思った点>"
git push origin main

提出方法:Git が使えないときはサーバへコピー

Git の調子が悪いときは、講師の指示で push の代わりに KadaiWinFormsApp フォルダをサーバ上の自分のフォルダへコピーして提出します。 その場合は、コミットメッセージの代わりに、提出先へエクスプローラーの右クリック →「新規作成」→「テキスト ドキュメント」で 提出メモ.txt を作り、「どこまで完成したか」「詰まったポイント」を書いておいてください。

Git の詳しい操作は、付録 C「Git のインストールと提出ルール」 を参照してください。


  • Windows フォームアプリから SQLServer に接続するときは Microsoft.Data.SqlClient を NuGet で追加する
  • 複数行を取得するときは SqlCommand.ExecuteReader()SqlDataReader のループを使う
  • DTO(Employee)はテーブル 1 行分のデータを運ぶ入れ物
  • Repository(EmployeeRepository)は DB 操作をフォームから切り出すクラス
  • DataGridView.DataSource = listList<T> を画面に表示できる
  • JOIN で関連テーブル(departments)の情報も一緒に取得できる
  • 後の章で実装する機能は、ボタンの枠やメソッドの枠を先に置いておくと見通しがよい

次章 第 24 章「Windowsフォーム社員管理アプリ:検索」 では、本章で作った一覧画面に 検索機能 を追加します。 名前(姓 or 名)の部分一致検索と、部署プルダウンでの絞り込みを実装します。

ここで重要になるのが パラメータ化クエリ(SqlParameter)です。 ユーザー入力をそのまま SQL に結合すると SQL インジェクション という重大なセキュリティ問題を引き起こすため、@param の形でパラメータを渡す方法を学びます。