EntityFramework Core每一次版本的迭代和更新都会带给咱们惊喜,每次都会尽可能知足大部分使用者的需求。在EF Core 2.0版本中出现了全局过滤新特性即HasQueryFilter,它出现的意义在哪里?可以解决什么问题呢?这是咱们须要思考的问题。经过HasQueryFilter方法来建立过滤器可以容许咱们对访问特定数据库表的全部查询额外添加如出一辙的过滤器。它主要用于软删除(soft-delete)场景,即用户并不想返回那些被标记为已删除可是还没有从数据库中作物理删除的数据行。数据库
在文章开头咱们讲述了HasQueryFilter特性所解决的问题,下面咱们利用实际例子来说述一下。在许多场景中咱们并不会从物理上删除数据,而只是仅仅改变数据的状态。这个时候就凸显了HasQueryFilter特性的做用。好比咱们建立一个博客实体。express
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方法或者单独创建的映射类中配置过滤未被删除的博客,以下:ui
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)便可,以下:spa
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进行过滤筛选的局限性之一。3d
局限性一:HasQueryFilter方法过滤筛选没法应用于导航属性。code
接下来咱们再来看一个例子,首先咱们定义以下继承关系:blog
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方法过滤筛选只能定义在基类中,没法对子类进行过滤。
不知咱们是否思考过一个问题,如有不少实体都有其软删除场景,那么咱们就都须要加上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,使用此特性仍然存在局限性没法对导航属性属性进行过滤,对实体为继承次模型,那么过滤筛选只能定义在基类中,可是最后咱们给出了通用解决方案解决重复定义查询过滤问题,但愿给阅读的您一点帮助。精简的内容,简单的讲解,但愿对阅读的您有所帮助,咱们明天再会。