Skip to content

第19章 WebアプリからOracle Databaseを利用する

この章では、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文を活かし、OracleConnectionOracleCommandOracleDataReader を使って、比較的レガシーなADO.NET形式でDBアクセスを行います。

Webアプリの目的は、MVCの構成を理解することです。
DBアクセスの目的は、SQLで取得した結果をWeb画面に表示する流れを理解することです。


この章では、次の画像を入れると理解しやすくなります。

図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検索の画面

画像を挿入する場合は、次のように書けます。

![MVCアプリとOracle Databaseの接続イメージ](images/19_mvc_oracle_overview.png)

画像ファイルは、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の役割分担を説明できる

項目内容
開発環境Visual Studio 2022
プロジェクト種類ASP.NET Core Web アプリ Model-View-Controller
対象フレームワーク.NET 8
プロジェクト名Chapter19_MvcOracle
DBOracle Database
接続ユーザーpingt
使用する表employeesdepartments
追加するNuGetパッケージOracle.ManagedDataAccess.Core

作業を始める前に、次の内容を確認してください。

  • SQL*Plusで pingt ユーザーに接続できる
  • employees 表をSELECTできる
  • departments 表をSELECTできる
  • コンソールアプリからOracle Databaseに接続できる
  • ASP.NET Core MVCプロジェクトを作成できる
  • Controller、Model、Viewの役割を説明できる
  • List<T> をViewに渡して一覧表示できる
  • OracleConnectionOracleCommandOracleDataReader の基本を理解している
  • 第18章の内容をGitに提出済みである

19-1 WebアプリからDBを利用する流れ

Section titled “19-1 WebアプリからDBを利用する流れ”

第18章では、社員一覧を表示するために、Controllerの中でサンプルデータを作成しました。

public IActionResult Index()
{
List<EmployeeListItem> employees = CreateSampleEmployees();
return View(employees);
}

この方法は、MVCの流れを理解するためには分かりやすいです。

しかし、実際の業務アプリでは、社員情報はソースコードの中に直接書かれているわけではありません。

多くの場合、データベースから取得します。


この章では、次のような流れに変更します。

ブラウザー
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プロジェクトを作成する”

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プロジェクトの作成は成功です。


プロジェクトには、主に次のフォルダがあります。

Controllers
Models
Views
wwwroot

この章では、さらに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接続用パッケージを追加する”

MVCアプリからOracle Databaseに接続するために、NuGetパッケージを追加します。

使用するパッケージは、コンソールアプリで使ったものと同じです。

Oracle.ManagedDataAccess.Core

Visual Studioで次の手順を行います。

1. ソリューションエクスプローラーでプロジェクト名を右クリックする
2. 「NuGet パッケージの管理」をクリックする
3. 「参照」タブを開く
4. 検索欄に Oracle.ManagedDataAccess.Core と入力する
5. Oracle.ManagedDataAccess.Core を選択する
6. 「インストール」をクリックする
7. 確認画面が出たら内容を確認して進める

図19-2 挿入候補

NuGetで Oracle.ManagedDataAccess.Core を検索・追加している画面を入れるとよいです。


Oracle接続を行うクラスでは、次の using を使います。

using Oracle.ManagedDataAccess.Client;

この using により、次のクラスを使えるようになります。

OracleConnection
OracleCommand
OracleDataReader
OracleException
OracleDbType

まず、社員一覧画面に表示する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_id
employee_name
department_name
hiredate
salary

次に、社員詳細画面用の 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
部署名

一覧画面と詳細画面では、必要な情報が異なります。

一覧画面では、見やすさを優先して必要最小限の情報を表示します。

社員ID
社員名
部署名
入社日
給与

詳細画面では、1人分の情報を詳しく表示します。

よみがな
職種ID
上司ID
歩合
部署ID

このように、画面ごとに必要なデータの形を分けると、Viewが扱いやすくなります。


ソリューションエクスプローラーで、プロジェクトを右クリックし、Repositories フォルダを作成します。

Chapter19_MvcOracle
└─ Repositories

その中に、EmployeeRepository.cs を作成します。


まず、次のように基本形を作成します。

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 は、社員データを取得する役割を持つクラスです。


接続文字列は、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から取得する”

社員一覧では、employees 表と departments 表をJOINして、部署名も一緒に取得します。

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

このSQLは、SQL研修で学習したJOINをC#から実行するものです。


EmployeeRepositoryGetEmployeeListItems メソッドを次のように作成します。

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データをオブジェクトとして扱う」流れと同じです。


salary はNULLになる可能性があります。

そのため、次のように DBNull.Value を確認しています。

employee.Salary = reader["salary"] == DBNull.Value
? null
: Convert.ToDecimal(reader["salary"]);

DBのNULLをC#の null として扱うため、EmployeeListItemSalarydecimal? にしています。

public decimal? Salary { get; set; }

19-7 EmployeesControllerからRepositoryを使う

Section titled “19-7 EmployeesControllerからRepositoryを使う”

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では、次のことを行っています。

1. EmployeeRepositoryを作成する
2. 社員一覧を取得する
3. ViewへList<EmployeeListItem>を渡す
4. エラーが発生した場合はエラーメッセージをViewに渡す

重要なのは、ControllerにSQL文を書いていないことです。

Controller
→ 画面表示の流れを担当
Repository
→ DBアクセスを担当

役割を分けることで、コードの見通しがよくなります。


この章では、学習しやすいように ex.Message をViewへ渡しています。

ViewData["ErrorDetail"] = ex.Message;

ただし、実務では、詳細な例外メッセージを利用者にそのまま見せないことが多いです。

接続文字列、SQL文、内部情報が含まれる可能性があるためです。

実務では、利用者には簡単なメッセージを表示し、詳細はログに残す設計が一般的です。


Views/Employees/Index.cshtmlを作成する

Section titled “Views/Employees/Index.cshtmlを作成する”

Views フォルダの中に Employees フォルダを作成し、その中に Index.cshtml を作成します。

Views
└─ Employees
└─ 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/Employees

Oracle 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から取得する”

社員詳細では、社員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_name
FROM
employees e
JOIN departments d
ON e.department_id = d.department_id
WHERE
e.employee_id = :employeeId

条件値は、文字列連結ではなくSQLパラメーターで渡します。


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 = true;

Oracleでは、パラメーターを名前で対応させたい場合に、この指定を入れておくと分かりやすくなります。

この研修では、SQLパラメーターを使うときは、基本的に次のように書く方針にします。

command.BindByName = true;
command.Parameters.Add("employeeId", OracleDbType.Int32).Value = employeeId;

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

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>

アプリを実行し、社員一覧画面から「詳細」リンクをクリックします。

または、次のURLに直接アクセスします。

https://localhost:xxxx/Employees/Details/1001

社員詳細が表示されれば成功です。

図19-6 挿入候補

社員詳細画面のスクリーンショットを入れるとよいです。


一覧取得メソッドに検索条件を追加する

Section titled “一覧取得メソッドに検索条件を追加する”

社員名で部分一致検索できるようにします。

EmployeeRepositoryGetEmployeeListItems を、次のように変更します。

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

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

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>

アプリを実行し、社員名の一部を入力して検索します。

例:

次のように、社員名に を含む社員だけが表示されれば成功です。

山田二郎
山口洋子

図19-7 挿入候補

社員名検索の入力欄と検索結果が分かる画面を入れるとよいです。


社員名に加えて、部署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;
}

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

検索フォームに部署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件の場合の表示を確認します。

@if (Model.Count == 0)
{
<p>表示する社員データがありません。</p>
}
else
{
<table class="table">
...
</table>
}

検索条件に合うデータがない場合に、利用者へ分かりやすく表示することは重要です。


この章の最終的な構成は次の通りです。

Controllers
└─ EmployeesController.cs
Models
├─ EmployeeListItem.cs
└─ EmployeeDetail.cs
Repositories
└─ EmployeeRepository.cs
Views
└─ Employees
├─ Index.cshtml
└─ Details.cshtml

それぞれの役割は次の通りです。

ファイル役割
EmployeesControllerリクエストを受け取り、Repositoryを呼び出してViewにデータを渡す
EmployeeRepositoryOracle 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章と第19章の違いを整理します。

項目第18章第19章
データ取得元Controller内のサンプルデータOracle Database
DB接続なしあり
DBアクセスクラスなしEmployeeRepository
一覧表示サンプルの List<EmployeeListItem>DBから取得した List<EmployeeListItem>
詳細表示サンプルデータから検索DBから社員IDで検索
検索LINQでサンプルデータを検索SQLパラメーターでDB検索

第19章では、MVCの構成は保ったまま、データ取得元だけをDBへ変更しました。


この章でよくあるつまずきを確認します。

つまずき原因対応
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側のエラーエラー番号とメッセージを確認する

次の項目について、自分で説明できるか確認してください。

  • MVCアプリからOracle Databaseに接続できる
  • MVCプロジェクトにOracle接続用NuGetパッケージを追加できる
  • EmployeeRepository の役割を説明できる
  • ControllerとRepositoryの役割の違いを説明できる
  • employeesdepartments をJOINして一覧データを取得できる
  • DBの1行を EmployeeListItem に変換できる
  • List<EmployeeListItem> をViewに渡せる
  • ViewでDBから取得した社員一覧を表示できる
  • 社員IDを指定して詳細データを取得できる
  • EmployeeDetail? を使い、見つからない場合に対応できる
  • SQLパラメーターを使って検索条件を渡せる
  • DBNull.Value を確認してNULLに対応できる
  • DB接続エラーが起きたときに確認すべき点を説明できる

研修の進め方によっては、隣の人または近くの人と説明確認を行います。

次の内容を、自分の言葉で説明してください。

  1. 第18章ではサンプルデータだった部分を、この章では何に置き換えましたか。
  2. EmployeeRepository は何を担当するクラスですか。
  3. ControllerにSQL文を直接書かない理由は何ですか。
  4. EmployeeListItemEmployeeDetail を分ける理由は何ですか。
  5. Index.cshtml@model List<EmployeeListItem> は何を意味していますか。
  6. DBのNULLをC#で扱うとき、どのような確認が必要ですか。
  7. 社員名検索でSQLパラメーターを使う理由は何ですか。
  8. 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 クラスを作成してください。

プロパティ:

プロパティ名
EmployeeIdint
EmployeeNamestring
DepartmentNamestring
HireDateDateTime
Salarydecimal?

条件:

  • Models フォルダに作成する
  • Salarydecimal? にする

課題19-4 EmployeeRepositoryを作成する

Section titled “課題19-4 EmployeeRepositoryを作成する”

Repositories フォルダを作成し、EmployeeRepository クラスを作成してください。

条件:

  • Repositories フォルダを作る
  • EmployeeRepository.cs を作る
  • 接続文字列をフィールドとして定義する
  • GetEmployeeListItems メソッドを作成する

課題19-5 DBから社員一覧を取得して表示する

Section titled “課題19-5 DBから社員一覧を取得して表示する”

employeesdepartments を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 クラスを作成してください。

プロパティ:

プロパティ名
EmployeeIdint
EmployeeNamestring
Yomistring?
JobIdint
ManagerIdint?
HireDateDateTime
Salarydecimal?
Commissiondecimal?
DepartmentIdint
DepartmentNamestring

課題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に提出します。

まず、現在の状態を確認します。

Terminal window
git status

変更されたファイルを追加します。

Terminal window
git add .

コミットします。

Terminal window
git commit -m "Chapter19 MVCからOracle Databaseを利用"

ファイルサーバー上のリポジトリへpushします。

Terminal window
git push

Gitの操作でエラーが出た場合は、自己判断で同じ操作を繰り返さず、講師に確認してください。


提出前に、次の項目を確認してください。

  • MVCプロジェクトを作成できている
  • Oracle.ManagedDataAccess.Core を追加している
  • EmployeeListItem Modelを作成している
  • EmployeeDetail Modelを作成している
  • EmployeeRepository を作成している
  • ControllerからRepositoryを呼び出している
  • ControllerにSQL文を直接書いていない
  • Oracle Databaseから社員一覧を取得できている
  • Viewに社員一覧を表示できている
  • 社員詳細画面を表示できている
  • 社員名検索ができている
  • 部署ID検索ができている
  • DBのNULLを DBNull.Value で確認している
  • SQLパラメーターを使っている
  • 文字列連結で検索SQLを作っていない
  • DB接続エラー時の表示を確認している
  • インデントが整っている
  • Gitにcommitしている
  • Gitにpushしている

この章では、ASP.NET Core MVCアプリからOracle Databaseを利用する方法を学習しました。

この章で学んだ主な内容は次の通りです。

  • MVCアプリからOracle Databaseに接続できる
  • Oracle接続には Oracle.ManagedDataAccess.Core を利用する
  • EmployeeRepository にDBアクセス処理をまとめると、Controllerが読みやすくなる
  • Controllerは、Repositoryを呼び出してViewへデータを渡す
  • employeesdepartments をJOINして、社員一覧に部署名を表示できる
  • SELECT結果を EmployeeListItemEmployeeDetail に変換できる
  • Viewでは @modelModel を使って、DBから取得したデータを表示できる
  • 社員IDを指定して詳細画面を表示できる
  • SQLパラメーターを使って、社員名や部署IDで検索できる
  • DBのNULLは DBNull.Value として確認する必要がある
  • Webアプリでは、DB接続エラーが起きたときの表示にも配慮する必要がある
  • SQL研修で学んだSELECT、JOIN、WHERE、LIKEは、Webアプリから実行するSQLでもそのまま活用できる

次章では、Webアプリで登録・更新・削除の流れを体験する ことを学習します。

ただし、深く作り込むのではなく、MVCアプリでフォーム入力を受け取り、DBへINSERT / UPDATE / DELETEを行う基本的な流れを確認します。