EF Core 已經不會在 SaveChanges() 的時候對實體模型進行額外驗證 | The Will Will Web

The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

EF Core 已經不會在 SaveChanges() 的時候對實體模型進行額外驗證

以前在使用 EF6 的時候,我就一直覺得有個困擾,那就是我們在 ASP.NET MVC 執行 Model Binding 的時候,除了 MVC 會幫你做輸入驗證模型驗證之外,當你在執行 db.SaveChanges(); 的時候,預設 Entity Framework 還會再讀取一次實體類別(Entity Class)上面定義的 DataAnnotations 定義,例如 [Required][StringLength] 這種,但這真的有點多此一舉。想不到從 EF Core 1.0 開始,這個特性就被移除了,但我 EF Core 6.0 才發現這件事,可見實務上真的很少用到! 😅

說說 Entity Framework 6 的時候問題是如何發生的

我們假設 Action 是這樣定義的:

[HttpPost]
public async ActionResult Create(Course course)
{
    if (ModelState.IsValid)
    {
        db.Add(course);
        db.SaveChanges();
        return RedirectToAction(nameof(Index));
    }

    return View(course);
}

事實上,當 Action 在執行之前,就已經透過 Model Binding 準備好 Course course 的參數值傳入,因此你直接透過 ModelState.IsValid 才能直接取得這個 Course 模型的驗證結果。

然而,想當年 Entity Framework 6 的時候,在執行 db.SaveChanges(); 的時候,還會再次驗證,等於是雙重驗證,但是 99% 的機會是完全用不到的,因為 ASP.NET MVC 已經做過檢查了,不會有問題的,因此很少人知道 Entity Framework 6 原來在儲存的時候,還會再驗證一次!

然而,有種情況是我們之前遇過的,那就是我們的 Action 使用 ViewModel 來接收用戶端傳來的資料,接著透過 AutoMapperValueInjecter 直接轉成實體模型 (Entity Model),然後再透過 Entity Framework 的 db.SaveChanges(); 寫入資料庫。但問題來了,若是 ASP.NET MVC 的 ViewModel 與 EF6 使用的 Model 所定義的驗證屬性不一致,那就尷尬了,開發人員會看到一個相對無法理解的錯誤,錯誤訊息真的非常不清楚:

一個或多個實體的驗證失敗。如需詳細資料,請參閱 'EntityValidationErrors' 屬性。

一個超級不清楚的錯誤,不知道是多少 Entity Framework 初學者的惡夢!😱

看到這個錯誤後,開發人員會想,我的 ModelState.IsValid 明明就已經驗證過了,為什麼 Entity Framework 還會出現 DbEntityValidationException 呢?重點是錯誤訊息提示你去找 EntityValidationErrors,上過我的 ASP.NET MVC 或 ASP.NET Core 課程的學員,應該都知道解決方法,不過很多人並不知道如何繼續查出錯誤的真正原因!

這個錯誤,我有在 修復 Visual Studio 中 ASP.NET MVC 5 專案範本的 Error.cshtml 檔案內容 文章中分享解析 EntityValidationErrors 內容的範例程式,有興趣大家可以看看!另外你也可以參考 余小章 @ 大內殿堂[C#.NET][Entity Framework] EF6 重新引發 DbEntityValidationException 例外 文章,裡面也有提到這個問題的處理方法。

以前我會透過以下技巧,特別關閉 Entity Framework 這部分驗證,讓 Entity Framework 直接產生 CUD (Create, Update, Delete) 的 T-SQL 命令,並藉此理解為何發生錯誤:

context.Configuration.ValidateOnSaveEnabled = false;
context.SaveChanges();
context.Configuration.ValidateOnSaveEnabled = true;

還好,這個 DbEntityValidationException 例外類型已經完全從 Entity Framework Core 移除了! 🤟

說說 Entity Framework Core 如何驗證

從 EF Core 1.0 開始,你在實體模型實體模型的部分類別定義的驗證屬性,通通失效,完全不會做驗證,你可以從這篇討論得到驗證。我覺得這是好事,不用再多一層擔心了!👍

早期我們在 ASP.NET MVC 5 的時候,大多使用 DB First 的方式開發 Entity Framework,我們不會直接對實體模型類別添加驗證屬性,而是在部分類別透過 [MetadataType] 資料存取層!

請注意,這裡的 MetadataType 隸屬於 System.ComponentModel.DataAnnotations 命名空間,他是 .NET Framework 的一部份,專門針對 DB First 這種開發流程特別設計的一個 Attribute,我想使用的人非常多。

不過,這個 MetadataType 從 .NET Core 1.0 到 .NET Core 2.2 都是不存在的,直到 .NET Core 3.0 才被加回來。這是因為從 ASP.NET Core 1.0 開始,ASP.NET Core MVC 改用 Microsoft.AspNetCore.Mvc 命名空間下的 [ModelMetadataType] 類別,其用途與用法與早期的 [MetadataType] 如出一轍,只有命名變更而已。因此,如果你寫的是 ASP.NET Core,請務必記得改用 [ModelMetadataType] 來宣告 Buddy Class!

有趣的地方在於,為什麼 .NET Core 3.0 開始要加回來?誰要用?總之,微軟是不打算用了,要不是有人吵,怎麼可能會移除後又加回來! 😅

目前我所知道的,就只有 Json.NET 會用到,詳細用法請見我的另一篇文章:認識 Entity Framework Core 載入關聯資料的三種方法,文章內有寫到使用的情境與範例程式。

但請記得,也只有在使用 Json.NET 對 JSON 資料序列化的時候才會用到 [MetadataType] 這個屬性,因為太多人依賴這個功能了。除此之外,ASP.NET MVC 的 Model Binding 的輸入驗證,請一律使用 [ModelMetadataType] 來宣告喔!🔥

相關連結