###### tags: `Docker`, `.NET Core 3.1`, `Synology`, `MVC`, `Oracle`
# 在 .NET Core 3.1 應用程式中整合 Oracle 資料庫
`本文由 Grok AI 協助編輯`
本文假設讀者已完成第一篇「[在 Synology NAS 的 Docker 環境中部署 .NET Core 3.1 應用程式](https://hackmd.io/@chihhaolai/dotnetCore31_docker)」的步驟,並將進一步介紹 Oracle 資料庫整合的開發與部署流程。我們將使用一個簡單的「書籍清單」範例來說明。
---
[toc]
---
:::info
### 實驗環境
- **Synology NAS**: RS3614RPxs / DS720P
- **DSM**: 6.2.4-25556 Update 2
- **Windows 開發環境**: .NET Core 3.1 SDK, Visual Studio 2019
- **資料庫**: Oracle(版本自定,假設運行於獨立伺服器)
:::
---
# 第一部分:應用程式擴展概述
在第一篇文章中,我們部署了一個基本的 ASP.NET Core 3.1 MVC 應用程式(`SimpleWeb`),包含預設的首頁和隱私頁面。本文將擴展此應用程式,新增一個「書籍清單」功能:
- **書籍管理**:連線 Oracle 資料庫,查詢並展示書籍資料。
此功能將透過新增控制器、模型和資料庫上下文實現,並最終部署至 Synology NAS 的 Docker 環境。
## 檔案結構樹狀圖
完成本部分後,專案的檔案結構如下:
```
SimpleWeb/
├── Controllers/
│ ├── BooksController.cs // 書籍控制器
│ ├── HomeController.cs // 首頁控制器
├── Data/
│ ├── OracleDbContext.cs // Oracle 資料庫上下文
├── Models/
│ ├── Book.cs // 書籍模型
│ ├── ErrorViewModel.cs // 錯誤頁面模型
├── Views/
│ ├── Books/
│ │ ├── Index.cshtml // 書籍清單視圖
│ ├── Home/
│ │ ├── Index.cshtml // 首頁
│ │ ├── Privacy.cshtml // 隱私頁面
│ ├── Shared/
│ │ ├── _Layout.cshtml // 共用佈局
│ │ ├── Error.cshtml // 錯誤頁面
├── wwwroot/
│ ├── css/
│ │ └── site.css // 自訂樣式
│ ├── js/
│ │ └── site.js // 自訂腳本
│ ├── lib/
│ │ ├── bootstrap/ // Bootstrap 框架
│ │ ├── jquery/ // jQuery 庫
│ │ ├── jquery-validation/ // 表單驗證
├── appsettings.json // 應用程式配置(包含連線字串)
├── Program.cs // 程式進入點
├── Startup.cs // 服務與中介軟體配置
├── SimpleWeb.csproj // 專案檔案
```
---
# 第二部分:開發 Oracle 資料庫整合功能
## 步驟 1:安裝必要的 NuGet 套件
在 Visual Studio 2019 中,打開 `SimpleWeb` 專案,透過「工具 > NuGet 套件管理員 > 管理解決方案的 NuGet 套件」安裝以下套件:
1. **Oracle 支援**:
- 安裝 `Oracle.EntityFrameworkCore`(版本與 .NET Core 3.1 相容,例如 3.19.0)。
2. **Entity Framework Core**:
- 安裝 `Microsoft.EntityFrameworkCore`(版本 3.1.x,例如 3.1.10)。
或者,在專案根目錄下使用命令列:
```bash
dotnet add package Oracle.EntityFrameworkCore --version 3.19.0
dotnet add package Microsoft.EntityFrameworkCore --version 3.1.10
```
## 步驟 2:定義模型與資料庫上下文
1. **新增模型**:
- 在 `Models` 資料夾中新增 `Book.cs`:
```csharp
namespace SimpleWeb.Models
{
public class Book
{
public int Id { get; set; }
public string Title { get; set; }
public string Author { get; set; }
}
}
```
2. **建立 `Data` 資料夾與 `OracleDbContext`**:
- 在專案根目錄下新增一個名為 `Data` 的資料夾。
- 在 `Data` 資料夾中新增 `OracleDbContext.cs`:
```csharp
using Microsoft.EntityFrameworkCore;
using SimpleWeb.Models;
namespace SimpleWeb.Data
{
public class OracleDbContext : DbContext
{
public OracleDbContext(DbContextOptions<OracleDbContext> options) : base(options) { }
public DbSet<Book> Books { get; set; }
}
}
```
### `Data` 資料夾與 `OracleDbContext` 的用途解釋
- **`Data` 資料夾**:
- 這是一個慣用的資料夾名稱,用於存放與資料存取相關的程式碼,例如資料庫上下文(DbContext)、資料庫遷移檔案(若使用 EF Core 遷移)或其他資料存取邏輯。
- 在本範例中,`Data` 資料夾的作用是集中管理與 Oracle 資料庫交互的類別,使專案結構更清晰。它分離了模型(`Models` 資料夾)和控制器(`Controllers` 資料夾),遵循 MVC 架構的關注點分離原則。
- 若未來需要新增其他資料庫或資料存取服務,可以繼續在此資料夾中擴展。
- **`OracleDbContext` 的用途**:
- `OracleDbContext` 是 Entity Framework Core(EF Core)中的一個類別,繼承自 `DbContext`,用於管理與 Oracle 資料庫的連線和資料操作。
- 它充當應用程式與 Oracle 資料庫之間的橋樑,提供查詢、新增、更新和刪除資料的能力。例如,透過 `DbSet<Book> Books` 屬性,應用程式可以直接操作 `BOOKS` 表格的資料。
- 在建構函數中,`OracleDbContext` 接收 `DbContextOptions<OracleDbContext>`,這是用來配置連線字串和其他資料庫設定的參數(稍後在 `Startup.cs` 中設定)。
## 步驟 3:配置服務與連線字串
1. **編輯 `appsettings.json`**:
- 在專案根目錄的 `appsettings.json` 中新增連線字串:
```json
{
"ConnectionStrings": {
"Oracle": "User Id=<user>;Password=<password>;Data Source=<oracle_host>:<port>/<service_name>"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
```
- 將 `<user>`、`<password>`、`<oracle_host>`、`<port>` 和 `<service_name>` 替換為實際值。例如:`"User Id=scott;Password=tiger;Data Source=192.168.1.100:1521/ORCL"`。
2. **編輯 `Startup.cs`**:
- 在 `ConfigureServices` 方法中註冊資料庫上下文:
```csharp
using SimpleWeb.Data;
using Microsoft.EntityFrameworkCore;
public void ConfigureServices(IServiceCollection services)
{
services.AddControllersWithViews();
services.AddDbContext<OracleDbContext>(options =>
options.UseOracle(Configuration.GetConnectionString("Oracle")));
}
```
## 步驟 4:新增控制器與視圖
1. **新增 `BooksController`**:
- 在 `Controllers` 資料夾中新增 `BooksController.cs`:
```csharp
using Microsoft.AspNetCore.Mvc;
using SimpleWeb.Data;
using SimpleWeb.Models;
using System.Linq;
namespace SimpleWeb.Controllers
{
public class BooksController : Controller
{
private readonly OracleDbContext _oracleDbContext;
public BooksController(OracleDbContext oracleDbContext)
{
_oracleDbContext = oracleDbContext;
}
public IActionResult Index()
{
var books = _oracleDbContext.Books.ToList();
return View(books);
}
[HttpPost]
public IActionResult Add(string title, string author)
{
var book = new Book { Title = title, Author = author };
_oracleDbContext.Books.Add(book);
_oracleDbContext.SaveChanges();
return RedirectToAction("Index");
}
}
}
```
- 在 `Views/Books` 資料夾中新增 `Index.cshtml`:
```html
@model IEnumerable<SimpleWeb.Models.Book>
<h2>Book List</h2>
<form asp-action="Add" method="post">
<input name="title" placeholder="Book Title" />
<input name="author" placeholder="Author" />
<button type="submit">Add Book</button>
</form>
<table class="table">
<thead>
<tr>
<th>ID</th>
<th>Title</th>
<th>Author</th>
</tr>
</thead>
<tbody>
@foreach (var item in Model)
{
<tr>
<td>@item.Id</td>
<td>@item.Title</td>
<td>@item.Author</td>
</tr>
}
</tbody>
</table>
```
##### 模擬網頁輸出樣子
以下是訪問 `http://<NAS_IP>:62005/Books` 時的模擬網頁輸出(假設已新增兩本書並包含初始化資料):
```
Book List
[輸入框: Book Title] [輸入框: Author] [Add Book 按鈕]
ID | Title | Author
---------------------------------------------------------
1 | The Great Gatsby | F. Scott Fitzgerald
2 | 1984 | George Orwell
3 | To Kill a Mockingbird | Harper Lee (假設剛新增)
```
##### 當「Add Book」按鈕按下後的程式運作與模型綁定說明
當使用者按下「Add Book」按鈕時,會觸發以下流程:
1. **表單提交**:
- 使用者在表單中輸入標題(例如 "To Kill a Mockingbird")和作者(例如 "Harper Lee"),按下按鈕後,瀏覽器發送 HTTP POST 請求至 `/Books/Add`,攜帶表單資料:
```
title=To Kill a Mockingbird&author=Harper Lee
```
2. **路由處理**:
- ASP.NET Core 的路由系統根據 URL(`/Books/Add`)和 HTTP 方法(POST),將請求導向 `BooksController.Add` 方法。
3. **模型綁定(Model Binding)**:
- **什麼是模型綁定**:
- 模型綁定是 ASP.NET Core 的內建機制,用於將 HTTP 請求中的資料(例如表單欄位)自動映射到控制器方法的參數。
- 在此例中,表單提交的資料為 `title=To Kill a Mockingbird&author=Harper Lee`,ASP.NET Core 會解析這些鍵值對,並根據參數名稱進行匹配。
- **綁定過程**:
- `Add` 方法的參數 `string title` 和 `string author` 與表單的 `<input name="title">` 和 `<input name="author">` 名稱一致。
- ASP.NET Core 將 `title` 的值 ("To Kill a Mockingbird") 綁定到 `title` 參數,`author` 的值 ("Harper Lee") 綁定到 `author` 參數。
- **名稱匹配的關鍵**:
- 若將參數改為 `string titleName, string authorName`,但表單仍使用 `name="title"` 和 `name="author"`,則綁定失敗,`titleName` 和 `authorName` 會收到 `null`。
- 解決方法是同步修改表單為 `<input name="titleName">` 和 `<input name="authorName">`,或使用 `[FromForm(Name = "title")]` 屬性明確指定綁定來源。
4. **控制器執行**:
- `var book = new Book { Title = title, Author = author };` 創建一個新的 `Book` 物件,`Title` 和 `Author` 分別設為 "To Kill a Mockingbird" 和 "Harper Lee"。
- `_oracleDbContext.Books.Add(book)` 將 `book` 加入資料庫上下文,標記為待新增。
- `_oracleDbContext.SaveChanges()` 生成並執行 SQL:
```sql
INSERT INTO BOOKS (TITLE, AUTHOR) VALUES ('To Kill a Mockingbird', 'Harper Lee');
```
- Oracle 自動生成 ID(例如 3),並由 EF Core 更新至 `book.Id`。
- `RedirectToAction("Index")` 重新導向至 `/Books`,觸發 `Index` 方法顯示更新後的清單。
5. **資料庫交互**:
- EF Core 透過連線字串(來自 `appsettings.json` 或環境變數)與 Oracle 資料庫通訊,完成資料插入。
6. **頁面更新**:
- 瀏覽器收到重新導向指令,載入更新後的 `Index` 頁面,顯示包含新書籍的清單。
---
# 第三部分:更新 Dockerfile
由於使用了 Oracle 資料庫,需確保 Dockerfile 包含必要的依賴。以下是更新後的 `Dockerfile`:
```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:3.1 AS base
WORKDIR /app
EXPOSE 80
# 設置時區
ENV TZ=Asia/Taipei
RUN apt-get update && apt-get install -y tzdata
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# 安裝 Oracle 客戶端依賴
RUN apt-get install -y libaio1
# 設置 Oracle 語言環境
ENV NLS_LANG=AMERICAN_AMERICA.AL32UTF8
# 預設命令
ENTRYPOINT ["dotnet"]
```
## 更新說明
- **Oracle 依賴**:包含 `libaio1` 和 `NLS_LANG` 配置,確保容器內可連線 Oracle 資料庫。
- 其他部分與第一篇一致。
## 重新建構映像
1. 將更新後的 `Dockerfile` 儲存至 `/volume1/docker/dotnet-runtime/`。
2. 在 Synology DSM 的「控制面板 > 工作排程器」中,執行第一篇中配置的 `Build Dotnet Runtime` 任務(或重新執行 `sh /volume1/docker/dotnet-runtime/build.sh`)。
3. 驗證新映像 `dotnet-runtime:3.1` 已更新。
---
# 第四部分:部署流程
## 步驟 1:初始化 Oracle 資料庫
在 Oracle 伺服器上建立表格:
```sql
CREATE TABLE BOOKS (
ID NUMBER GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
TITLE VARCHAR2(255),
AUTHOR VARCHAR2(255)
);
```
- 若需要測試資料,可插入範例:
```sql
INSERT INTO BOOKS (TITLE, AUTHOR) VALUES ('The Great Gatsby', 'F. Scott Fitzgerald');
INSERT INTO BOOKS (TITLE, AUTHOR) VALUES ('1984', 'George Orwell');
COMMIT;
```
## 步驟 2:發佈應用程式
在 Visual Studio 中:
1. 右鍵點擊 `SimpleWeb` 專案,選擇「發佈」。
2. 選擇「資料夾」,設置輸出路徑為 `./publish`。
3. 點擊「發佈」。
或者,使用命令直接發佈至 NAS:
```bash
dotnet publish -c Release -r linux-x64 -f netcoreapp3.1 --self-contained false -o "\\172.19.48.55\docker\SimpleWeb"
```
## 步驟 3:將發佈檔案傳輸到 NAS
若未使用命令直接發佈,手動將 `./publish` 目錄上傳至 `/volume1/docker/mywebapp/`。若已使用命令,則檔案已位於 NAS 的共享路徑。
## 步驟 4:將連線字串移至環境變數
為提高安全性,可將連線字串從 `appsettings.json` 移至環境變數:
1. **移除 `appsettings.json` 中的連線字串**:
- 編輯 `appsettings.json`,移除或註解掉 `ConnectionStrings` 部分:
```json
{
"ConnectionStrings": {
// "Oracle": "User Id=<user>;Password=<password>;Data Source=<oracle_host>:<port>/<service_name>"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}
```
2. **在 Docker 容器中配置環境變數**:
- 在 DSM 的「Docker」套件中,編輯容器設定。
- 在「環境」標籤中新增:
- 變數:`ConnectionStrings__Oracle`
- 值:`User Id=<user>;Password=<password>;Data Source=<oracle_host>:<port>/<service_name>`(例如 `User Id=scott;Password=tiger;Data Source=192.168.1.100:1521/ORCL`)。
3. **運作原理**:
- ASP.NET Core 會從環境變數中讀取 `ConnectionStrings__Oracle`,並映射到 `Configuration.GetConnectionString("Oracle")`,確保應用程式能正常連線。
## 步驟 5:啟動容器
1. 打開 DSM 的「Docker」套件。
2. 在「容器」標籤中,點擊「建立」:
- **映像**:選擇 `dotnet-runtime:3.1`。
- **容器名稱**:`simpleweb-container`。
- **進階設定**:
- **端口設定**:本地端口 `62005`,容器端口 `5000`。
- **卷冊**:本地路徑 `/volume1/docker/mywebapp/`,容器路徑 `/app`。
- **環境**(若使用環境變數):新增 `ConnectionStrings__Oracle`。
- **命令**:`dotnet /app/SimpleWeb.dll`。
3. 點擊「套用」啟動容器。
## 步驟 6:驗證部署
- 訪問 `http://<NAS_IP>:62005/Books` 查看書籍清單。
- 輸入書名和作者,新增書籍並確認資料正確儲存至 Oracle 資料庫。
---
# 第五部分:修復常見問題
1. **Oracle 連線失敗**:
- 檢查 `appsettings.json` 或環境變數中的連線字串是否正確。
- 確保 NAS 的 Docker 網路可訪問 Oracle 伺服器。
2. **資料庫操作異常**:
- 確認 `libaio1` 已正確安裝於容器中。
- 檢查表格結構與模型是否匹配。
---
### 注意事項
1. **資料庫存取**:確保 Oracle 伺服器可從容器內訪問,可能需配置網路橋接。
2. **安全性**:建議使用環境變數儲存連線字串,避免硬編碼。
3. **效能**:若書籍數量增加,可考慮新增分頁查詢。
---
# 結論
本文展示了如何為 .NET Core 3.1 應用程式整合 Oracle 資料庫,並成功部署至 Synology NAS 的 Docker 環境。基於第一篇的基礎,讀者現在可以處理基本的 Oracle 資料庫操作,並根據需求進一步擴展功能。