重写ValidateEntity虚方法实现可控的上下文验证和自定义验证
上篇文章介绍了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系列文章到这就结束了,奉上所有文章的导航:
重写ValidateEntity虚方法实现可控的上下文验证和自定义验证的更多相关文章
- 5、面向对象以及winform的简单运用(方法重载、隐藏、重写与虚方法)
方法的重载: 规定一个方法可以具有不同的实现,但方法的名称是相同的.如: //同样是Man这个方法 public int Man(int age,int name) { …… } //重载 publi ...
- 1)jquery validate 远程验证remote,自定义验证 , 手机号验证 2)bootstrap validate 远程remote验证的方法.
1)jquery validate 远程验证remote,自定义验证 1-1: js <script src="YYFramework/Public/js/jquery-3.1.1. ...
- c#面向对象基础 重写、虚方法、抽象类
虚方法 抽象类与抽象方法 1.书写规范: 在类前面加上abstract关键字,就成为了抽象类:在一个方法前面加上abstract关键字,就成为了抽象方法(抽象方法不能有实现方法,直接在后面加分号) 例 ...
- 隐藏父类方法的new和重写父类虚方法virtual的区别
一.代码 public class Parent { public void Method_A() { Console.WriteLine("Parent Method_A"); ...
- 如何重写object虚方法
在 C# 中 Object 是所有类的基类,所有的结构和类都直接或间接的派生自它.前面这段话可以说所有的 C# 开发人员都知道,但是我相信其中有一部分程序员并不清楚甚至不知道我们常用的 ToStrin ...
- C# 读书笔记之访问虚方法、重写方法和隐藏方法
C#允许派生类中的方法与基类中方法具有相同的签名:基类中使用关键字virtual定义虚方法:然后派生类中使用关键字override来重写方法,或使用关键字new来覆盖方法(隐藏方法). 重写方法用相同 ...
- 抽象方法(abstract method) 和 虚方法 (virtual method), 重载(overload) 和 重写(override)的区别于联系
1. 抽象方法 (abstract method) 在抽象类中,可以存在没有实现的方法,只是该方法必须声明为abstract抽象方法. 在继承此抽象类的类中,通过给方法加上override关键字来实现 ...
- Partial(部分方法,局部方法),virtual(虚方法),abstract(抽象方法)
Partial 部分方法顾明思议是方法的一部分,不完整的,在ide编译时候,会将所有部分方法加载到一起统一编译,如果分部方法没有被实现,编译器就不会.对他们进行编译. 局部类型的限制 (1) 局部类型 ...
- [C#解惑] #1 在构造函数内调用虚方法
谜题 在C#中,用virtual关键字修饰的方法(属性.事件)称为虚方法(属性.事件),表示该方法可以由派生类重写(override).虚方法是.NET中的重要概念,可以说在某种程度上,虚方法使得多态 ...
随机推荐
- vagrant 1.8.6 安装过程及总结遇到的坑
下面先总结遇到的问题,这些问题如果你也遇到,可能需要搜索很多次才能找到原因. 如果想看安装过程,可以先直接跳到后面第二部分部分. 1 问题汇总: 1.1 vagrant版本过高问题. vagrant ...
- iOS中数据库应用基础
iOS 数据库入门 一.数据库简介 1.什么是数据库? 数据库(Database) 是按照数据结构来组织,存储和管理数据的仓库 数据库可以分为2大种类 关系型数据库(主流) PC端 Oracle My ...
- C#定时任务组件之FluentScheduler
FluentScheduler是.NET开源处理定时任务组件 1.任务的创建注册 public static void TaskActionByMinutes(Action action, int c ...
- Python开发环境配置
好久没有写博客了,自从6月份毕业后,进入一家做书法.字画文化宣传的互联网公司(www.manyiaby.com),这段时间一直在进行前端开发,对于后端的使用很少了,整天都是什么html.css.jav ...
- Android动画效果之初识Property Animation(属性动画)
前言: 前面两篇介绍了Android的Tween Animation(补间动画) Android动画效果之Tween Animation(补间动画).Frame Animation(逐帧动画)Andr ...
- Java设计模式之策略模式(Strategy)
前言: 最近一直在学习基于okHttp网络请求,学习的过程中就想起了之前项目中有这么一个需求不同的接口要采用不同的加密方式,比如登录之前要采用RSA加密,登录之后要采用AES加密,当时是采用靠传递一个 ...
- iOS引入JavaScriptCore引擎框架(二)
为何放弃第一种方案 UIWebView的JSContext获取 上篇中,我们通过简单的kvc获取UIWebVIew的JSContext,但是实际上,apple并未给开发者提供访问UIWebVi ...
- 【Oracle 集群】Linux下Oracle RAC集群搭建之基本测试与使用(九)
Oracle 11G RAC数据库安装(九) 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习的汇总 ...
- SQL Tuning 基础概述04 - Oracle 表的类型及介绍
Tables A table describes an entity such as employees. You define a table with a table name, such as ...
- C#设计模式之简单工厂模式(Simple Factory)
1. 概述 简单工厂模式就是将一个类的实例化交给一个静态工厂来执行. 2. 使用频率 中 3. 模式结构 3.1 机构图 3.2 模式中的角色 Product:抽象类,把具体产品类公共的代码进行抽象和 ...