6

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

 2 years ago
source link: https://blog.miniasp.com/post/2022/04/23/EF-Core-has-no-ValidateOnSaveEnabled-anymore
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

以前在使用 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] 來宣告喔!🔥


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK