我在 2013 年的 ASP.NET MVC 開發心得分享 (24):擴充部分類別的建構子 文章中,是我第一次介紹 .NET Framework 年代的 ASP.NET MVC 如何替「實體資料模型」(Entity Data Model) 在有使用 EDMX 自動產生程式碼的情況下如何還能擴充現有的「實體模型類別」(Entity Model Class) 的「驗證屬性」(Validation Attributes)。但直到 ASP.NET Core 的 MVC 開始,作法有點改變了,這篇文章我來記錄一下這些變化的地方。
準備範例程式
我為這篇文章準備了一個 ASP.NET Core 6 的範例專案,所有程式碼都會從 master
分支的最新版開始改起,初始化專案的步驟如下:
-
取回原始碼
git clone https://github.com/doggy8088/EFCore6Demo.git
cd EFCore6Demo
dotnet build
這份原始碼已經包含了一個 ContosoUniversity.db
資料庫檔案 (SQLite)!
備註: 如果你有修改 Entity Framework Core 的實體模型,才需要用以下命令更新資料庫。
dotnet ef migrations add MIGRATION_NAME
dotnet ef database update -v
-
開啟 VS Code
code .
-
啟動專案
dotnet watch run
-
呼叫 API
你可以直接用瀏覽器打開 https://localhost:7295/api/Departments/1 即可看到結果。
也可以透過 curl
進行測試:
curl -s "https://localhost:7295/api/Departments/1"
如果你在 Windows PowerShell 下執行上述命令,可能會看到類似 cmdlet Invoke-WebRequest at command pipeline position 1
的訊息而無法執行。若有這種狀況,請先執行 Remove-Item Alias:curl
移除掉系統內建的 curl
命令別名,然後再重新執行一次相同命令即可。
你會得到以下結果:
{
"departmentId": 1,
"name": "教育訓練部",
"budget": 1000,
"startDate": "2022-04-20T23:38:36.372125",
"instructorId": null,
"instructor": null,
"courses": []
}
這裡你可以看到 courses
這個屬性回傳一個「空陣列」,這意味著 Entity Framework Core 並不會透過「導覽屬性」載入 Courses
的關聯資料。
-
按下 Ctrl-C
關閉 dotnet watch run
執行
替現有的「實體模型類別」擴充「驗證屬性」
由於我們在本專案的 Models
資料夾下,所有的 實體模型類別 (Entity Model Class) 與 ContosoUniversityContext
類別都是透過 dotnet ef dbcontext scaffold
或 EF Core Power Tools 這類工具自動產生的,這類自動產生的程式碼,理論上我們不應該去手動修改這些類別,以免日後資料庫更新時這些客製化後的程式碼被覆蓋掉,那就不妙了。
因此,我們如果真的要對這些「實體模型類別」(Entity Model Class) 特別加上「驗證屬性」(Validation Attributes) 的話,就需要用到 buddy class 的開發技巧,透過 partial class (部分類別) 的擴充,將驗證屬性定義到另一個不會被程式碼產生器工具重新產生模型類別而覆蓋的檔案中!
這裡我以 Models/Department.cs
檔案為例,原始碼如下:
using System;
using System.Collections.Generic;
namespace EFCore6Demo.Models
{
public partial class Department
{
public Department()
{
Courses = new HashSet<Course>();
}
public int DepartmentId { get; set; }
public string? Name { get; set; }
public decimal Budget { get; set; }
public DateTime StartDate { get; set; }
public int? InstructorId { get; set; }
public virtual Person? Instructor { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
}
我要擴充 [Required]
驗證屬性到 Name
、Budget
與 StartDate
屬性的話,就會建立另一個 Models/Department.Partial.cs
檔案 (檔名不同),其內容如下:
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
#nullable disable
namespace EFCore6Demo.Models
{
[ModelMetadataType(typeof(DepartmentMetadata))]
public partial class Department
{
}
internal class DepartmentMetadata
{
public Int32 DepartmentId { get; set; }
[Required]
public String Name { get; set; }
[Required]
public Decimal Budget { get; set; }
[Required]
public DateTime StartDate { get; set; }
public Nullable<Int32> InstructorId { get; set; }
}
}
以上就是替現有的「實體模型類別」擴充「驗證屬性」的標準方法!
介紹 Duotify.EFCore.EntityPartialGenerator 工具
可以想見,替每一個實體模型類別擴充驗證屬性這種方法是一種苦工,要是我有 100 個實體資料模型呢?那我不就要重複的工作做的一百遍嗎?!
沒錯,我就是專門為了這個需求,設計了一款 Duotify.EFCore.EntityPartialGenerator 工具,他會全自動的將目前專案中使用到的 DbContext 類別,自動分析出所有實體模型類別,並且自動產生上述程式碼的骨架,方便我們日後隨時可以擴充驗證屬性!😍
以下就是這個專案的使用方式:
-
安裝工具
dotnet tool install -g Duotify.EFCore.EntityPartialGenerator
-
查詢使用方式
efp
efp
is stands for Entity Framework Partial class generator.
Usage: efp generate -c ContosoUniversityContext -v -o Models
Commands:
list: Lists available DbContext types.
generate: Generate a set of "buddy class" for all entity types in a DbContext
-
列出專案中所有 DbContext 資料內容類別
efp list
輸出的 ContosoUniversityContext
就是你目前專案用到的 DbContext 類別:
G:\Projects\EFCore6Demo>efp list
Build started...
Build succeeded.
ContosoUniversityContext
-
自動產生實體模型類別的 "buddy class"
如果專案只有一個 DbContext 類別,而且預設輸出目錄也在 Models 的話,你可以這樣執行命令:
efp generate
否則你也可以寫比較完整的命令如下:
efp generate -c ContosoUniversityContext -o Models
執行前會先建置專案,如果專案無法成功建置就無法產生程式碼!🔥
如果想在產生程式碼的同時也顯示產生的檔案名稱,可以加上 -v
參數:
efp generate -c ContosoUniversityContext -o Models -v
如果想重新產生程式碼,且要覆寫現有已存在的檔案,可以加上 -f
參數:
efp generate -c ContosoUniversityContext -o Models -v -f
相關連結
- 開源專案 ▶ https://github.com/doggy8088/Duotify.EFCore.EntityPartialGenerator
- NuGet 套件 ▶ https://www.nuget.org/packages/Duotify.EFCore.EntityPartialGenerator