Entity Framework 4.1/4.3 之七 (DBContext 之4 数据验证)
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验证就先讲到这里,也不知道有没有讲清楚,有效的验证可以为我们提供安全,当然内容中提到的也并不是也好用。大家分场合使用。发挥各自优势。我是百灵,我们下回见。