Entity Framework 4.1/4.3 之七 (DBContext 之4 數據驗證)
中國男籃輸了,不過不影響我對中國男籃的喜歡。在Entity Framework 4.1/4.3 之六 (DBContext 3)中講了EF DBContext API的常用功能,今天來我們接着來講一下DBContext API的驗證。
三、DBContext 的驗證 (Validating with the Validation API)
1、定義和觸發驗證 (Defining and Triggering Validation)
會引起DBContext去執行驗證的一些方法
(1)、當跟蹤狀態為增加、修改時。調用執行SaveChanges()方法會執行驗證。
(2)、DbEntityEntry.GetValidationResult() 方法將會對單獨的Object執行驗證。
(3)、DbEntityEntry
(4)、DbContext.GetValidationErrors 可以在DBContext進行 增加、修改實體的時候不捕獲驗證異常。
我們來描述一下驗證的流程:
DBContext.SaveChanges()這時候調用驗證 —> DBContext.GetValidationErrors —> DBContext.ValidateEntity(CustomLogic) —> DBEntityEntry.GetValidationResult(ValidationAttribute,IValidatableObject.Validate)
2、驗證單個實體並獲取驗證結果 (Validating a Single Object on Demand with GetValidationResult)
代碼如下,先是在實體中對屬性進行約束,我們來看一下代碼:
[MaxLength(500)] public string Description { get; set; } [MaxLength(10)] public string LastName { get; set; } private static void ValidateNewPerson() { var person = new Person { FirstName = "Julie", LastName = "Lerman", Photo = new PersonPhoto { Photo = new Byte[] { 0 } } }; using (var context = new BreakAwayContext()) { if (context.Entry(person).GetValidationResult().IsValid) { Console.WriteLine("Person is Valid"); } else { Console.WriteLine("Person is Invalid"); } } }
輸出的結果是:Person is Valid
代碼真是個好東西,他能讓我們一下子就明白,原來是這么回事。[MaxLength(500)]好定了內容的長度,這個特性需要引用using System.ComponentModel.DataAnnotations; 如果你試着把LastName的屬性值改為超為10長度的字符串,得到的結果會是InValidate也就是False,不信你試試。對了,GetValidationResult()是用來獲取驗證的結果。
3、通過DataAnnotations來指定屬性的驗證規則 (Specifying Property Rules with ValidationAttribute DataAnnotations)
下面列出的是DataAnnotations驗證的方式:
可以限定字條串的長度、數值的大小、還可以自定義表達式、
DataTypeAttribute [DataType(DataType enum)]
RangeAttribute [Range (low value, high value, error message string)]
RegularExpressionAttribute [RegularExpression(@”expression”)]
RequiredAttribute [Required]
StringLengthAttribute [StringLength(max length value, MinimumLength=min length value)]
CustomValidationAttribute This attribute can be applied to a type as well as to a property.
Entity Framework 提供了MaxLengthAttribute 和 MinLengthAttribute。為了符合代碼先行的理念,DBContext 的驗證API中提代了很多方法,利用這些方法,我們就可以直接在編寫代碼的時候來對屬性設置約束。我們來看個例子:
modelBuilder.Entity<TestEntity>().Property(p => p.name).HasMaxLength(10);
例子設定了TestEntity實體的name屬性的最大長度為10。通過這種方式,我們就可以在DBContext.OnModelCreating 方法中加入上面的代碼來代替[MaxLength]方式的特性限定。你可以試試這種新的方式,試之前記得把實體中的[MaxLength]去掉,同樣調用GetValidationResult()來獲取驗證的結果。
對臨時屬性的驗證 (Validating Unmapped or “Transient” Properties)
有這樣一種情況,在實體中有些屬性並沒有和數據庫中字段有映射關系。只是我們為了處理業務而臨時加的屬性,如果我們給這處臨時屬性加上特性約束后,Entity Framework同樣會對其進行驗證。
4、檢查驗證結果的詳細信息 (Inspecting Validation Result Details)
我們注意到GetValidationResult 在驗證失敗后並不是簡單的返回或者說拋出一個exception。而是返回一個System.Data.Entity.Validation.DbEntityValidationResult 類型的值。DbEntityValidationResult 也公來了一個ValidationErrors屬性,這個屬性包含記錄了詳細錯誤信息的DbValidationError類型集合。下圖展示了獲取驗證結果,包含IsValid值(是否驗證通過的值),和ValidationErrors屬性。
錯誤提示:當我們把LastName的屬性值賦值超過了約束限定大小時,返回ValidationErrors驗證結果,它包含一個單獨的DbValidationError。DbValidationError有兩個屬性,一個是發生異常對應的屬性名稱,一個是錯誤信息。這里有一個疑問?錯誤消息是哪兒來的呢???我們可以自己定義錯誤消息嗎??? 我們來看看代碼:
[MaxLength(10,ErrorMessage= "Dude! Last name is too long! 10 is max.")] public string LastName { get; set; }
通過代碼我們看到了,錯誤信息來源於我們屬性約束值提供的錯誤消息。當然,如果你不寫也沒關系,EF會自己提供錯誤消息。以就是上圖展示的ErrorMessage。下圖將展示出我們自定義的錯誤消息:
5、深入探索屬性驗證 (Exploring More Validation Attributes)
(1)、表達式驗證
[RegularExpression(@"^[a-zA-Z''-'\s]{1,40}$")] public string Country { get; set; }
public static void ValidateDestination() { ConsoleValidationResults( new Destination { Name = "Bei Jing City", Country = "China", Description = "Big city" }); }
如果你試着把Country的值改為 C.H.I.N.A 就會出現驗證異常。因為C.H.I.N.A與正則不匹配。
(2)、使用自定義的屬性驗證
using System.ComponentModel.DataAnnotations; namespace Model { public static class BusinessValidations { public static ValidationResult DescriptionRules(string value) { var errors = new System.Text.StringBuilder(); if (value != null) { var description = value as string; if (description.Contains("!")) { errors.AppendLine("Description should not contain '!'."); } if (description.Contains(":)") || description.Contains(":(")) { errors.AppendLine("Description should not contain emoticons."); } } if (errors.Length > 0) return new ValidationResult(errors.ToString()); else
return ValidationResult.Success; } } }
提示:你可以看到難證結果此處是System.ComponentModel.DataAnnotations.ValidationResult 類型。
好了,驗證的功能寫好了,下面我們來使用一下自己的驗證。
[MaxLength(500)] [CustomValidation(typeof(BusinessValidations), "DescriptionRules")] public string Description { get; set; }
如果你願意的話,自己寫個測試,使用GetValidationResult 來測試吧。
(3)、在請求中驗證屬性 (Validating Individual Properties on Demand)
除了提供getvalidationresults方法,dbentityentry 也可以讓你進行屬性驗證:
context.Entry(trip).Property(t => t.Description);
它會返回一個 DbPropertyEntry 類來描述屬性。DbPropertyEntry 類有明確的方法(GetValidationErrors)來驗證特定項目。這個方法將返回ICollection<DbValidationError>,它與 DbValidationError 類型相同。我們通過下面這個例子來看看GetValidationErrors的應用。
private static void ValidatePropertyOnDemand() { var trip=new Trip { EndDate = DateTime.Now, StartDate = DateTime.Now, CostUSD = 500.00M, Description = "Hope you won't be freezing :)" }; using (var context = new BreakAwayContext()) { var errors = context.Entry(trip).Property(t => t.Description).GetValidationErrors(); Console.WriteLine("# Errors from Description validation: {0}", errors.Count()); } }
(4)、使用IValidatableObject接口來進驗證
除了ValidationAttribute,.Net 4 新增了IValidatableObject 接口供開發者進行業務邏輯驗證。IValidatableObject 提供了Validate 方法供開發者自己來擴展自定義驗證。我們來看例子:
public class Trip : IValidatableObject { [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Identifier { get; set; } public DateTime StartDate { get; set; } public DateTime EndDate { get; set; } [CustomValidation(typeof(BusinessValidations), "DescriptionRules")] public string Description { get; set; } public decimal CostUSD { get; set; } [Timestamp] public byte[] RowVersion { get; set; } public int DestinationId { get; set; } [Required] public Destination Destination { get; set; } public List<Activity> Activities { get; set; }
public IEnumerable<ValidationResult> Validate( ValidationContext validationContext) { if (StartDate.Date >= EndDate.Date) { yield return new ValidationResult( "Start Date must be earlier than End Date", new[] { "StartDate", "EndDate" }); } } }
Trip 繼續 IValidatableObject 並實現 接口方法Validate,在Validate方法中加入了自己的驗證邏輯。我們來測試一下這個驗證,下面是測試代碼:
private static void ValidateTrip() { ConsoleValidationResults(new Trip { EndDate = DateTime.Now, StartDate = DateTime.Now.AddDays(2), CostUSD = 500.00M, Destination = new Destination { Name = "Somewhere Fun" } }); }
當我們調用ValidateTrip, 程序會顯示 “Start Date must be earlier than End Date.” 這種Validate 驗證方式我們多在MVC和WPF編程中用到,幫綁定屬性的時候可以把錯誤提示也一便綁定了。
(5)、使用CustomValidationAttributes 進行驗證 以下是代碼:代碼可以很好的說明
public static ValidationResult TripDateValidator( Trip trip, ValidationContext validationContext) { if (trip.StartDate.Date >= trip.EndDate.Date) { return new ValidationResult( "Start Date must be earlier than End Date", new[] { "StartDate", "EndDate" }); } return ValidationResult.Success; }
public static ValidationResult TripCostInDescriptionValidator( Trip trip, ValidationContext validationContext) { if (trip.CostUSD > 0) { if (trip.Description.Contains(Convert.ToInt32(trip.CostUSD).ToString())) { return new ValidationResult( "Description cannot contain trip cost", new[] { "Description" }); } } return ValidationResult.Success; }
這是我們自己寫的自己定義的驗證方法,下面我們來使用自定義方法:
[CustomValidation(typeof(Trip), "TripDateValidator")] [CustomValidation(typeof(Trip), "TripCostInDescriptionValidator")] public class Trip: IValidatableObject
下面是測試程序:
private static void ValidateTrip() { ConsoleValidationResults(new Trip { EndDate = DateTime.Now, StartDate = DateTime.Now.AddDays(2), CostUSD = 500.00M, Description = "You should enjoy this 500 dollar trip", Destination = new Destination { Name = "Somewhere Fun" } }); }
關於DBContext驗證就先講到這里,也不知道有沒有講清楚,有效的驗證可以為我們提供安全,當然內容中提到的也並不是也好用。大家分場合使用。發揮各自優勢。我是百靈,我們下回見。