﻿# 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 是這樣定義的：

```csharp
[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** 來接收用戶端傳來的資料，接著透過 [AutoMapper](https://automapper.org/) 或 [ValueInjecter](https://github.com/omuleanu/ValueInjecter) 直接轉成**實體模型** (Entity Model)，然後再透過 Entity Framework 的 `db.SaveChanges();` 寫入資料庫。但問題來了，若是 ASP.NET MVC 的 **ViewModel** 與 EF6 使用的 **Model** 所定義的**驗證屬性**不一致，那就尷尬了，開發人員會看到一個相對無法理解的錯誤，錯誤訊息真的非常不清楚：

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

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

看到這個錯誤後，開發人員會想，我的 `ModelState.IsValid` 明明就已經驗證過了，為什麼 Entity Framework 還會出現 [DbEntityValidationException](https://learn.microsoft.com/zh-tw/dotnet/api/system.data.entity.validation.dbentityvalidationexception?view=entity-framework-6.2.0&WT.mc_id=DT-MVP-4015686) 呢？重點是錯誤訊息提示你去找 `EntityValidationErrors`，上過我的 ASP.NET MVC 或 ASP.NET Core 課程的學員，應該都知道解決方法，不過很多人並不知道如何繼續查出錯誤的真正原因！

這個錯誤，我有在 [修復 Visual Studio 中 ASP.NET MVC 5 專案範本的 Error.cshtml 檔案內容](/post/2022/03/26/Fixing-Visual-Studio-ASPNET-MVC-5-Project-Template-Wrong-Error-View) 文章中分享解析 `EntityValidationErrors` 內容的範例程式，有興趣大家可以看看！另外你也可以參考 [余小章 @ 大內殿堂](https://dotblogs.com.tw/yc421206) 的 [\[C#.NET\]\[Entity Framework\] EF6 重新引發 DbEntityValidationException 例外](https://dotblogs.com.tw/yc421206/2015/11/02/153774) 文章，裡面也有提到這個問題的處理方法。

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

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

還好，這個 [DbEntityValidationException](https://learn.microsoft.com/zh-tw/dotnet/api/system.data.entity.validation.dbentityvalidationexception?view=entity-framework-6.2.0&WT.mc_id=DT-MVP-4015686) 例外類型已經完全從 Entity Framework Core 移除了！ 🤟

### 說說 Entity Framework Core 如何驗證

從 EF Core 1.0 開始，你在**實體模型**或**實體模型的部分類別**定義的驗證屬性，通通失效，完全不會做驗證，你可以從[這篇討論](https://github.com/dotnet/efcore/issues/5224#issuecomment-215847094)得到驗證。我覺得這是好事，不用再多一層**擔心**了！👍

早期我們在 ASP.NET MVC 5 的時候，大多使用 DB First 的方式開發 Entity Framework，我們不會直接對**實體模型類別**添加**驗證屬性**，而是在**部分類別**透過 [\[MetadataType\]](https://learn.microsoft.com/zh-tw/dotnet/api/system.componentmodel.dataannotations.metadatatypeattribute?view=net-6.0&WT.mc_id=DT-MVP-4015686) 資料存取層！

請注意，這裡的 `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\]](https://learn.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.mvc.modelmetadatatypeattribute?view=aspnetcore-6.0&WT.mc_id=DT-MVP-4015686) 類別，其用途與用法與早期的 `[MetadataType]` 如出一轍，只有命名變更而已。因此，如果你寫的是 ASP.NET Core，請務必記得改用 `[ModelMetadataType]` 來宣告 Buddy Class！

有趣的地方在於，為什麼 .NET Core 3.0 開始要加回來？誰要用？總之，微軟是不打算用了，要不是[有人吵](https://github.com/dotnet/runtime/issues/26012)，怎麼可能會移除後又加回來！ 😅

目前我所知道的，就只有 [Json.NET](https://www.newtonsoft.com/json) 會用到，詳細用法請見我的另一篇文章：[認識 Entity Framework Core 載入關聯資料的三種方法](/post/2022/04/21/Loading-Related-Data-in-EF-Core)，文章內有寫到使用的情境與範例程式。

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

### 相關連結

-   [\[Port Request\] Add MetadataTypeAttribute to System.ComponentModel.Annotations · Issue #26012 · dotnet/runtime](https://github.com/dotnet/runtime/issues/26012)
-   [System.Text.Json.Serialization.JsonIgnoreAttribute not working in asp.net Core 3.1 partial ModelMetadataType class · Issue #17891 · dotnet/aspnetcore](https://github.com/dotnet/aspnetcore/issues/17891)
-   [\[JsonIgnore\] not working with EF Core types when requesting single resource when Lazy Loading is enabled (.NET 5) · Issue #31396 · dotnet/aspnetcore](https://github.com/dotnet/aspnetcore/issues/31396)
-   [json.net - .net Core 3 \[JsonIgnore\] not working when requesting single resource - Stack Overflow](https://stackoverflow.com/questions/60184661/net-core-3-jsonignore-not-working-when-requesting-single-resource)
