###### 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 資料庫操作,並根據需求進一步擴展功能。