前言

EntityFramework Core每一次版本的迭代和更新都会带给我们惊喜,每次都会尽量满足大部分使用者的需求。在EF Core 2.0版本中出现了全局过滤新特性即HasQueryFilter,它出现的意义在哪里?能够解决什么问题呢?这是我们需要思考的问题。通过HasQueryFilter方法来创建过滤器能够允许我们对访问特定数据库表的所有查询额外添加一模一样的过滤器。它主要用于软删除(soft-delete)场景,即用户并不想返回那些被标记为已删除但是尚未从数据库中做物理删除的数据行。

HasQueryFilter全局过滤器

在文章开头我们讲述了HasQueryFilter特性所解决的问题,下面我们利用实际例子来讲述一下。在许多场景中我们并不会从物理上删除数据,而只是仅仅改变数据的状态。这个时候就凸显了HasQueryFilter特性的作用。比如我们创建一个博客实体。

    public class Blog
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool IsDeleted { get; set; }
public DateTime CreatedTime { get; set; }
public DateTime ModifiedTime { get; set; }
public ICollection<Post> Posts { get; set; }
}

是不是因为我们本节讲述全局过滤所以才会加IsDeleted字段呢?显然不是,这个是有其应用场景,当我们管理自己的博客时,可能瞌睡没睡好(当然也有其他原因,不必纠结于此)导致一个不小心删除了某一篇文章即使在博客后台有友好提示【是否删除该博客】,但是你依然手滑删除了,这个时候我们找到博客园运营反应,然后分分钟就还原了我们所删除的博客,就像将文件扔到了回收站再还原一样。接下来我们在OnModelCreating方法或者单独建立的映射类中配置过滤未被删除的博客,如下:

     modelBuilder.Entity<Blog>(e =>
{
e.HasQueryFilter(f => !f.IsDeleted);
}); 或者 public class BlogConfiguration : IEntityTypeConfiguration<Blog>
{
public void Configure(EntityTypeBuilder<Blog> builder)
{
builder.HasQueryFilter(f => !f.IsDeleted);
......
}
}

要是我们对于特殊场景需要禁用查询过滤或者说忽略查询过滤又该如何呢?通过对应的APi(IgnoreQueryFilters)即可,如下:

            using (var context = new EFCoreDbContext())
{
var blogs = context.Blogs
.Include(d => d.Posts)
.IgnoreQueryFilters()
.AsNoTracking()
.ToList();
}

使用HasQueryFilter进行查询过滤是不是就是如此简单呢?是不是问题到此就这样结束了呢?看过Jeff博客的童鞋知道,Jeff经常问这样的问题且自问自答,肯定不止于此,我们继续往下探索。上述我们只是应用一个博客实体,我们还存在发表实体且二者存在关联关系,我们同上在Post中定义IsDeleted字段,此时我们在对Blog进行过滤的同时也对Post进行过滤,如下:

builder.HasQueryFilter(f => !f.IsDeleted && f.Posts.All(w => !w.IsDeleted));

由上得知使用HasQueryFilter进行过滤筛选的局限性之一。

局限性一:HasQueryFilter方法过滤筛选无法应用于导航属性。

接下来我们再来看一个例子,首先我们定义如下继承关系:

    public abstract class Payment
{
public int PaymentId { get; set; }
public decimal Amount { get; set; }
public string Name { get; set; }
public bool IsDeleted { get; set; }
} public class CashPayment : Payment
{
} public class CreditcardPayment : Payment
{
public string CreditcardNumber { get; set; }
}

上述我们定义支付基类Payment,然后派生出现金支付CashPayment和信用卡支付CreditcardPayment子类,接下来我们配置如下查询过滤:

builder.HasQueryFilter(f => !(f is CreditcardPayment && f.IsDeleted && ((CreditcardPayment)f).IsDeleted));

接下来我们进行如下查询:

            using (var context = new EFCoreDbContext())
{
var payments = context.Payments.ToList();
}

没毛病,接下来我们查询子类CreditcardPayment,如下:

            using (var context = new EFCoreDbContext())
{
var payments = context.Payments.OfType<CashPayment>().ToList();
}

由上得知使用HasQueryFilter进行过滤筛选的局限性之二。

局限性二:HasQueryFilter方法过滤筛选只能定义在基类中,无法对子类进行过滤。

HasQueryFilter通用解决方案

不知我们是否思考过一个问题,若有很多实体都有其软删除场景,那么我们就都需要加上IsDeleted字段,同时还需要配置全局过滤器,如此重复性动作我们是否觉得厌烦,搬砖的我们的单从程序角度来看,搬砖的本源就是为了解放劳动生产率,提高工作效率,说的更通俗一点则是解决重复性劳动。此时为了解决上述所延伸出的问题,我们完全可抽象出一个软删除接口,如下:

    public interface ISoftDleteBaseEntity
{
bool IsDeleted { get; set; }
}

接下来让所有需要进行软删除的实体继承该接口,比如博客实体,如下:

    public class Blog : ISoftDleteBaseEntity
{
public int Id { get; set; }
public string Name { get; set; }
public string Url { get; set; }
public bool IsDeleted { get; set; }
......
}

为了解决重复性配置劳动,我们让所有继承自上述接口一次性在OnModelCreating方法中配置全局过滤,如此一来则免去了在每个对应实体映射类中进行配置,如下:

            Expression<Func<ISoftDleteBaseEntity, bool>> expression = e => !e.IsDeleted;
foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ISoftDleteBaseEntity).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(expression);
}

愿望是美好的,结果尼玛抛出异常不支持该操作。看到上述lambda表达式都没有翻译过来,此时我们转到定义看看。

public virtual EntityTypeBuilder HasQueryFilter([CanBeNullAttribute] LambdaExpression filter);

居然参数类型不是以表达式树的形式给出,如果是如下形式则肯定可以。

public virtual EntityTypeBuilder HasQueryFilter([CanBeNullAttribute] Expression<Func<TEntity, TProperty>> filter);

所以问题出在无法翻译lambda表达式,那么我们就来自动构建lambda表达式参数和主体,如下:

             foreach (var entityType in modelBuilder.Model.GetEntityTypes()
.Where(e => typeof(ISoftDleteBaseEntity).IsAssignableFrom(e.ClrType)))
{
modelBuilder.Entity(entityType.ClrType).Property<Boolean>("IsDeleted");
var parameter = Expression.Parameter(entityType.ClrType, "e");
var body = Expression.Equal(
Expression.Call(typeof(EF), nameof(EF.Property), new[] { typeof(bool) }, parameter, Expression.Constant("IsDeleted")),
Expression.Constant(false));
modelBuilder.Entity(entityType.ClrType).HasQueryFilter(Expression.Lambda(body, parameter));
}

至此美好愿望得以实现,算是给出了一种通用解决方案。

总结

本节我们讲述了EntityFramework Core 2.0中的新特性HasQueryFilter,使用此特性仍然存在局限性无法对导航属性属性进行过滤,对实体为继承次模型,那么过滤筛选只能定义在基类中,但是最后我们给出了通用解决方案解决重复定义查询过滤问题,希望给阅读的您一点帮助。精简的内容,简单的讲解,希望对阅读的您有所帮助,我们明天再会。

EntityFramework Core 2.0全局过滤(HasQueryFilter)的更多相关文章

  1. EntityFramework Core 2.0执行原始查询如何防止SQL注入?

    前言 接下来一段时间我们来讲讲EntityFramework Core基础,精简的内容,深入浅出,希望为想学习EntityFramework Core的童鞋提供一点帮助. EntityFramewor ...

  2. EntityFramework Core 2.0 Explicitly Compiled Query(显式编译查询)

    前言 EntityFramework Core 2.0引入了显式编译查询,在查询数据时预先编译好LINQ查询便于在请求数据时能够立即响应.显式编译查询提供了高可用场景,通过使用显式编译的查询可以提高查 ...

  3. .NetCore技术研究-EntityFramework Core 3.0 Preview

    前段时间.Net Core 3.0 发布了,Entity Framework Core 3.0 也发布了Preview版.假期用了一上午大致研究了一遍,同时又体验了一把Visual Studio 20 ...

  4. [译]ASP.NET Core 2.0 全局配置项

    问题 如何在 ASP.NET Core 2.0 应用程序中读取全局配置项? 答案 首先新建一个空项目,并添加两个配置文件: 1. appsettings.json { "Section1&q ...

  5. EntityFramework Core 2.0自定义标量函数两种方式

    前言 上一节我们讲完原始查询如何防止SQL注入问题同时并提供了几种方式.本节我们继续来讲讲EF Core 2.0中的新特性自定义标量函数. 自定义标量函数两种方式 在EF Core 2.0中我们可以将 ...

  6. EntityFramework Core 3.0查询

    前言 随着.NET Core 3.0的发布,EF Core 3.0也随之正式发布,关于这一块最近一段时间也没太多去关注,陆续会去对比之前版本有什么变化没有,本节我们来看下两个查询. 分组 我们知道在E ...

  7. Asp.Net Core 项目 EntityFramework Core 根据登录用户名过滤数据

    1.创建ASP.NET Core Web Applicatoin (MVC)项目,并且使用 Individual User Account 2.创建数据筛选接口 Models->IDataFil ...

  8. 在ASP.NET Core中通过EF Core实现一个简单的全局过滤查询

    前言 不知道大家是否和我有同样的问题: 一般在数据库的设计阶段,会制定一些默认的规则,其中有一条硬性规定就是一定不要对任何表中的数据执行delete硬删除操作,因为每条数据对我们来说都是有用的,并且是 ...

  9. [译]ASP.NET Core 2.0 系列文章目录

    基础篇 [译]ASP.NET Core 2.0 中间件 [译]ASP.NET Core 2.0 带初始参数的中间件 [译]ASP.NET Core 2.0 依赖注入 [译]ASP.NET Core 2 ...

随机推荐

  1. promise使用方法

    代码报错会走向失败时的回调 let p = new Promise((resolve, reject) => { throw new Error("我错了"); //会失败 ...

  2. [PHP]全局使用 Composer 组件的思路

    /** * 全局使用 composer 组件的步骤: * * 1. 建一个包含所有需要全局使用的composer包的项目. * 如:globalVendor/ * composer init 初始化 ...

  3. 性能测试监控:Jmeter +InfluxDB +collectd +Grafana

    虚拟机ip 192.168.180.128 Influxdb Influxdb是一个开源的分布式时序.时间和指标数据库,使用go语言编写,无需外部依赖. 它有三大特性: 时序性(Time Series ...

  4. C#基础(七)虚函数

    若一个实例方法声明前带有virtual关键字,那么这个方法就是虚方法.虚方法与非虚方法的最大不同是,虚方法的实现可以由派生类所取代,这种取代是通过方法的重写实现的(以后再讲)虚方法的特点:虚方法前不允 ...

  5. javascript中的BOM对象

    1.window对象 所有的浏览器都支持window对象 概念上讲,一个html文档对应一个window对象 功能上讲,控制浏览器窗口 使用上讲,window对象不需要创建对象,直接使用 2.wind ...

  6. vsftpd安装和使用 Linux系统和window系统

    vsftpd 安装(Linux)一.安装系统环境 centos 6.9 64位二.vsftpd版本 vsftpd-2.2.2-24.el6.x86_64三.安装步骤1.安装 执行 yum -y ins ...

  7. 利用回调实现Java的异步调用

    异步是指调用发出后,调用者不会立刻得到结果,而是在调用发出后,被调用者通知调用者,或通过回调函数处理这个调用. 回调简单地说就是B中有一个A,这样A在调用B的某个方法时实际上是调用到了自己的方法. 利 ...

  8. bzoj 1598: [Usaco2008 Mar]牛跑步 [k短路 A*] [学习笔记]

    1598: [Usaco2008 Mar]牛跑步 题意:k短路 ~~貌似A*的题目除了x数码就是k短路~~ \[ f(x) = g(x) + h(x) \] \(g(x)\)为到达当前状态实际代价,\ ...

  9. php语言基础(一)

    一.php标记风格 1.1xml风格(常用) <?php echo "xml风格": ?> 1.2脚本风格 <script language="php& ...

  10. 使用tcp+select实现客户端与客户端的通信

    使用多路复用实现客户端与客户端进行通信: 原理:客户端只要一连上服务器,立马给服务器发送用户名,然后在服务端将newsocketfd存放在同一个结构体中,客户端先给服务器发送数据,然后通过服务器转发给 ...