上篇文章介绍了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. With(ReadPast)就不会被阻塞吗?

    在生产环境中,会有很多使用ReadPast查询提示的场合,来避免正在被其它事务锁定的行对当前查询造成阻塞,而又不会获取到“脏数据”. 可是很多人都疑惑,为什么我使用了ReadPast仍然有时会被阻塞? ...

  2. MySQL对时间戳的转换处理

    开发中很多时候在数据库里都会存储Long类型的时间戳,而时间戳做比对会相对麻烦 我的绝决方案: SELECT FROM_UNIXTIME(LEFT(create_time,10), '%Y-%m-%d ...

  3. hibernate多对一单向关联

    关联是类(类的实例)之间的关系,表示有意义和值得关注的连接. 本系列将介绍Hibernate中主要的几种关联映射 Hibernate一对一主键单向关联Hibernate一对一主键双向关联Hiberna ...

  4. xib文件的加载方法

    xib文件的加载方法 以UITableViewCell的cell为例 很多时候因为系统的cell无法满足我们的日常需求,我们都会自定义cell 因为cell的界面比较固定,所以通常都会选择用xib来描 ...

  5. Quartz.net持久化与集群部署开发详解

    序言 我前边有几篇文章有介绍过quartz的基本使用语法与类库.但是他的执行计划都是被写在本地的xml文件中.无法做集群部署,我让它看起来脆弱不堪,那是我的罪过. 但是quart.net是经过许多大项 ...

  6. Vue1.0 的技术栈

    vuejs概述 Vue.js是用于构建交互式的Web界面的库.它提供了MVVM数据绑定和一个可组合的组件系统,具有简单.灵活的API. 结合node.js 可以实现前后端开发从物理上的分离.使前端负责 ...

  7. 1、.NET平台概述

        本学习主要参考Andrew Troelsen的C#与.NET4高级程序设计,这小节主要述说以下几个东西:     宏观上讨论一下.net相关的主题:程序集.CIL(Common Interme ...

  8. jQuery操作DOM元素

    作为一个后端程序员,也是要和前端页面打交道的.最常见的场景莫过DOM元素操作和前端页面使用AJAX向服务器发送请求.实现上述两个功能当然可以使用原生js来完成,但在实际开发过程中很少这样做,通常会使用 ...

  9. 设计模式(十二)享元模式(Flyweight Pattern)

    一.引言 在软件开发过程,如果我们需要重复使用某个对象的时候,如果我们重复地使用new创建这个对象的话,这样我们在内存就需要多次地去申请内存空间了,这样可能会出现内存使用越来越多的情况,这样的问题是非 ...

  10. 随机记录工作中常见的sql用法错误(一)

    没事开始写博客,留下以前工作中常用的笔记,内容不全或者需要补充的可以留言,我只写我常用的. 网上很多类似动软生成器的小工具,这类工具虽然在表关系复杂的时候没什么软用,但是在一些简单的表结构关系还是很方 ...