.NET Magazine國際中文電子雜誌
作 者:許薰尹
審 稿:張智凱
文章編號: N170518301
出刊日期: 2017/5/3
我們所設計的應用程式時常常需要搜集使用者輸入的資料,也需要檢查使用者輸入的資料是否正確符合需求。Entity Framework預設支援使用.NET Framework 4提供的ValidationAttribute、IValidatableObject來驗證實體模型的資料是否如預期,若預設的功能不符合需求,您也可以自行設計驗證機制。DbContext類別也新增了新的Validation API進一步整合並擴充驗證的功能。本篇文章將介紹如何在Entity Framework應用程式之中使用ValidationAttribute、IValidatableObject以及Validation API來設計驗證、自訂驗證,以及利用try..catch語法攔截驗證例外錯誤。
- 預設執行以下動作,會促使DbContext進行驗證:
- 呼叫DbContext.SaveChanges()方法,將驗證所有標識為Added與Modified的物件。
- 呼叫DbEntityEntry.GetValidationResult()方法,將驗證特定物件。
- 叫用DbContext.GetValidationErrors()方法,將驗證所有標識為Added與Modified的物件。
Store實體類別(Entity Class)
本文延續使用《Change Tracking API - 1》一文建立的ADO.NET實體資料模型來說明Entity Framework提供的驗證API。參考以下範例程式碼,目前Store實體類別(Entity Class)的定義如下,stor_id、stor_name、stor_address、city、state、zip屬性上方都套用了StringLength Attribute限定資料的有效長度。ValidationAttribute是.NET Framework 4 的功能,不是Entity Framework的一部分,但Entity Framework的Validation API已經整合了ValidationAttribute,將會根據套用的這些Attribute進行資料驗證檢查:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PubsDemo
{
class Program
{
static void Main(string[] args)
{
using (var context = new PubsContext())
{
var aStore = new store();
aStore.stor_id = "99999";
aStore.stor_name = "9999 store";
aStore.stor_address = "679 Carson St.";
aStore.city = "Portland";
aStore.state = "OROR";
aStore.zip = "890766";
if (context.Entry(aStore).GetValidationResult().IsValid)
{
Console.WriteLine("Validation success!");
}
else
{
Console.WriteLine("Validation failed!");
}
}
}
}
}
使用GetValidationResult()方法進行驗證
DbEntityEntry<T>類別的GetValidationResult()方法可以針對一個實體(Entity)的屬性資料進行驗證,它會根據實體設定的驗證Attribute進行資料有效性檢查,然後回傳一個DbEntityValidationResult物件代表驗證的結果,只要有任何一個屬性值違反驗證Attribute的規定,就會自動將DbEntityValidationResult物件IsValid屬性的值設定為「false」,代表驗證失敗;所有實體屬性驗證都成功的話,才會將IsValid屬性的值設定為「true」。
參考以下範例程式碼,建立一個store物件,故意設定stor_id屬性值設定為「99999」,其資料長度超過「4」;將state屬性的值設定為「9999」,資料長度超過「2」,zip屬性值設定為「890766」,資料長度超過「5」,然後判斷IsValid屬性來顯示驗證結果:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PubsDemo
{
class Program
{
static void Main(string[] args)
{
using (var context = new PubsContext())
{
var aStore = new store();
aStore.stor_id = "9999";
aStore.stor_name = "9999 store";
aStore.stor_address = "679 Carson St.";
aStore.city = "Portland";
aStore.state = "OR";
aStore.zip = "89076";
if (context.Entry(aStore).GetValidationResult().IsValid)
{
Console.WriteLine("Validation success!");
}
else
{
Console.WriteLine("Validation failed!");
}
}
}
}
}
此範例執行結果如下所示,將印出驗證失敗的訊息,請參考下圖所示:

圖 1:使用GetValidationResult()方法驗證一個實體(Entity)的屬性。
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PubsDemo
{
class Program
{
static void Main(string[] args)
{
using (var context = new PubsContext())
{
var aStore = new store();
aStore.stor_id = "9999";
aStore.stor_name = "9999 store";
aStore.stor_address = "679 Carson St.";
aStore.city = "Portland";
aStore.state = "OR";
aStore.zip = "89076";
if (context.Entry(aStore).GetValidationResult().IsValid)
{
Console.WriteLine("Validation success!");
}
else
{
Console.WriteLine("Validation failed!");
}
}
}
}
}
修改目前程式碼進一步測試,填入有效的store屬性資料,讓資料不要超過預期的長度:
此範例執行結果如下所示:

圖 2:使用GetValidationResult()方法驗證一個實體(Entity)的屬性。
使用ValidationErrors屬性檢視詳細錯誤資訊
DbEntityEntry<T>類別的GetValidationResult()方法會回傳DbEntityValidationResult物件,此物件包含一個ValidationErrors屬性,可以進一步得知驗證錯誤的詳細資訊。ValidationErrors屬性是一個由DbValidationError物件所成的集合,DbValidationError物件包含兩個屬性:PropertyName,驗證錯誤的屬性名稱;ErrorMessage:驗證錯誤的錯誤訊息。
參考以下範例程式碼,叫用DbEntityEntry<T>類別的GetValidationResult()方法驗證新建立的store物件,故意設定stor_id屬性值設定為「99999」,其資料長度超過「4」;將state屬性的值設定為「9999」,資料長度超過「2」,zip屬性值設定為「890766」,資料長度超過「5」,然後利用一個foreach迴圈印出驗證不成功的屬性名稱與錯誤訊息:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PubsDemo
{
class Program
{
static void Main(string[] args)
{
using (var context = new PubsContext())
{
var aStore = new store();
aStore.stor_id = "99999";
aStore.stor_name = "9999 store";
aStore.stor_address = "679 Carson St.";
aStore.city = "Portland";
aStore.state = "OROR";
aStore.zip = "890766";
DbEntityValidationResult result = context.Entry(aStore).GetValidationResult();
if (!result.IsValid)
{
foreach (DbValidationError item in result.ValidationErrors)
{
Console.WriteLine($" {item.PropertyName} - {item.ErrorMessage}");
}
}
}
}
}
}
此範例執行結果如下:
stor_id - The field stor_id must be a string with a maximum length of 4.
state - The field state must be a string with a maximum length of 2.
zip - The field zip must be a string with a maximum length of 5.
自訂錯誤訊息
ValidationAttribute包含一個ErrorMessage屬性,可以自定驗證錯誤訊息,參考以下範例程式碼,讓我們修改Store類別,為每一個StringLength Attribute加上自訂錯誤訊息,訊息中的{0}代表參數,代表屬性的名稱;{1}參數則是StringLength中設定的長度:
namespace PubsDemo
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
public partial class store
{
public store()
{
}
[Key]
[StringLength(4, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string stor_id { get; set; }
[StringLength(40, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string stor_name { get; set; }
[StringLength(40, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string stor_address { get; set; }
[StringLength(20, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string city { get; set; }
[StringLength(2, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string state { get; set; }
[StringLength(5, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string zip { get; set; }
public virtual ICollection<sale> sales { get; set; }
public virtual ICollection<discount> discounts { get; set; }
}
}
重新執行測試程式碼,範例執行結果如下所示:
stor_id - stor_id 長度不可超過 4
state - state 長度不可超過 2
zip - zip 長度不可超過 5
使用CustomValidationAttribute自訂驗證
如果預設的驗證Attribute不能滿足您複雜的商業邏輯需求,Entity Framework可以允許你使用CustomValidationAttribute在指定的屬性套用自訂驗證的邏輯。參考以下範例程式碼,包含一個MyCustomValidations靜態類別,裏頭定義一個ContentValidationRule靜態方法,此方法檢查指定的屬性值是否包含不合法的字串「admin」與「test」,若包含這兩個字串,則回傳一個ValidationResult物件,並設定錯誤訊息為「名稱不可包含 admin 或 test 字串」;若不包含這兩個字串,則回傳ValidationResult.Success:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PubsDemo
{
public static class MyCustomValidations
{
public static ValidationResult ContentValidationRule(string value)
{
string errMsg = "";
if (value != null)
{
if (value.Contains("admin") || value.Contains("test"))
{
errMsg = "名稱不可包含 admin 或 test 字串";
return new ValidationResult(errMsg);
}
}
return ValidationResult.Success;
}
}
}
接著修改store 類別程式碼,使用CustomValidation Attribute套用自訂驗證程式碼到store類別欲驗證的stor_name屬性上方,CustomValidation需要傳入兩個參數,第一個參數指定驗證程式碼所在的類別之型別,本例為「typeof(MyCustomValidations)」;第二個參數則是要叫用的方法名稱,本例為「ContentValidationRule」方法:
namespace PubsDemo
{
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.Spatial;
public partial class store
{
public store()
{
}
[Key]
[StringLength(4, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string stor_id { get; set; }
[StringLength(40)]
[CustomValidation(typeof(MyCustomValidations), "ContentValidationRule")]
public virtual string stor_name { get; set; }
[StringLength(40)]
public virtual string stor_address { get; set; }
[StringLength(20)]
public virtual string city { get; set; }
[StringLength(2, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string state { get; set; }
[StringLength(5, ErrorMessage = "{0} 長度不可超過 {1}")]
public virtual string zip { get; set; }
public virtual ICollection<sale> sales { get; set; }
public virtual ICollection<discount> discounts { get; set; }
}
}
修改一下主程式碼來進行測試程式,將含有無效的admin字串填入stor_name屬性,然後利用一個foreach迴圈印出驗證不成功的屬性名稱與錯誤訊息:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PubsDemo
{
class Program
{
static void Main(string[] args)
{
using (var context = new PubsContext())
{
var aStore = new store();
aStore.stor_id = "99999";
aStore.stor_name = "9999 admin store";
aStore.stor_address = "679 Carson St.";
aStore.city = "Portland";
aStore.state = "OROR";
aStore.zip = "890766";
DbEntityValidationResult result = context.Entry(aStore).GetValidationResult();
if (!result.IsValid)
{
foreach (DbValidationError item in result.ValidationErrors)
{
Console.WriteLine($" {item.PropertyName} - {item.ErrorMessage}");
}
}
}
}
}
}
執行測試程式碼,此範例執行結果如下所示,除了顯示AttributeValidation的驗證錯誤資訊之外,也顯示了自訂驗證錯誤訊息:
stor_id - stor_id 長度不可超過 4
stor_name - 名稱不可包含admin或test字串
state - state 長度不可超過 2
zip - zip 長度不可超過 5
驗證特定屬性
DbPropertyEntry類別包含GetValidationErrors()方法,可以針對特定的屬性來進行資料驗證。GetValidationErrors()方法會回傳一個ICollection<DbValidationError>集合,包含多個驗證錯誤資訊。
參考以下程式碼範例,延續前文範例,建立一個store物件,並且故意填入長度超過40的字串到stor_name屬性中,且包含無效的admin字串,然後使用GetValidationErrors()方法驗證stor_name屬性,最後利用一個foreach迴圈印出驗證不成功的屬性名稱與錯誤訊息:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Entity.Validation;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PubsDemo
{
class Program
{
static void Main(string[] args)
{
using (var context = new PubsContext())
{
var aStore = new store();
aStore.stor_id = "99999";
aStore.stor_name = "9999 admin store 9999 admin store 9999 admin store 9999 admin store ";
aStore.stor_address = "679 Carson St.";
aStore.city = "Portland";
aStore.state = "OR";
aStore.zip = "89076";
ICollection<DbValidationError> result =
context.Entry(aStore).Property(n => n.stor_name).GetValidationErrors();
foreach (var item in result)
{
Console.WriteLine($" {item.PropertyName} - {item.ErrorMessage}");
}
}
}
}
}
根據我們設定的驗證規則,此範例執行結果如下所示:
stor_name - The field stor_name must be a string with a maximum length of 40.
stor_name - 名稱不可包含admin或test字串