第19章 WebアプリからOracle Databaseを利用する
この章の目的
Section titled “この章の目的”この章では、ASP.NET Core MVCアプリからOracle Databaseに接続し、DBから取得した社員情報をWeb画面に表示します。
第18章では、Controllerの中でサンプルデータを作成し、社員一覧画面に表示しました。
List<EmployeeListItem> employees = CreateSampleEmployees();
return View(employees);この章では、このサンプルデータ部分を、Oracle Databaseからの取得処理に置き換えます。
第18章:Controller ↓サンプルデータ ↓View
第19章:Controller ↓EmployeeRepository ↓Oracle Database ↓Viewこの章では、Entity Framework CoreのようなO/Rマッパーは使いません。
SQL研修で学習したSELECT文を活かし、OracleConnection、OracleCommand、OracleDataReader を使って、比較的レガシーなADO.NET形式でDBアクセスを行います。
Webアプリの目的は、MVCの構成を理解することです。
DBアクセスの目的は、SQLで取得した結果をWeb画面に表示する流れを理解することです。
挿絵・スクリーンショット案
Section titled “挿絵・スクリーンショット案”この章では、次の画像を入れると理解しやすくなります。
図19-1 MVCアプリとOracle Databaseの接続イメージ図19-2 NuGetでOracle.ManagedDataAccess.Coreを追加する画面図19-3 Models / Repositories / Controllers / Views のフォルダ構成図19-4 DB接続に失敗したときのエラー画面例図19-5 Oracle DBから取得した社員一覧画面図19-6 社員詳細画面図19-7 社員名検索・部署ID検索の画面画像を挿入する場合は、次のように書けます。
画像ファイルは、images フォルダにまとめると管理しやすくなります。
CSharpText/ ├─ 19_mvc_oracle_database.md └─ images/ ├─ 19_mvc_oracle_overview.png ├─ 19_nuget_oracle_package.png ├─ 19_solution_explorer.png └─ 19_employee_list.pngこの章でできるようになること
Section titled “この章でできるようになること”この章を終えると、次のことができるようになります。
- ASP.NET Core MVCアプリにOracle接続用のNuGetパッケージを追加できる
- MVCアプリからOracle Databaseに接続できる
EmployeeRepositoryクラスを作成できる- WebアプリでDBアクセス処理をControllerから分離できる
employees表とdepartments表をJOINして社員一覧を取得できる- DBから取得したデータを
EmployeeListItemとして扱える List<EmployeeListItem>をViewに渡して一覧表示できる- 社員IDを指定して詳細画面を表示できる
- 社員名や部署IDで検索できる
- DB接続時の例外処理を意識できる
- MVC、Repository、DBの役割分担を説明できる
本章で使用する環境
Section titled “本章で使用する環境”| 項目 | 内容 |
|---|---|
| 開発環境 | Visual Studio 2022 |
| プロジェクト種類 | ASP.NET Core Web アプリ Model-View-Controller |
| 対象フレームワーク | .NET 8 |
| プロジェクト名 | Chapter19_MvcOracle |
| DB | Oracle Database |
| 接続ユーザー | pingt |
| 使用する表 | employees、departments |
| 追加するNuGetパッケージ | Oracle.ManagedDataAccess.Core |
作業前チェック
Section titled “作業前チェック”作業を始める前に、次の内容を確認してください。
- SQL*Plusで
pingtユーザーに接続できる -
employees表をSELECTできる -
departments表をSELECTできる - コンソールアプリからOracle Databaseに接続できる
- ASP.NET Core MVCプロジェクトを作成できる
- Controller、Model、Viewの役割を説明できる
-
List<T>をViewに渡して一覧表示できる -
OracleConnection、OracleCommand、OracleDataReaderの基本を理解している - 第18章の内容をGitに提出済みである
19-1 WebアプリからDBを利用する流れ
Section titled “19-1 WebアプリからDBを利用する流れ”第18章までの流れ
Section titled “第18章までの流れ”第18章では、社員一覧を表示するために、Controllerの中でサンプルデータを作成しました。
public IActionResult Index(){ List<EmployeeListItem> employees = CreateSampleEmployees();
return View(employees);}この方法は、MVCの流れを理解するためには分かりやすいです。
しかし、実際の業務アプリでは、社員情報はソースコードの中に直接書かれているわけではありません。
多くの場合、データベースから取得します。
この章での流れ
Section titled “この章での流れ”この章では、次のような流れに変更します。
ブラウザー ↓EmployeesController ↓EmployeeRepository ↓Oracle Database ↓EmployeeListItemのリスト ↓Index.cshtml ↓ブラウザーに社員一覧を表示図で表すと、次のようなイメージです。
[Browser] ↓ request[EmployeesController] ↓ calls[EmployeeRepository] ↓ SQL[Oracle Database] ↓ result[List<EmployeeListItem>] ↓[View] ↓ HTML[Browser]図19-1 挿入候補
MVCアプリとOracle Databaseの接続イメージを図にすると分かりやすくなります。
ControllerにDB処理を直接書かない
Section titled “ControllerにDB処理を直接書かない”DB接続処理をControllerの中に直接書くこともできます。
しかし、ControllerにSQLや接続処理を大量に書くと、次のような問題が出ます。
Controllerが長くなる画面制御とDB処理が混ざる修正箇所が分かりにくくなる再利用しにくいそこで、この章ではDBアクセス処理を EmployeeRepository クラスに分けます。
EmployeesController 画面表示の流れを担当する
EmployeeRepository Oracle Databaseから社員データを取得する
EmployeeListItem 社員一覧画面に表示する1行分のデータを表す
Index.cshtml 社員一覧をHTMLで表示する19-2 MVCプロジェクトを作成する
Section titled “19-2 MVCプロジェクトを作成する”プロジェクトを作成する
Section titled “プロジェクトを作成する”Visual Studio 2022で、ASP.NET Core MVCプロジェクトを作成します。
1. 「新しいプロジェクトの作成」をクリックする2. 「ASP.NET Core Web アプリ Model-View-Controller」を選択する3. プロジェクト名を Chapter19_MvcOracle にする4. フレームワークは .NET 8.0 を選択する5. 認証の種類は「なし」にする6. HTTPS用の構成はチェックありでよい7. 「作成」をクリックする作成後、まずは初期状態で実行します。
ブラウザーに初期画面が表示されれば、MVCプロジェクトの作成は成功です。
必要なフォルダを確認する
Section titled “必要なフォルダを確認する”プロジェクトには、主に次のフォルダがあります。
ControllersModelsViewswwwrootこの章では、さらにDBアクセス用のフォルダを追加します。
Repositories最終的には、次のような構成になります。
Chapter19_MvcOracle ├─ Controllers │ └─ EmployeesController.cs ├─ Models │ ├─ EmployeeListItem.cs │ └─ EmployeeDetail.cs ├─ Repositories │ └─ EmployeeRepository.cs ├─ Views │ └─ Employees │ ├─ Index.cshtml │ └─ Details.cshtml └─ wwwroot図19-3 挿入候補
ソリューションエクスプローラーで、上記フォルダ構成が見えるスクリーンショットを入れるとよいです。
19-3 Oracle接続用パッケージを追加する
Section titled “19-3 Oracle接続用パッケージを追加する”NuGetパッケージを追加する
Section titled “NuGetパッケージを追加する”MVCアプリからOracle Databaseに接続するために、NuGetパッケージを追加します。
使用するパッケージは、コンソールアプリで使ったものと同じです。
Oracle.ManagedDataAccess.CoreVisual Studioで次の手順を行います。
1. ソリューションエクスプローラーでプロジェクト名を右クリックする2. 「NuGet パッケージの管理」をクリックする3. 「参照」タブを開く4. 検索欄に Oracle.ManagedDataAccess.Core と入力する5. Oracle.ManagedDataAccess.Core を選択する6. 「インストール」をクリックする7. 確認画面が出たら内容を確認して進める図19-2 挿入候補
NuGetで
Oracle.ManagedDataAccess.Coreを検索・追加している画面を入れるとよいです。
usingで使用する名前空間
Section titled “usingで使用する名前空間”Oracle接続を行うクラスでは、次の using を使います。
using Oracle.ManagedDataAccess.Client;この using により、次のクラスを使えるようになります。
OracleConnectionOracleCommandOracleDataReaderOracleExceptionOracleDbType19-4 Modelクラスを作成する
Section titled “19-4 Modelクラスを作成する”EmployeeListItemクラス
Section titled “EmployeeListItemクラス”まず、社員一覧画面に表示する1行分のデータを表す EmployeeListItem クラスを作成します。
Models フォルダに EmployeeListItem.cs を追加します。
namespace Chapter19_MvcOracle.Models{ public class EmployeeListItem { public int EmployeeId { get; set; } public string EmployeeName { get; set; } = ""; public string DepartmentName { get; set; } = ""; public DateTime HireDate { get; set; } public decimal? Salary { get; set; } }}EmployeeListItem は、employees 表と departments 表をJOINした結果を画面に表示するためのModelです。
employee_idemployee_namedepartment_namehiredatesalaryEmployeeDetailクラス
Section titled “EmployeeDetailクラス”次に、社員詳細画面用の EmployeeDetail クラスを作成します。
Models フォルダに EmployeeDetail.cs を追加します。
namespace Chapter19_MvcOracle.Models{ public class EmployeeDetail { public int EmployeeId { get; set; } public string EmployeeName { get; set; } = ""; public string? Yomi { get; set; } public int JobId { get; set; } public int? ManagerId { get; set; } public DateTime HireDate { get; set; } public decimal? Salary { get; set; } public decimal? Commission { get; set; } public int DepartmentId { get; set; } public string DepartmentName { get; set; } = ""; }}詳細画面では、一覧画面よりも多くの項目を表示します。
社員ID社員名よみがな職種ID上司ID入社日給与歩合部署ID部署名一覧用と詳細用を分ける理由
Section titled “一覧用と詳細用を分ける理由”一覧画面と詳細画面では、必要な情報が異なります。
一覧画面では、見やすさを優先して必要最小限の情報を表示します。
社員ID社員名部署名入社日給与詳細画面では、1人分の情報を詳しく表示します。
よみがな職種ID上司ID歩合部署IDこのように、画面ごとに必要なデータの形を分けると、Viewが扱いやすくなります。
19-5 EmployeeRepositoryを作成する
Section titled “19-5 EmployeeRepositoryを作成する”Repositoriesフォルダを作成する
Section titled “Repositoriesフォルダを作成する”ソリューションエクスプローラーで、プロジェクトを右クリックし、Repositories フォルダを作成します。
Chapter19_MvcOracle └─ Repositoriesその中に、EmployeeRepository.cs を作成します。
EmployeeRepositoryの基本形
Section titled “EmployeeRepositoryの基本形”まず、次のように基本形を作成します。
using Chapter19_MvcOracle.Models;using Oracle.ManagedDataAccess.Client;
namespace Chapter19_MvcOracle.Repositories{ public class EmployeeRepository { private readonly string _connectionString = "User Id=pingt;Password=oracle;Data Source=localhost:1521/XEPDB1;";
public List<EmployeeListItem> GetEmployeeListItems() { List<EmployeeListItem> employees = new List<EmployeeListItem>();
return employees; } }}この段階では、まだDB接続処理は書いていません。
EmployeeRepository は、社員データを取得する役割を持つクラスです。
接続文字列について
Section titled “接続文字列について”接続文字列は、Oracle Databaseに接続するための情報です。
private readonly string _connectionString = "User Id=pingt;Password=oracle;Data Source=localhost:1521/XEPDB1;";この章では、学習を優先してRepository内に直接書きます。
実務では、接続文字列は通常、設定ファイルや環境変数で管理します。
補足
研修では、まず「WebアプリからOracle DBに接続して一覧表示する」ことを優先します。 接続文字列の設定ファイル化やDIによる注入は、必要に応じて後から扱います。
19-6 社員一覧をDBから取得する
Section titled “19-6 社員一覧をDBから取得する”使用するSQL
Section titled “使用するSQL”社員一覧では、employees 表と departments 表をJOINして、部署名も一緒に取得します。
SELECT e.employee_id, e.employee_name, d.department_name, e.hiredate, e.salaryFROM employees e JOIN departments d ON e.department_id = d.department_idORDER BY e.employee_idこのSQLは、SQL研修で学習したJOINをC#から実行するものです。
GetEmployeeListItemsメソッド
Section titled “GetEmployeeListItemsメソッド”EmployeeRepository の GetEmployeeListItems メソッドを次のように作成します。
using Chapter19_MvcOracle.Models;using Oracle.ManagedDataAccess.Client;
namespace Chapter19_MvcOracle.Repositories{ public class EmployeeRepository { private readonly string _connectionString = "User Id=pingt;Password=oracle;Data Source=localhost:1521/XEPDB1;";
public List<EmployeeListItem> GetEmployeeListItems() { List<EmployeeListItem> employees = new List<EmployeeListItem>();
string sql = @" SELECT e.employee_id, e.employee_name, d.department_name, e.hiredate, e.salary FROM employees e JOIN departments d ON e.department_id = d.department_id ORDER BY e.employee_id";
using (OracleConnection connection = new OracleConnection(_connectionString)) { connection.Open();
using (OracleCommand command = new OracleCommand(sql, connection)) using (OracleDataReader reader = command.ExecuteReader()) { while (reader.Read()) { EmployeeListItem employee = new EmployeeListItem();
employee.EmployeeId = Convert.ToInt32(reader["employee_id"]); employee.EmployeeName = reader["employee_name"].ToString() ?? ""; employee.DepartmentName = reader["department_name"].ToString() ?? ""; employee.HireDate = Convert.ToDateTime(reader["hiredate"]); employee.Salary = reader["salary"] == DBNull.Value ? null : Convert.ToDecimal(reader["salary"]);
employees.Add(employee); } } }
return employees; } }}GetEmployeeListItems メソッドの流れは次の通りです。
1. 空の List<EmployeeListItem> を作成する2. SQL文を用意する3. OracleConnectionでDBに接続する4. OracleCommandでSQLを実行する5. OracleDataReaderで1行ずつ読み取る6. 1行分をEmployeeListItemに変換する7. Listに追加する8. 最後にListを返す第17章で学んだ「DBデータをオブジェクトとして扱う」流れと同じです。
NULLの扱い
Section titled “NULLの扱い”salary はNULLになる可能性があります。
そのため、次のように DBNull.Value を確認しています。
employee.Salary = reader["salary"] == DBNull.Value ? null : Convert.ToDecimal(reader["salary"]);DBのNULLをC#の null として扱うため、EmployeeListItem の Salary は decimal? にしています。
public decimal? Salary { get; set; }19-7 EmployeesControllerからRepositoryを使う
Section titled “19-7 EmployeesControllerからRepositoryを使う”EmployeesControllerを作成する
Section titled “EmployeesControllerを作成する”Controllers フォルダに EmployeesController.cs を作成します。
using Chapter19_MvcOracle.Models;using Chapter19_MvcOracle.Repositories;using Microsoft.AspNetCore.Mvc;using Oracle.ManagedDataAccess.Client;
namespace Chapter19_MvcOracle.Controllers{ public class EmployeesController : Controller { public IActionResult Index() { try { EmployeeRepository repository = new EmployeeRepository();
List<EmployeeListItem> employees = repository.GetEmployeeListItems();
return View(employees); } catch (OracleException ex) { ViewData["ErrorMessage"] = "Oracle Database処理中にエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(new List<EmployeeListItem>()); } catch (Exception ex) { ViewData["ErrorMessage"] = "予期しないエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(new List<EmployeeListItem>()); } } }}Controllerの役割
Section titled “Controllerの役割”Controllerでは、次のことを行っています。
1. EmployeeRepositoryを作成する2. 社員一覧を取得する3. ViewへList<EmployeeListItem>を渡す4. エラーが発生した場合はエラーメッセージをViewに渡す重要なのは、ControllerにSQL文を書いていないことです。
Controller → 画面表示の流れを担当
Repository → DBアクセスを担当役割を分けることで、コードの見通しがよくなります。
学習用の例外表示について
Section titled “学習用の例外表示について”この章では、学習しやすいように ex.Message をViewへ渡しています。
ViewData["ErrorDetail"] = ex.Message;ただし、実務では、詳細な例外メッセージを利用者にそのまま見せないことが多いです。
接続文字列、SQL文、内部情報が含まれる可能性があるためです。
実務では、利用者には簡単なメッセージを表示し、詳細はログに残す設計が一般的です。
19-8 社員一覧Viewを作成する
Section titled “19-8 社員一覧Viewを作成する”Views/Employees/Index.cshtmlを作成する
Section titled “Views/Employees/Index.cshtmlを作成する”Views フォルダの中に Employees フォルダを作成し、その中に Index.cshtml を作成します。
Views └─ Employees └─ Index.cshtmlIndex.cshtml
Section titled “Index.cshtml”Index.cshtml を次のようにします。
@model List<Chapter19_MvcOracle.Models.EmployeeListItem>
@{ ViewData["Title"] = "社員一覧";}
<h1>社員一覧</h1>
@if (ViewData["ErrorMessage"] != null){ <div class="alert alert-danger"> <p>@ViewData["ErrorMessage"]</p> <p>@ViewData["ErrorDetail"]</p> </div>}
@if (Model.Count == 0){ <p>表示する社員データがありません。</p>}else{ <table class="table"> <thead> <tr> <th>社員ID</th> <th>社員名</th> <th>部署名</th> <th>入社日</th> <th>給与</th> <th></th> </tr> </thead> <tbody> @foreach (var employee in Model) { <tr> <td>@employee.EmployeeId</td> <td>@employee.EmployeeName</td> <td>@employee.DepartmentName</td> <td>@employee.HireDate.ToString("yyyy/MM/dd")</td> <td> @if (employee.Salary.HasValue) { @($"{employee.Salary.Value}円") } else { @("未設定") } </td> <td> <a asp-controller="Employees" asp-action="Details" asp-route-id="@employee.EmployeeId">詳細</a> </td> </tr> } </tbody> </table>}アプリを実行し、次のURLにアクセスします。
https://localhost:xxxx/EmployeesOracle Databaseから取得した社員一覧が表示されれば成功です。
社員ID 社員名 部署名 入社日 給与1001 山田二郎 総務 2001/04/01 500000円1002 佐藤昭夫 営業 2001/04/01 500000円1003 山口洋子 開発 2001/10/01 500000円...図19-5 挿入候補
DBから取得した社員一覧がブラウザーに表示されている画面を入れるとよいです。
19-9 社員詳細をDBから取得する
Section titled “19-9 社員詳細をDBから取得する”使用するSQL
Section titled “使用するSQL”社員詳細では、社員IDを指定して1件だけ取得します。
SELECT e.employee_id, e.employee_name, e.yomi, e.job_id, e.manager_id, e.hiredate, e.salary, e.commission, e.department_id, d.department_nameFROM employees e JOIN departments d ON e.department_id = d.department_idWHERE e.employee_id = :employeeId条件値は、文字列連結ではなくSQLパラメーターで渡します。
FindDetailByIdメソッドを追加する
Section titled “FindDetailByIdメソッドを追加する”EmployeeRepository に、次のメソッドを追加します。
public EmployeeDetail? FindDetailById(int employeeId){ string sql = @" SELECT e.employee_id, e.employee_name, e.yomi, e.job_id, e.manager_id, e.hiredate, e.salary, e.commission, e.department_id, d.department_name FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE e.employee_id = :employeeId";
using (OracleConnection connection = new OracleConnection(_connectionString)) { connection.Open();
using (OracleCommand command = new OracleCommand(sql, connection)) { command.BindByName = true; command.Parameters.Add("employeeId", OracleDbType.Int32).Value = employeeId;
using (OracleDataReader reader = command.ExecuteReader()) { if (reader.Read()) { EmployeeDetail employee = new EmployeeDetail();
employee.EmployeeId = Convert.ToInt32(reader["employee_id"]); employee.EmployeeName = reader["employee_name"].ToString() ?? ""; employee.Yomi = reader["yomi"] == DBNull.Value ? null : reader["yomi"].ToString(); employee.JobId = Convert.ToInt32(reader["job_id"]); employee.ManagerId = reader["manager_id"] == DBNull.Value ? null : Convert.ToInt32(reader["manager_id"]); employee.HireDate = Convert.ToDateTime(reader["hiredate"]); employee.Salary = reader["salary"] == DBNull.Value ? null : Convert.ToDecimal(reader["salary"]); employee.Commission = reader["commission"] == DBNull.Value ? null : Convert.ToDecimal(reader["commission"]); employee.DepartmentId = Convert.ToInt32(reader["department_id"]); employee.DepartmentName = reader["department_name"].ToString() ?? "";
return employee; } } } }
return null;}command.BindByNameについて
Section titled “command.BindByNameについて”次の行を追加しています。
command.BindByName = true;Oracleでは、パラメーターを名前で対応させたい場合に、この指定を入れておくと分かりやすくなります。
この研修では、SQLパラメーターを使うときは、基本的に次のように書く方針にします。
command.BindByName = true;command.Parameters.Add("employeeId", OracleDbType.Int32).Value = employeeId;Details Actionを追加する
Section titled “Details Actionを追加する”EmployeesController に Details Actionを追加します。
public IActionResult Details(int id){ try { EmployeeRepository repository = new EmployeeRepository();
EmployeeDetail? employee = repository.FindDetailById(id);
if (employee == null) { return NotFound(); }
return View(employee); } catch (OracleException ex) { ViewData["ErrorMessage"] = "Oracle Database処理中にエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(null); } catch (Exception ex) { ViewData["ErrorMessage"] = "予期しないエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(null); }}Details.cshtmlを作成する
Section titled “Details.cshtmlを作成する”Views/Employees フォルダに Details.cshtml を作成します。
@model Chapter19_MvcOracle.Models.EmployeeDetail?
@{ ViewData["Title"] = "社員詳細";}
<h1>社員詳細</h1>
@if (ViewData["ErrorMessage"] != null){ <div class="alert alert-danger"> <p>@ViewData["ErrorMessage"]</p> <p>@ViewData["ErrorDetail"]</p> </div>}
@if (Model == null){ <p>社員情報を表示できません。</p>}else{ <table class="table"> <tr> <th>社員ID</th> <td>@Model.EmployeeId</td> </tr> <tr> <th>社員名</th> <td>@Model.EmployeeName</td> </tr> <tr> <th>よみがな</th> <td>@(Model.Yomi ?? "未設定")</td> </tr> <tr> <th>職種ID</th> <td>@Model.JobId</td> </tr> <tr> <th>上司ID</th> <td> @if (Model.ManagerId.HasValue) { @Model.ManagerId.Value } else { @("未設定") } </td> </tr> <tr> <th>入社日</th> <td>@Model.HireDate.ToString("yyyy/MM/dd")</td> </tr> <tr> <th>給与</th> <td> @if (Model.Salary.HasValue) { @($"{Model.Salary.Value}円") } else { @("未設定") } </td> </tr> <tr> <th>歩合</th> <td> @if (Model.Commission.HasValue) { @Model.Commission.Value } else { @("未設定") } </td> </tr> <tr> <th>部署ID</th> <td>@Model.DepartmentId</td> </tr> <tr> <th>部署名</th> <td>@Model.DepartmentName</td> </tr> </table>}
<a asp-controller="Employees" asp-action="Index">一覧に戻る</a>詳細画面を確認する
Section titled “詳細画面を確認する”アプリを実行し、社員一覧画面から「詳細」リンクをクリックします。
または、次のURLに直接アクセスします。
https://localhost:xxxx/Employees/Details/1001社員詳細が表示されれば成功です。
図19-6 挿入候補
社員詳細画面のスクリーンショットを入れるとよいです。
19-10 社員名で検索する
Section titled “19-10 社員名で検索する”一覧取得メソッドに検索条件を追加する
Section titled “一覧取得メソッドに検索条件を追加する”社員名で部分一致検索できるようにします。
EmployeeRepository の GetEmployeeListItems を、次のように変更します。
public List<EmployeeListItem> GetEmployeeListItems(string? keyword){ List<EmployeeListItem> employees = new List<EmployeeListItem>();
string sql = @" SELECT e.employee_id, e.employee_name, d.department_name, e.hiredate, e.salary FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE (:keyword IS NULL OR e.employee_name LIKE :keyword) ORDER BY e.employee_id";
using (OracleConnection connection = new OracleConnection(_connectionString)) { connection.Open();
using (OracleCommand command = new OracleCommand(sql, connection)) { command.BindByName = true;
if (string.IsNullOrWhiteSpace(keyword)) { command.Parameters.Add("keyword", OracleDbType.Varchar2).Value = DBNull.Value; } else { command.Parameters.Add("keyword", OracleDbType.Varchar2).Value = "%" + keyword + "%"; }
using (OracleDataReader reader = command.ExecuteReader()) { while (reader.Read()) { EmployeeListItem employee = new EmployeeListItem();
employee.EmployeeId = Convert.ToInt32(reader["employee_id"]); employee.EmployeeName = reader["employee_name"].ToString() ?? ""; employee.DepartmentName = reader["department_name"].ToString() ?? ""; employee.HireDate = Convert.ToDateTime(reader["hiredate"]); employee.Salary = reader["salary"] == DBNull.Value ? null : Convert.ToDecimal(reader["salary"]);
employees.Add(employee); } } } }
return employees;}Controller側を変更する
Section titled “Controller側を変更する”EmployeesController の Index Actionを次のように変更します。
public IActionResult Index(string? keyword){ try { EmployeeRepository repository = new EmployeeRepository();
List<EmployeeListItem> employees = repository.GetEmployeeListItems(keyword);
ViewData["Keyword"] = keyword;
return View(employees); } catch (OracleException ex) { ViewData["ErrorMessage"] = "Oracle Database処理中にエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(new List<EmployeeListItem>()); } catch (Exception ex) { ViewData["ErrorMessage"] = "予期しないエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(new List<EmployeeListItem>()); }}Viewに検索フォームを追加する
Section titled “Viewに検索フォームを追加する”Index.cshtml の <h1>社員一覧</h1> の下に、検索フォームを追加します。
<form asp-controller="Employees" asp-action="Index" method="get" class="mb-3"> <div class="mb-3"> <label for="keyword" class="form-label">社員名検索</label> <input type="text" id="keyword" name="keyword" value="@ViewData["Keyword"]" class="form-control" /> </div>
<button type="submit" class="btn btn-primary">検索</button> <a asp-controller="Employees" asp-action="Index" class="btn btn-secondary">クリア</a></form>検索を確認する
Section titled “検索を確認する”アプリを実行し、社員名の一部を入力して検索します。
例:
山次のように、社員名に 山 を含む社員だけが表示されれば成功です。
山田二郎山口洋子図19-7 挿入候補
社員名検索の入力欄と検索結果が分かる画面を入れるとよいです。
19-11 部署IDでも検索する
Section titled “19-11 部署IDでも検索する”検索条件を追加する
Section titled “検索条件を追加する”社員名に加えて、部署IDでも検索できるようにします。
Repositoryのメソッドを次のように変更します。
public List<EmployeeListItem> GetEmployeeListItems(string? keyword, int? departmentId){ List<EmployeeListItem> employees = new List<EmployeeListItem>();
string sql = @" SELECT e.employee_id, e.employee_name, d.department_name, e.hiredate, e.salary FROM employees e JOIN departments d ON e.department_id = d.department_id WHERE (:keyword IS NULL OR e.employee_name LIKE :keyword) AND (:departmentId IS NULL OR e.department_id = :departmentId) ORDER BY e.employee_id";
using (OracleConnection connection = new OracleConnection(_connectionString)) { connection.Open();
using (OracleCommand command = new OracleCommand(sql, connection)) { command.BindByName = true;
if (string.IsNullOrWhiteSpace(keyword)) { command.Parameters.Add("keyword", OracleDbType.Varchar2).Value = DBNull.Value; } else { command.Parameters.Add("keyword", OracleDbType.Varchar2).Value = "%" + keyword + "%"; }
if (departmentId.HasValue) { command.Parameters.Add("departmentId", OracleDbType.Int32).Value = departmentId.Value; } else { command.Parameters.Add("departmentId", OracleDbType.Int32).Value = DBNull.Value; }
using (OracleDataReader reader = command.ExecuteReader()) { while (reader.Read()) { EmployeeListItem employee = new EmployeeListItem();
employee.EmployeeId = Convert.ToInt32(reader["employee_id"]); employee.EmployeeName = reader["employee_name"].ToString() ?? ""; employee.DepartmentName = reader["department_name"].ToString() ?? ""; employee.HireDate = Convert.ToDateTime(reader["hiredate"]); employee.Salary = reader["salary"] == DBNull.Value ? null : Convert.ToDecimal(reader["salary"]);
employees.Add(employee); } } } }
return employees;}Controller側を変更する
Section titled “Controller側を変更する”Index Actionを次のように変更します。
public IActionResult Index(string? keyword, int? departmentId){ try { EmployeeRepository repository = new EmployeeRepository();
List<EmployeeListItem> employees = repository.GetEmployeeListItems(keyword, departmentId);
ViewData["Keyword"] = keyword; ViewData["DepartmentId"] = departmentId;
return View(employees); } catch (OracleException ex) { ViewData["ErrorMessage"] = "Oracle Database処理中にエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(new List<EmployeeListItem>()); } catch (Exception ex) { ViewData["ErrorMessage"] = "予期しないエラーが発生しました。"; ViewData["ErrorDetail"] = ex.Message;
return View(new List<EmployeeListItem>()); }}Viewに部署ID入力欄を追加する
Section titled “Viewに部署ID入力欄を追加する”検索フォームに部署ID入力欄を追加します。
<form asp-controller="Employees" asp-action="Index" method="get" class="mb-3"> <div class="mb-3"> <label for="keyword" class="form-label">社員名検索</label> <input type="text" id="keyword" name="keyword" value="@ViewData["Keyword"]" class="form-control" /> </div>
<div class="mb-3"> <label for="departmentId" class="form-label">部署ID</label> <input type="number" id="departmentId" name="departmentId" value="@ViewData["DepartmentId"]" class="form-control" /> </div>
<button type="submit" class="btn btn-primary">検索</button> <a asp-controller="Employees" asp-action="Index" class="btn btn-secondary">クリア</a></form>検索結果0件の場合
Section titled “検索結果0件の場合”一覧表示部分で、0件の場合の表示を確認します。
@if (Model.Count == 0){ <p>表示する社員データがありません。</p>}else{ <table class="table"> ... </table>}検索条件に合うデータがない場合に、利用者へ分かりやすく表示することは重要です。
19-12 この章の構成を整理する
Section titled “19-12 この章の構成を整理する”最終的な構成
Section titled “最終的な構成”この章の最終的な構成は次の通りです。
Controllers └─ EmployeesController.cs
Models ├─ EmployeeListItem.cs └─ EmployeeDetail.cs
Repositories └─ EmployeeRepository.cs
Views └─ Employees ├─ Index.cshtml └─ Details.cshtmlそれぞれの役割は次の通りです。
| ファイル | 役割 |
|---|---|
EmployeesController | リクエストを受け取り、Repositoryを呼び出してViewにデータを渡す |
EmployeeRepository | Oracle Databaseから社員データを取得する |
EmployeeListItem | 社員一覧画面の1行分のデータ |
EmployeeDetail | 社員詳細画面のデータ |
Index.cshtml | 社員一覧画面 |
Details.cshtml | 社員詳細画面 |
社員一覧画面の流れは次の通りです。
1. ブラウザーで /Employees にアクセスする2. EmployeesController.Index が呼ばれる3. EmployeeRepository.GetEmployeeListItems が呼ばれる4. Oracle DatabaseにSELECT文を実行する5. SELECT結果を EmployeeListItem に変換する6. List<EmployeeListItem> をControllerへ返す7. ControllerがViewへ渡す8. Index.cshtmlが一覧表を表示する社員詳細画面の流れは次の通りです。
1. 一覧画面で詳細リンクをクリックする2. /Employees/Details/1001 にアクセスする3. EmployeesController.Details が呼ばれる4. EmployeeRepository.FindDetailById が呼ばれる5. Oracle Databaseから1件取得する6. EmployeeDetail に変換する7. Details.cshtmlが詳細を表示する第18章からの変化
Section titled “第18章からの変化”第18章と第19章の違いを整理します。
| 項目 | 第18章 | 第19章 |
|---|---|---|
| データ取得元 | Controller内のサンプルデータ | Oracle Database |
| DB接続 | なし | あり |
| DBアクセスクラス | なし | EmployeeRepository |
| 一覧表示 | サンプルの List<EmployeeListItem> | DBから取得した List<EmployeeListItem> |
| 詳細表示 | サンプルデータから検索 | DBから社員IDで検索 |
| 検索 | LINQでサンプルデータを検索 | SQLパラメーターでDB検索 |
第19章では、MVCの構成は保ったまま、データ取得元だけをDBへ変更しました。
よくあるつまずき
Section titled “よくあるつまずき”この章でよくあるつまずきを確認します。
| つまずき | 原因 | 対応 |
|---|---|---|
OracleConnection が見つからない | NuGetパッケージが入っていない | Oracle.ManagedDataAccess.Core を追加する |
OracleConnection が見つからない | using が不足している | using Oracle.ManagedDataAccess.Client; を追加する |
| Webアプリ実行時にDB接続エラーになる | Oracle Databaseが起動していない | SQL*Plusで接続確認する |
| 一覧画面が空になる | SQLが0件を返している | SQL*Plusで同じSQLを実行する |
| Viewが見つからない | Viewの場所や名前が違う | Views/Employees/Index.cshtml を確認する |
@model の型エラーになる | Controllerから渡す型とViewの型が違う | List<EmployeeListItem> で一致しているか確認する |
DBNull.Value の処理を忘れる | DBのNULLを直接変換している | 変換前に DBNull.Value を確認する |
| 詳細画面で404になる | 指定した社員IDが存在しない | 存在する社員IDで確認する |
| 検索条件が効かない | inputのnameとAction引数名が違う | name="keyword" と Index(string? keyword) を確認する |
| SQLパラメーターが効かない | パラメーター名や型が違う | SQL側とC#側の名前を確認する |
ORA- で始まるエラーが出る | Oracle側のエラー | エラー番号とメッセージを確認する |
学んだことチェック
Section titled “学んだことチェック”次の項目について、自分で説明できるか確認してください。
- MVCアプリからOracle Databaseに接続できる
- MVCプロジェクトにOracle接続用NuGetパッケージを追加できる
-
EmployeeRepositoryの役割を説明できる - ControllerとRepositoryの役割の違いを説明できる
-
employeesとdepartmentsをJOINして一覧データを取得できる - DBの1行を
EmployeeListItemに変換できる -
List<EmployeeListItem>をViewに渡せる - ViewでDBから取得した社員一覧を表示できる
- 社員IDを指定して詳細データを取得できる
-
EmployeeDetail?を使い、見つからない場合に対応できる - SQLパラメーターを使って検索条件を渡せる
-
DBNull.Valueを確認してNULLに対応できる - DB接続エラーが起きたときに確認すべき点を説明できる
研修の進め方によっては、隣の人または近くの人と説明確認を行います。
次の内容を、自分の言葉で説明してください。
- 第18章ではサンプルデータだった部分を、この章では何に置き換えましたか。
EmployeeRepositoryは何を担当するクラスですか。- ControllerにSQL文を直接書かない理由は何ですか。
EmployeeListItemとEmployeeDetailを分ける理由は何ですか。Index.cshtmlの@model List<EmployeeListItem>は何を意味していますか。- DBのNULLをC#で扱うとき、どのような確認が必要ですか。
- 社員名検索でSQLパラメーターを使う理由は何ですか。
- WebアプリでDB接続エラーが出た場合、まず何を確認しますか。
説明するときは、完全な答えでなくても構いません。
自分の言葉で説明しようとすることが大切です。
この章の演習課題に取り組みます。
制限時間は 100分 です。
時間内にすべて完成しなくても構いません。
できたところまでを保存し、Gitに提出してください。
まずは、全員が必須課題に取り組んでください。
課題19-1 MVCプロジェクトを作成する
Section titled “課題19-1 MVCプロジェクトを作成する”ASP.NET Core MVCプロジェクトを作成してください。
条件:
- プロジェクト名は
Chapter19_MvcOracleとする - フレームワークは
.NET 8.0を選択する - 認証は「なし」にする
- 初期状態で実行できることを確認する
課題19-2 Oracle接続用パッケージを追加する
Section titled “課題19-2 Oracle接続用パッケージを追加する”NuGetで Oracle.ManagedDataAccess.Core を追加してください。
条件:
- プロジェクトにパッケージを追加する
using Oracle.ManagedDataAccess.Client;を使える状態にする
課題19-3 EmployeeListItem Modelを作成する
Section titled “課題19-3 EmployeeListItem Modelを作成する”社員一覧画面用の EmployeeListItem クラスを作成してください。
プロパティ:
| プロパティ名 | 型 |
|---|---|
EmployeeId | int |
EmployeeName | string |
DepartmentName | string |
HireDate | DateTime |
Salary | decimal? |
条件:
Modelsフォルダに作成するSalaryはdecimal?にする
課題19-4 EmployeeRepositoryを作成する
Section titled “課題19-4 EmployeeRepositoryを作成する”Repositories フォルダを作成し、EmployeeRepository クラスを作成してください。
条件:
Repositoriesフォルダを作るEmployeeRepository.csを作る- 接続文字列をフィールドとして定義する
GetEmployeeListItemsメソッドを作成する
課題19-5 DBから社員一覧を取得して表示する
Section titled “課題19-5 DBから社員一覧を取得して表示する”employees と departments をJOINし、社員一覧をWeb画面に表示してください。
条件:
EmployeeRepository.GetEmployeeListItemsでDBから取得するEmployeesController.IndexからRepositoryを呼び出すViews/Employees/Index.cshtmlを作成する@model List<EmployeeListItem>を指定する- 表形式で社員一覧を表示する
必須課題が終わった人は、発展課題に取り組んでください。
課題19-6 EmployeeDetail Modelを作成する
Section titled “課題19-6 EmployeeDetail Modelを作成する”社員詳細画面用の EmployeeDetail クラスを作成してください。
プロパティ:
| プロパティ名 | 型 |
|---|---|
EmployeeId | int |
EmployeeName | string |
Yomi | string? |
JobId | int |
ManagerId | int? |
HireDate | DateTime |
Salary | decimal? |
Commission | decimal? |
DepartmentId | int |
DepartmentName | string |
課題19-7 社員詳細画面を作成する
Section titled “課題19-7 社員詳細画面を作成する”社員IDを指定して、詳細画面を表示してください。
条件:
EmployeeRepository.FindDetailById(int employeeId)を作成するEmployeesController.Details(int id)を作成するViews/Employees/Details.cshtmlを作成する- 一覧画面から詳細リンクで遷移できるようにする
- 見つからない場合は
NotFound()を返す
課題19-8 社員名検索を作成する
Section titled “課題19-8 社員名検索を作成する”社員名で部分一致検索できるようにしてください。
条件:
Index(string? keyword)で検索キーワードを受け取る- Repositoryで
LIKE :keywordを使う - SQLパラメーターを使う
- Viewに検索フォームを追加する
- 検索結果が0件の場合はメッセージを表示する
課題19-9 部署ID検索を追加する
Section titled “課題19-9 部署ID検索を追加する”社員名検索に加えて、部署ID検索を追加してください。
条件:
Index(string? keyword, int? departmentId)とする- Viewに部署ID入力欄を追加する
- Repository側で部署ID条件を追加する
- 社員名と部署IDの両方が入力された場合は、両方の条件で絞り込む
課題19-10 エラー時の表示を整える
Section titled “課題19-10 エラー時の表示を整える”DB接続エラーなどが発生したとき、Viewにエラーメッセージを表示してください。
条件:
- Controllerで
try-catchを使う ViewData["ErrorMessage"]を使ってViewに渡す- View側でエラー表示領域を作る
- 学習用として
ErrorDetailも表示してよい
課題19-11 SQL*Plusで同じSQLを確認する
Section titled “課題19-11 SQL*Plusで同じSQLを確認する”Webアプリで使っているSQLをSQL*Plusでも実行し、結果を比較してください。
条件:
- 社員一覧用SQLをSQL*Plusで実行する
- 社員詳細用SQLをSQL*Plusで実行する
- Web画面の結果と一致していることを確認する
- 結果が異なる場合は、SQLとC#コードを確認する
Gitへの提出
Section titled “Gitへの提出”課題が終わったら、できたところまでをGitに提出します。
まず、現在の状態を確認します。
git status変更されたファイルを追加します。
git add .コミットします。
git commit -m "Chapter19 MVCからOracle Databaseを利用"ファイルサーバー上のリポジトリへpushします。
git pushGitの操作でエラーが出た場合は、自己判断で同じ操作を繰り返さず、講師に確認してください。
提出前チェックリスト
Section titled “提出前チェックリスト”提出前に、次の項目を確認してください。
- MVCプロジェクトを作成できている
-
Oracle.ManagedDataAccess.Coreを追加している -
EmployeeListItemModelを作成している -
EmployeeDetailModelを作成している -
EmployeeRepositoryを作成している - ControllerからRepositoryを呼び出している
- ControllerにSQL文を直接書いていない
- Oracle Databaseから社員一覧を取得できている
- Viewに社員一覧を表示できている
- 社員詳細画面を表示できている
- 社員名検索ができている
- 部署ID検索ができている
- DBのNULLを
DBNull.Valueで確認している - SQLパラメーターを使っている
- 文字列連結で検索SQLを作っていない
- DB接続エラー時の表示を確認している
- インデントが整っている
- Gitにcommitしている
- Gitにpushしている
この章のまとめ
Section titled “この章のまとめ”この章では、ASP.NET Core MVCアプリからOracle Databaseを利用する方法を学習しました。
この章で学んだ主な内容は次の通りです。
- MVCアプリからOracle Databaseに接続できる
- Oracle接続には
Oracle.ManagedDataAccess.Coreを利用する EmployeeRepositoryにDBアクセス処理をまとめると、Controllerが読みやすくなる- Controllerは、Repositoryを呼び出してViewへデータを渡す
employeesとdepartmentsをJOINして、社員一覧に部署名を表示できる- SELECT結果を
EmployeeListItemやEmployeeDetailに変換できる - Viewでは
@modelとModelを使って、DBから取得したデータを表示できる - 社員IDを指定して詳細画面を表示できる
- SQLパラメーターを使って、社員名や部署IDで検索できる
- DBのNULLは
DBNull.Valueとして確認する必要がある - Webアプリでは、DB接続エラーが起きたときの表示にも配慮する必要がある
- SQL研修で学んだSELECT、JOIN、WHERE、LIKEは、Webアプリから実行するSQLでもそのまま活用できる
次章では、Webアプリで登録・更新・削除の流れを体験する ことを学習します。
ただし、深く作り込むのではなく、MVCアプリでフォーム入力を受け取り、DBへINSERT / UPDATE / DELETEを行う基本的な流れを確認します。