重写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中的重要概念,可以说在某种程度上,虚方法使得多态 ...
随机推荐
- 【java】jstack
介绍 jstack用于打印出给定的java进程ID或core file或远程调试服务的Java堆栈信息,如果是在64位机器上,需要指定选项"-J-d64",Windows的jsta ...
- 使用google 语言 api 来实现整个网站的翻译
---恢复内容开始--- 使用google 语言 api 来实现整个网站的翻译,这时我们可以利用免费的google api来做处理来实现多语言的功能. 放在 HTML 文件中 <div id=& ...
- Android之SQLite数据库篇
一.SQLite简介 Google为Andriod的较大的数据处理提供了SQLite,他在数据存储.管理.维护等各方面都相当出色,功能也非常的强大. 二.SQLite的特点 1.轻量级使用 SQLit ...
- ContentProvider域名替换小工具
开发项目域名想怎么换就怎么换,就是这么任性! 这是一个很有意思的小工具! 这是一个方便开发人员和测试人员的小工具!! 吐槽: 一直在做Android开发,一直总有一个问题存在:做自己公司的apk开发时 ...
- Bootstrap3插件系列:bootstrap-select2
1.下载插件 https://github.com/select2/select2 http://select2.github.io/ 2.引用插件 <script src="~/Sc ...
- PHP 面向对象编程和设计模式 (3/5) - 单例模式和工厂模式
PHP高级程序设计 学习笔记 2014.06.11 设计模式(Design pattern)是一套被反复使用.多数人知晓的.经过分类编目的.代码设计经验的总结.使用设计模式是为了可重用代码.让代码更容 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(20)-权限管理系统-根据权限获取菜单
系列目录 不知不觉到20讲,真是漫长的日子,可惜最近工作挺忙,要不可以有更多的时间来更新,多谢大家的一路支持.如果你觉得好,记得帮我点击推荐^-^ 我们在之前已经插入一些真实数据,其中包含了一个用户和 ...
- ASP.NET MVC5+EF6+EasyUI 后台管理系统(32)-swfupload多文件上传[附源码]
系列目录 文件上传这东西说到底有时候很痛,原来的asp.net服务器控件提供了很简单的上传,但是有回传,还没有进度条提示.这次我们演示利用swfupload多文件上传,项目上文件上传是比不可少的,大家 ...
- highlight.js 代码高亮插件
官网:https://highlightjs.org/ API:http://highlightjs.readthedocs.org/en/latest/api.html 1. 简单使用: <l ...
- php多关键字查询
php单一关键字查询 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 tdansitional//EN" "http: ...