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

posted @ 2012-08-03 18:00  jerry-Tom  阅读(4542)  评论(5编辑  收藏  举报