上篇文章介绍了ValidationAttribute和IValidatableObject.Validate验证,但是这种验证还是稍微简单了,对于复杂的实体,例如:继承过来的实体、实现某接口的实体等等,简单的验证就无能为力了。这里重写ValidateEntity方法可以实现更为复杂的验证。
ValidateEntity本身是虚方法(virtual),故可以重写此方法加上自己的验证逻辑。在引入:System.Data.Entity.Infrastructure、System.Data.Entity.Validation、System.Collections.Generic三个命名空间的前提下,直接在BreakAwayContext类里输入protected override,vs就会智能提示出所有可以重写的需方法,ValidateEntity就是其中:

        /// <summary>
/// 重写ValidateEntity方法实现验证
/// </summary>
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
return base.ValidateEntity(entityEntry, items);
}

方法分析:简单说就是传递了几个参数,然后调用基类(base)的验证方法。这里未对参数进行任何操作直接返回,实际情况中是先操作传递过来的参数,然后返回,这样就达到了自定义验证的效果。

另外一个把验证逻辑写在上下文BreakAwayContext类里的好处就是可以根据上下文追踪到的其他实体来验证需要验证的实体,甚至可以验证数据库里的数据。这些都是ValidationAttribute和IValidatableObject.Validate方法做不到的。除非传递一个上下文对象的实例,但是这个明显不符合分层的思想。

ok,看下接下来要操作的两个实体:

    /// <summary>
/// 结账类
/// </summary>
public class Payment
{
public Payment()
{
PaymentDate = DateTime.Now;
}
public int PaymentId { get; set; } //主键
public int ReservationId { get; set; } //预约id
public DateTime PaymentDate { get; set; } //结账日期
public decimal Amount { get; set; } //金额
}
    /// <summary>
/// 预约类
/// </summary>
public class Reservation
{
public Reservation()
{
Payments = new List<Payment>();
}
public int ReservationId { get; set; }
public DateTime DateTimeMade { get; set; } //预约时间
public Person Traveler { get; set; } //预约人
public Trip Trip { get; set; } //属于哪个旅行
public Nullable<DateTime> PaidInFull { get; set; } //已付全款 public List<Payment> Payments { get; set; } //一对多
}

很明显是一个一对多的关系,预约类对应多个结账类。从表结账类和主表预约类是通过ReservationId连接的。上验证方法:

        /// <summary>
/// 重写ValidateEntity方法实现验证(ValidateEntity验证会在定制的验证规则通过后执行)
/// </summary>
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
var reservation = entityEntry.Entity as DbContexts.Model.Reservation;
if (reservation != null)
{
if (entityEntry.State == EntityState.Added && reservation.Payments.Count == )
{
result.ValidationErrors.Add(new DbValidationError("Reservation", "New reservation must have a payment."));
}
}
if (!result.IsValid)
{
return result;
}
return base.ValidateEntity(entityEntry, items);
}

此方法的验证规则是:新添加的Reservation预约类实体必须得有从表Payment数据,否则添加验证错误到DbEntityValidationResult里,这样验证就不通过了。

当然,为了整洁和方便整理,也可以把Reservation的验证单独提取出来:

        /// <summary>
/// 重写ValidateEntity方法实现验证(ValidateEntity验证会在定制的验证规则通过后执行)
/// </summary>
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>()); ValidateReservation(result);
if (!result.IsValid)
{
return result;
}
return base.ValidateEntity(entityEntry, items); //调用基类的验证方法
}
/// <summary>
/// 多个验证规则
/// </summary>
private void ValidateReservation(DbEntityValidationResult result)
{
var reservation = result.Entry.Entity as DbContexts.Model.Reservation;
if (reservation != null)
{
if (result.Entry.State == EntityState.Added && reservation.Payments.Count == )
{
result.ValidationErrors.Add(new DbValidationError("Reservation", "New reservation must have a payment."));
}
}
}

注:ValidateEntity临时的关闭了延迟加载,故上面的方法不会去数据库里查从表数据。

上面的验证是先进行自定义的规则验证,都通过了再进行上下文的验证。现在颠倒下顺序,先走上下文的验证,通过了再进行个性化的验证。这里的上下文验证是Data Annotation定义的属性最大长度、不为空等;自定义验证是每一个目的地类Destination下不能有同名的住宿类Lodging,这个也是符合逻辑的,如果连基本的Data Annotation验证都不通过也没必须再去数据库查询唯一不唯一了。上方法:

        /// <summary>
/// 先走上下文验证,再进行自定义验证
/// </summary>
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items); //先走上下文验证
if (result.IsValid)
{
ValidateLodging(result); //上下文验证通过再验证自定义验证
}
return result;
}
/// <summary>
/// 自定义验证:每一个目的地类Destination下不能有同名的住宿类Lodging
/// </summary>
/// <param name="result"></param>
private void ValidateLodging(DbEntityValidationResult result)
{
var lodging = result.Entry.Entity as DbContexts.Model.Lodging;
if (lodging != null && lodging.DestinationId != )
{
if (Lodgings.Any(l => l.Name == lodging.Name && l.DestinationId == lodging.DestinationId))
{
result.ValidationErrors.Add(new DbValidationError("Lodging", "There is already a lodging named " + lodging.Name + " at this destination."));
}
}
}

来写个方法测试下这个验证:

        /// <summary>
/// 先验证上下文,再验证自定义的规则
/// </summary>
private static void CreateDuplicateLodging()
{
using (var context = new DbContexts.DataAccess.BreakAwayContext())
{
var destination = context.Destinations.FirstOrDefault(d => d.Name == "Grand Canyon");
try
{
context.Lodgings.Add(new DbContexts.Model.Lodging
{
Destination = destination,
Name = "Grand Hotel"
});
context.SaveChanges();
Console.WriteLine("Save Successful");
}
catch (DbEntityValidationException ex)
{
Console.WriteLine("Save Failed: ");
foreach (var error in ex.EntityValidationErrors)
{
Console.WriteLine(string.Join(Environment.NewLine, error.ValidationErrors.Select(v => v.ErrorMessage)));
}
return;
}
}
}

跑下程序显然这条数据添加不进去,上下文的验证的确能通过,但是自定义的验证不能通过。因为Name为Grand Hotel的Lodging已经存在了。看看输出:
Save Failed:
There is already a lodging named Grand Hotel at this destination.

当然也可以让它连第一层验证都通过不了,在Lodging的MilesFromNearestAirport属性上加上区间验证:

        [Range(., )]
public decimal MilesFromNearestAirport { get; set; }

由于实体已经发生变化,必须先重新生成下数据库再跑上面的方法会输出:
Save Failed:
字段 MilesFromNearestAirport 必须在 0.5 和 150 之间。

第一层验证没通过也就不会去数据库里查是否有重复的记录了,这个效率比较高,不会发送不必要的sql到数据库。当然也可以两者同时验证:

        /// <summary>
///同时验证
/// </summary>
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = base.ValidateEntity(entityEntry, items);
ValidateLodging(result);
return result;
}

输出:
Save Failed:
字段 MilesFromNearestAirport 必须在 0.5 和 150 之间。
There is already a lodging named Grand Hotel at this destination.

简单的示例结束,其实利用传递进来的参数可以做很多自定义和个性化的验证。明白其原理后项目中自己就可以随意发挥了。本章源码点这里

DbContext系列文章到这就结束了,奉上所有文章的导航:

EF DbContext 系列文章导航

  1. EF如何操作内存中的数据和加载外键数据:延迟加载、贪婪加载、显示加载  本章源码
  2. EF里单个实体的增查改删以及主从表关联数据的各种增删改查  本章源码
  3. 使用EF自带的EntityState枚举和自定义枚举实现单个和多个实体的增删改查  本章源码
  4. EF里查看/修改实体的当前值、原始值和数据库值以及重写SaveChanges方法记录实体状态  本章源码
  5. EF里如何定制实体的验证规则和实现IObjectWithState接口进行验证以及多个实体的同时验证  本章源码
  6. 重写ValidateEntity虚方法实现可控的上下文验证和自定义验证  本章源码

重写ValidateEntity虚方法实现可控的上下文验证和自定义验证的更多相关文章

  1. 5、面向对象以及winform的简单运用(方法重载、隐藏、重写与虚方法)

    方法的重载: 规定一个方法可以具有不同的实现,但方法的名称是相同的.如: //同样是Man这个方法 public int Man(int age,int name) { …… } //重载 publi ...

  2. 1)jquery validate 远程验证remote,自定义验证 , 手机号验证 2)bootstrap validate 远程remote验证的方法.

    1)jquery  validate 远程验证remote,自定义验证 1-1: js <script src="YYFramework/Public/js/jquery-3.1.1. ...

  3. c#面向对象基础 重写、虚方法、抽象类

    虚方法 抽象类与抽象方法 1.书写规范: 在类前面加上abstract关键字,就成为了抽象类:在一个方法前面加上abstract关键字,就成为了抽象方法(抽象方法不能有实现方法,直接在后面加分号) 例 ...

  4. 隐藏父类方法的new和重写父类虚方法virtual的区别

    一.代码 public class Parent { public void Method_A() { Console.WriteLine("Parent Method_A"); ...

  5. 如何重写object虚方法

    在 C# 中 Object 是所有类的基类,所有的结构和类都直接或间接的派生自它.前面这段话可以说所有的 C# 开发人员都知道,但是我相信其中有一部分程序员并不清楚甚至不知道我们常用的 ToStrin ...

  6. C# 读书笔记之访问虚方法、重写方法和隐藏方法

    C#允许派生类中的方法与基类中方法具有相同的签名:基类中使用关键字virtual定义虚方法:然后派生类中使用关键字override来重写方法,或使用关键字new来覆盖方法(隐藏方法). 重写方法用相同 ...

  7. 抽象方法(abstract method) 和 虚方法 (virtual method), 重载(overload) 和 重写(override)的区别于联系

    1. 抽象方法 (abstract method) 在抽象类中,可以存在没有实现的方法,只是该方法必须声明为abstract抽象方法. 在继承此抽象类的类中,通过给方法加上override关键字来实现 ...

  8. Partial(部分方法,局部方法),virtual(虚方法),abstract(抽象方法)

    Partial 部分方法顾明思议是方法的一部分,不完整的,在ide编译时候,会将所有部分方法加载到一起统一编译,如果分部方法没有被实现,编译器就不会.对他们进行编译. 局部类型的限制 (1) 局部类型 ...

  9. [C#解惑] #1 在构造函数内调用虚方法

    谜题 在C#中,用virtual关键字修饰的方法(属性.事件)称为虚方法(属性.事件),表示该方法可以由派生类重写(override).虚方法是.NET中的重要概念,可以说在某种程度上,虚方法使得多态 ...

随机推荐

  1. TODO:Go语言goroutine和channel使用

    TODO:Go语言goroutine和channel使用 goroutine是Go语言中的轻量级线程实现,由Go语言运行时(runtime)管理.使用的时候在函数前面加"go"这个 ...

  2. Code First Migrations

    在MVC开发当中难免会对类进行修改,修改后再次运行就会出现异常,提示上下文的模型已在数据库创建后发生改变. 支持“AppContext”上下文的模型已在数据库创建后发生更改.请考虑使用 Code Fi ...

  3. python __getitem__, __setitem__ 实现属性的索引式存取

    class MyDictionary(object): """docstring for MyDictionary""" kv = {} d ...

  4. tornado session

    [转]tornado入门 - session cookie 和session 的区别: 1.cookie数据存放在客户的浏览器上,session数据放在服务器上. 2.cookie不是很安全,别人可以 ...

  5. Redis数据结构详解之List(二)

    序言 思来想去感觉redis中的list没什么好写的,如果单写几个命令的操作过于乏味,所以本篇最后我会根据redis中list数据类型的特殊属性,同时对比成熟的消息队列产品rabbitmq,使用red ...

  6. AngularJS Resource:与 RESTful API 交互

    REST(表征性状态传输,Representational State Transfer)是Roy Fielding博士在2000年他的博士论文中提出来的一种软件架构风格.RESTful风格的设计不仅 ...

  7. DropDownList 下拉框选择改变,促发事件和防全局刷新(记录)

    代码: <asp:ScriptManager ID="ScriptManager1" runat="server"> </asp:Script ...

  8. Hibernate(3)——实例总结Hibernate对象的状态和ThreadLoacl封闭的session

    俗话说,自己写的代码,6个月后也是别人的代码……复习!复习!复习!涉及的知识点总结如下: Hibernate的内部执行过程(CRUD) 对象的状态及其转换图和例子 使用JUnit测试 使用getCur ...

  9. java笔记--理解java类加载器以及ClassLoader类

    类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制 ...

  10. 【分布式】Zookeeper的服务器角色

    一.前言 前一篇已经详细的讲解了Zookeeper的Leader选举过程,下面接着学习Zookeeper中服务器的各个角色及其细节. 二.服务器角色 2.1 Leader Leader服务器是Zook ...