上篇文章介绍了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. 有意思的记录-Java

    1.文件读取 项目外的绝对路径或相对路径文件读取 String path = "/xx/xx.txt"; BufferedReader reader = new BufferedR ...

  2. datepickerx设置默认日期

    datepicher插件是jQuery UI的一个插件,它提供一个日期弹出窗口(或直接显示在页面),供用户选择日期.在Web开发中,总会遇到需要用户输入日期的情况.一般都是提供一个text类型的inp ...

  3. (转)倾力总结40条常见的移动端Web页面问题解决方案

    原文链接:戳这里 1.安卓浏览器看背景图片,有些设备会模糊.   用同等比例的图片在PC机上很清楚,但是手机上很模糊,原因是什么呢? 经过研究,是devicePixelRatio作怪,因为手机分辨率太 ...

  4. Java 设计模式 —— 单例模式

    1. 概念: 单例模式是一种常用的软件设计模式.核心结构中只包含一个被称为单例的特殊类.通过单例模式可以保证系统中一个类只有一个实例而且该实例易于外界访问,从而方便对实例个数的控制并节约系统资源.如果 ...

  5. Android数据存储之SQLCipher数据库加密

    前言: 最近研究了Android Sqlite数据库(文章地址:Android数据存储之Sqlite的介绍及使用)以及ContentProvider程序间数据共享(Android探索之ContentP ...

  6. DM9000驱动移植在mini2440(linux2.6.29)和FS4412(linux3.14.78)上的实现(deep dive)篇一

    关于dm9000的驱动移植分为两篇,第一篇在mini2440上实现,基于linux2.6.29,也成功在在6410上移植了一遍,和2440非常类似,第二篇在fs4412(Cortex A9)上实现,基 ...

  7. 读书笔记--SQL必知必会--建立练习环境

    书目信息 中文名:<SQL必知必会(第4版)> 英文名:<Sams Teach Yourself SQL in 10 Minutes - Fourth Edition> MyS ...

  8. 虚拟IP(VIP)

    高可用性HA(High Availability)指的是通过尽量缩短因日常维护操作(计划)和突发的系统崩溃(非计划)所导致的停机时间,以提高系统和应用的可用性.HA系统是目前企业防止核心计算机系统因故 ...

  9. 用eclipse开发项目时遇到的常见错误整理,和配套解决方案(1)

    01. MyEclipse项目导入eclipse后,怎么发布不了? 今天导入了之前的一个MyEclipse项目,更改jdk后,发现发布不了.解决方案如下: 打开项目根目录,找到.settings文件夹 ...

  10. “全能”选手—Django 1.10文档中文版Part1

    本文是博主翻译的Django1.10版本官方文档的第一部分,如时间充裕,争取一直翻译下去,经验不足,或有错漏,敬请指正. 另外对于公开文档进行翻译的版权问题不是很清楚,如有侵权请联系我! 另外,要转载 ...