備註
僅限 EF4.1 及更新 版本 - 在 Entity Framework 4.1 中介紹了此頁面所討論的功能、API 等。 如果您使用舊版,部分或全部資訊不適用
此頁面的內容是改編自裘莉·勒曼https://thedatafarm.com()最初撰寫的文章。
Entity Framework 提供各種不同的驗證功能,可饋送至使用者介面以進行客戶端驗證,或用於伺服器端驗證。 第一次使用程式代碼時,您可以使用註釋或 Fluent API 組態來指定驗證。 您可以在程式代碼中指定其他驗證以及更複雜的驗證,這些驗證將可作用,不論您的模型是從程式代碼優先、模型優先還是資料庫優先的方法產生。
模型
我會使用簡單的類別來示範驗證:部落格和文章。
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string BloggerName { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public ICollection<Comment> Comments { get; set; }
}
數據批注
Code First 使用來自 System.ComponentModel.DataAnnotations 程式集的批註作為設定 Code First 類別的一種方法。 在這些批註中,有些提供了規則,例如 Required、MaxLength 和 MinLength。 許多 .NET 用戶端應用程式也會辨識這些批註,例如,ASP.NET MVC。 您可以使用這些批注來達成客戶端端驗證。 例如,您可以將 Blog Title 屬性強制為必要屬性。
[Required]
public string Title { get; set; }
由於應用程式中沒有任何額外的程式代碼或標記變更,現有的MVC應用程式會執行客戶端驗證,甚至會使用屬性和批註名稱動態建置訊息。
在此建立檢視的回傳方法中,Entity Framework 被使用來將新的部落格儲存至資料庫,但在應用程式進入程式代碼之前,MVC 的客戶端驗證已經被觸發。
不過,客戶端驗證並非萬無一失。 使用者可能會影響瀏覽器的功能,或者更糟的是,駭客可能會使用一些技巧來避免UI驗證。 但 Entity Framework 也會辨識 Required 批注並加以驗證。
測試此作業的簡單方式是停用MVC的客戶端驗證功能。 您可以在MVC應用程式的 web.config 檔案中執行此動作。 appSettings 區段具有 ClientValidationEnabled 的索引鍵。 將此金鑰設定為 false 會防止 UI 執行驗證。
<appSettings>
<add key="ClientValidationEnabled"value="false"/>
...
</appSettings>
即使客戶端驗證已停用,您也會在應用程式中得到相同的回應。 錯誤訊息「標題欄位是必填的」將像之前一樣顯示。 如今,這將是伺服器端驗證所產生的結果。 Entity Framework 會在批註 Required 上執行驗證,(甚至在建立 INSERT 命令以傳送到資料庫之前),並將錯誤傳回 MVC,以顯示訊息。
流暢接口(Fluent API)
您可以使用 Code First 的 Fluent API 來代替標註,以便取得相同的客戶端和伺服器端驗證。 我不會使用 Required,而是使用 MaxLength 驗證來向您顯示。
在從類別建置模型時,會套用 Fluent API 組態。 您可以覆寫 DbContext 類別的 OnModelCreating 方法來插入設定。 以下是一個組態,指定 BloggerName 屬性不能超過 10 個字元。
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
public DbSet<Comment> Comments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>().Property(p => p.BloggerName).HasMaxLength(10);
}
}
根據 Fluent API 設定擲回的驗證錯誤不會自動到達 UI,但您可以在程式碼中捕捉這些錯誤,並做出相應的回應。
以下是應用程式 BlogController 類別中一些例外狀況處理的代碼,它會在 Entity Framework 嘗試儲存含有超過 10 字元的 BloggerName 的部落格時擷取該驗證錯誤。
[HttpPost]
public ActionResult Edit(int id, Blog blog)
{
try
{
db.Entry(blog).State = EntityState.Modified;
db.SaveChanges();
return RedirectToAction("Index");
}
catch (DbEntityValidationException ex)
{
var error = ex.EntityValidationErrors.First().ValidationErrors.First();
this.ModelState.AddModelError(error.PropertyName, error.ErrorMessage);
return View();
}
}
驗證不會自動傳回視圖,因此需要使用附加的程式碼 ModelState.AddModelError。 這可確保錯誤詳細資訊會傳遞到檢視,以便使用 ValidationMessageFor Htmlhelper 來顯示錯誤。
@Html.ValidationMessageFor(model => model.BloggerName)
IValidatableObject 物件
IValidatableObject 是存在於 System.ComponentModel.DataAnnotations 中的介面。 雖然它不屬於 Entity Framework API 的一部分,但您仍然可以將其用於 Entity Framework 類別中的伺服器端驗證。
IValidatableObject
Validate提供 Entity Framework 會在 SaveChanges 期間呼叫的方法,或者您可以隨時呼叫自己來驗證類別。
Required 和 MaxLength 等設定會在單一欄位上執行驗證。 在 Validate 方法中,您可以有更複雜的邏輯,例如比較兩個欄位。
在下列範例中,類別Blog已擴充以實作IValidatableObject,然後提供Title和BloggerName不可匹配的規則。
public class Blog : IValidatableObject
{
public int Id { get; set; }
[Required]
public string Title { get; set; }
public string BloggerName { get; set; }
public DateTime DateCreated { get; set; }
public virtual ICollection<Post> Posts { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
if (Title == BloggerName)
{
yield return new ValidationResult(
"Blog Title cannot match Blogger Name",
new[] { nameof(Title), nameof(BloggerName) });
}
}
}
ValidationResult 構造函式需要一個代表錯誤訊息的 string 和一個表示與驗證相關聯成員名稱的 string 陣列。 由於此驗證會同時檢查 Title 和 BloggerName,因此會傳回這兩個屬性名稱。
不同於 Fluent API 提供的驗證,此驗證結果將被檢視和例外狀況處理程式識別,而我之前用來將錯誤添加至 ModelState 的做法則是多餘的。 由於我在 中 ValidationResult設定這兩個屬性名稱,因此MVC HtmlHelpers會顯示這兩個屬性的錯誤訊息。
DbContext.ValidateEntity
DbContext 具有稱為 ValidateEntity的可覆寫方法。 當您呼叫 SaveChanges時,Entity Framework 會針對其快取中的每個實體呼叫這個方法,其狀態不是 Unchanged。 您可以將驗證邏輯直接放在此處,甚至可以使用這個方法來呼叫,例如在上一節中新增的 Blog.Validate 方法。
這裡有一個 ValidateEntity 覆寫的範例,用於驗證新 Post,以確保文章標題尚未被使用。 它會先檢查實體是否為貼文,且其狀態為 [已新增]。 如果是這種情況,則會在資料庫中查看是否有具有相同標題的貼文。 如果已經有現有的文章,則會建立新的 DbEntityValidationResult 文章。
DbEntityValidationResult 容納一個 DbEntityEntry 和一個 ICollection<DbValidationErrors> 給單一實體。 在此方法開始時,會首先實例化 DbEntityValidationResult,並將發現的任何錯誤新增至其 ValidationErrors 集合中。
protected override DbEntityValidationResult ValidateEntity (
System.Data.Entity.Infrastructure.DbEntityEntry entityEntry,
IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
if (entityEntry.Entity is Post post && entityEntry.State == EntityState.Added)
{
// Check for uniqueness of post title
if (Posts.Where(p => p.Title == post.Title).Any())
{
result.ValidationErrors.Add(
new System.Data.Entity.Validation.DbValidationError(
nameof(Title),
"Post title must be unique."));
}
}
if (result.ValidationErrors.Count > 0)
{
return result;
}
else
{
return base.ValidateEntity(entityEntry, items);
}
}
直接觸發驗證
呼叫 SaveChanges 會觸發本文涵蓋的所有驗證。 但您不需要依賴 SaveChanges。 您可能偏好在應用程式中的其他地方進行驗證。
DbContext.GetValidationErrors 會觸發所有驗證、註釋或 Fluent API 所定義的驗證、在 IValidatableObject 中建立的驗證(例如, Blog.Validate),以及方法中 DbContext.ValidateEntity 執行的驗證。
下列程式代碼會在GetValidationErrors的目前實體上呼叫DbContext。
ValidationErrors 依實體類型群組為 DbEntityValidationResult。 程式碼會先逐一遍歷 DbEntityValidationResult 方法所傳回的項目,接著遍歷每個內部的 DbValidationError。
foreach (var validationResult in db.GetValidationErrors())
{
foreach (var error in validationResult.ValidationErrors)
{
Debug.WriteLine(
"Entity Property: {0}, Error {1}",
error.PropertyName,
error.ErrorMessage);
}
}
使用驗證時的其他考慮
以下是使用 Entity Framework 驗證時要考慮的一些其他要點:
- 在驗證過程中停用了延遲載入
- EF 會驗證非對應屬性上的資料批註(未對應至資料庫中資料行的屬性)
- 在
SaveChanges偵測到變更之後,就會執行驗證。 如果您在驗證期間進行變更,您必須負責通知變更追蹤器 -
DbUnexpectedValidationException如果在驗證期間發生錯誤,則會擲回 - Entity Framework 包含在模型中的面向(長度上限、必要等)都會導致驗證,即便您的類別和/或您使用 EF 設計工具建立模型時沒有數據批注,也一樣。
- 優先順序規則:
- Fluent API 呼叫會覆寫對應的數據註解
- 執行順序:
- 在類型驗證之前發生屬性驗證
- 只有在屬性驗證成功時,才會發生類型驗證
- 如果屬性很複雜,其驗證也會包含:
- 複雜類型屬性的屬性層級驗證
- 複雜類型的型別層級驗證,包括對複雜類型的
IValidatableObject驗證
總結
Entity Framework 中的驗證 API 與 MVC 中的用戶端驗證搭配運作非常好,但您不需要依賴客戶端驗證。 Entity Framework 會負責處理伺服器端的驗證,無論您使用的是 DataAnnotations 還是程式碼優先的 Fluent API 所應用的配置。
您也看到一些用於自定義行為的擴充點,無論您是使用 IValidatableObject 介面還是使用 DbContext.ValidateEntity 方法。 而最後兩種驗證方法可透過 DbContext取得,不論您使用 Code First、Model First 或 Database First 工作流程來描述概念模型。