Entity Framework Core 2.0 全局查询过滤器
不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址
本博文翻译自:
http://gunnarpeipman.com/2017/08/ef-core-global-query-filters/
Entity Framework Core 2.0 全局查询过滤器
Entity Framework Core 2.0引入了全局查询过滤器,可以在创建模型时应用到实体 。它使得构建多租户应用程序和支持对实体 的软删除变得更加容易。这篇博客文章提供了关于如何在实际应用中使用全局查询过滤器的更深入的概述,以及如何将全局查询过滤器自动应用到领域实体。
示例解决方案。 我在 ASP.NET Core 2中构建了示例解决方案EFCoreGlobalQueryFilters 在更复杂的上下文中演示了全局查询过滤器。它演示了如何自动地将全局查询过滤器应用到领域实体。创建简单的数据库并使用sql脚本填充测试数据。
How global query filters look like?
全局查询过滤器是什么?
这就是全局查询筛选器在软删除时的样子。我们在DbContext类中重写了OnModelCreating方法。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Playlist>().HasKey(e => e.Id);
modelBuilder.Entity<Playlist>().HasQueryFilter(e => !e.IsDeleted);
modelBuilder.Entity<Song>().HasKey(e => e.Id);
modelBuilder.Entity<Song>().HasQueryFilter(e => !e.IsDeleted);
base.OnModelCreating(modelBuilder);
}
这些过滤器,会在我们对给定类型的实体进行查询时应用
真正的应用程序需要什么?
上面的代码是简化的,不考虑实际的应用场景。但是应用程序的体系结构通常是复杂的。所以当我们考虑到作为数字核心或企业一部分任务的关键的应用程序时,创建的将不仅仅是几个类。本文的目标是演示以下内容:
- 如何支持多租户
- 如何支持软删除实体
- 如何自动检测实体
示例解决方案 有助于我们从更复杂的场景开始,但它没有提供完全灵活和复杂的框架。当涉及到现实生活中的应用程序时,涉及的问题太多了,而每个应用程序通常都有自己的解决方案,以解决不同的问题。
定义实体
让我们从定义一些实体开始。他们使用简单的基类,并且期望所有的实体都从基类扩展。
public abstract class BaseEntity
{
public int Id { get; set; }
public Guid TenantId { get; set; }
public bool IsDeleted { get; set; }
}
public class Playlist : BaseEntity
{
public string Title { get; set; }
public IList<Song> Songs { get; set; }
}
public class Song : BaseEntity
{
public string Artist { get; set; }
public string Title { get; set; }
public string Location { get; set; }
}
现在我们有一些简单的实体了,是时候对多租户和软删除的实体进行下一步操作了。
租户提供者
在讨论多租户之前,web应用程序必须有某种方式来检测与当前请求相关的租户。它可以是基于host的header检测,但也可以是别的东西。在这篇文章中我们使用虚拟的提供者以便于我们提供简单的示例。
public interface ITenantProvider
{
Guid GetTenantId();
}
public class DummyTenantProvider : ITenantProvider
{
public Guid GetTenantId()
{
return Guid.Parse("069b57ab-6ec7-479c-b6d4-a61ba3001c86");
}
}
这个提供者必须在启动类的ConfigureServices方法中注册。
创建数据上下文
我希望在这一点上,已经创建了这个数据库,并配置了应用程序来使用它,好了现在让我们从支持租户提供程序的简单数据上下文开始
public class PlaylistContext : DbContext
{
private Guid _tenantId;
private readonly IEntityTypeProvider _entityTypeProvider;
public virtual DbSet<Playlist> Playlists { get; set; }
public virtual DbSet<Song> Songs { get; set; }
public PlaylistContext(DbContextOptions<PlaylistContext> options,
ITenantProvider tenantProvider)
: base(options)
{
_tenantId = tenantProvider.GetTenantId();
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Playlist>().HasKey(e => e.Id);
modelBuilder.Entity<Song>().HasKey(e => e.Id);
base.OnModelCreating(modelBuilder);
}
}
现在我们有了可操作的context和租户ID,那么接下来我们就可以对自动创建的全局查询过滤器进行下一步操作了。
检测实体类型
在为所有实体类型添加全局查询过滤器之前,必须检测实体类型。如果我们知道基本实体类型,那么就很容易读取这些类型。但是有一个问题-model是建立在每个请求之上的,而我们每次在创建model时都要扫描程序集,显然这并不是一个好主意。因此,类型检测必须支持某种类型的缓存。下面示例中的这两个方法用于数据上下文类。
private static IList<Type> _entityTypeCache;
private static IList<Type> GetEntityTypes()
{
if(_entityTypeCache != null)
{
return _entityTypeCache.ToList();
}
_entityTypeCache = (from a in GetReferencingAssemblies()
from t in a.DefinedTypes
where t.BaseType == typeof(BaseEntity)
select t.AsType()).ToList();
return _entityTypeCache;
}
private static IEnumerable<Assembly> GetReferencingAssemblies()
{
var assemblies = new List<Assembly>();
var dependencies = DependencyContext.Default.RuntimeLibraries;
foreach (var library in dependencies)
{
try
{
var assembly = Assembly.Load(new AssemblyName(library.Name));
assemblies.Add(assembly);
}
catch (FileNotFoundException)
{ }
}
return assemblies;
}
警告! 如果有单独的服务来返回实体类型,那么在体系结构方面可以更好地理解。在上面的代码中,可以直接使用实体类型变量,而更糟糕的是,可以调用GetReferencingAssemblies方法。如果您编写真正的应用程序,那么最好使用单独的提供程序。
现在,数据上下文知道实体类型,并且可以编写一些代码来获得适用于所有实体的查询过滤器。
将查询过滤器应用于所有实体
这听起来很容易做,但事实并非如此。有些实体类型的列表,并没有直接使用方便的通用方法。在这一点上,需要一个小技巧。我从CodeDump页面找到了解决方案EF-Core 2.0 过滤所有查询 (并试图实现软删除). 这里的代码不能使用,因为这里的数据上下文对ITenantProvider有实例级的依赖关系。但要点仍然是相同的:让我们为数据上下文中的一些通用方法创建通用方法调用。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
foreach (var type in GetEntityTypes())
{
var method = SetGlobalQueryMethod.MakeGenericMethod(type);
method.Invoke(this, new object[] { modelBuilder });
}
base.OnModelCreating(modelBuilder);
}
static readonly MethodInfo SetGlobalQueryMethod = typeof(PlaylistContext).GetMethods(BindingFlags.Public | BindingFlags.Instance)
.Single(t => t.IsGenericMethod && t.Name == "SetGlobalQuery");
public void SetGlobalQuery<T>(ModelBuilder builder) where T : BaseEntity
{
builder.Entity<T>().HasKey(e => e.Id);
//Debug.WriteLine("Adding global query for: " + typeof(T));
builder.Entity<T>().HasQueryFilter(e => e.TenantId == _tenantId && !e.IsDeleted);
}
这不是一种简单直观的代码。甚至当我看着这段代码时,我也会瞪大眼睛。即使我看了上百遍,它仍然看起来很疯狂和笨拙。SetGlobalQuery方法也是为实体定义主键的好地方,因为它们都是从相同的基础实体类继承而来的。
测试驱动
如果我们想要了解全局查询过滤器是如何工作的,我们可以使用样例应用程序中的HomeController 来实现这一点。
public class HomeController : Controller
{
private readonly PlaylistContext _context;
public HomeController(PlaylistContext context)
{
_context = context;
}
public IActionResult Index()
{
var playlists = _context.Playlists.OrderBy(p => p.Title);
return View(playlists);
}
}
我修改了默认视图,以显示查询返回的所有播放列表。
@model IEnumerable<Playlist>
<div class="row">
<div class="col-lg-8">
<h2>Playlists</h2>
<table class="table table-bordered">
<thead>
<tr>
<th>Playlist</th>
<th>Tenant ID</th>
<th>Is deleted</th>
</tr>
</thead>
<tbody>
@foreach(var playlist in Model)
{
<tr>
<td>@playlist.Title</td>
<td>@playlist.TenantId</td>
<td>@playlist.IsDeleted</td>
</tr>
}
</tbody>
</table>
</div>
</div>
Web应用程序现在可以运行了。下面是我使用的示例数据。让我们记住,示例应用程序使用的租户ID是069b57ab-6ec7-479c-b6d4-a61ba3001c86。
当运行web应用程序时,将显示下面的表。
当我们比较这两个表时,我们会很容易发现全局查询过滤器在工作中给出的预期结果。
结束
全局查询过滤器是Entity Framework Core 2.0的完美补充,如果没有很多实体,那么我们可以通过文档中给出的简单示例来实现。在更复杂的情况下,需要一些复杂的代码来自动应用全局查询过滤器。希望将来会有更好的解决方案,但目前这里给出的解决方案也做得很好。
欢迎转载,转载请注明翻译原文出处(本文章),原文出处(原博客地址),然后谢谢观看
如果觉得我的翻译对您有帮助,请点击推荐支持:)
Entity Framework Core 2.0 全局查询过滤器的更多相关文章
- .Net Core 2.0生态(4):Entity Framework Core 2.0 特性介绍和使用指南
前言 这是.Net Core 2.0生态生态介绍的最后一篇,EF一直是我喜欢的一个ORM框架,随着版本升级EF也发展到EF6.x,Entity Framework Core是一个支持跨平台的全新版本, ...
- 【EF】Entity Framework Core 2.0 特性介绍和使用指南
阅读目录 前言 获取和使用 新特性 项目升级和核心API变化 下一步计划 遗憾的地方 回到目录 前言 这是.Net Core 2.0生态生态介绍的最后一篇,EF一直是我喜欢的一个ORM框架,随着版本升 ...
- Entity Framework Core 2.0 中使用LIKE 操作符
Entity Framework Core 2.0 中使用LIKE 操作符 不定时更新翻译系列,此系列更新毫无时间规律,文笔菜翻译菜求各位看官老爷们轻喷,如觉得我翻译有问题请挪步原博客地址 本博文翻译 ...
- Entity Framework Core 2.0 使用代码进行自动迁移
一.前言 我们在使用EF进行开发的时候,肯定会遇到将迁移更新到生产数据库这个问题,前面写了一篇文章介绍了Entity Framework Core 2.0的入门使用,这里面介绍了使用命令生成迁移所需的 ...
- ASP.Net Core项目在Mac上使用Entity Framework Core 2.0进行迁移可能会遇到的一个问题.
在ASP.Net Core 2.0的项目里, 我使用Entity Framework Core 2.0 作为ORM. 有人习惯把数据库的连接字符串写在appSettings.json里面, 有的习惯写 ...
- ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0
ASP.NET 5.0 将改名为 ASP.NET Core 1.0 ASP.NET MVC 6 将改名为 ASP.NET MVC Core 1.0 Entity Framework 7.0 将 ...
- [转帖]2016年时的新闻:ASP.NET Core 1.0、ASP.NET MVC Core 1.0和Entity Framework Core 1.0
ASP.NET Core 1.0.ASP.NET MVC Core 1.0和Entity Framework Core 1.0 http://www.cnblogs.com/webapi/p/5673 ...
- Entity Framework Core 2.0 使用入门
一.前言 Entity Framework(后面简称EF)作为微软家的ORM,自然而然从.NET Framework延续到了.NET Core.以前我也嫌弃EF太重而不去使用它,但是EF Core(E ...
- Entity Framework Core 2.0 入门简介
不多说废话了, 直接切入正题. EF Core支持情况 EF Core的数据库Providers: 此外还即将支持CosmosDB和 Oracle. EFCore 2.0新的东西: 查询: EF.Fu ...
随机推荐
- javascript数组集锦
设计数组的函数方法 toString, toLocaleString, valueOf, concat, splice, slice indexOf,lastIndexOf, push, pop, s ...
- JavaScript中的坑--全局变量惹得祸
js中变量的作用域及闭包的概念 概述 身为一名程序员,因为bug周末加班是必不可少的事情,当解决bug的时候,总有些bug是因为规范导致的,但是这些bug往往不好找,也就是"前人挖坑,后 ...
- 利用Python实现一个感知机学习算法
本文主要参考英文教材Python Machine Learning第二章.pdf文档下载链接: https://pan.baidu.com/s/1nuS07Qp 密码: gcb9. 本文主要内容包括利 ...
- POJ 3984 路径输出
迷宫问题 Description 定义一个二维数组: int maze[5][5] = { 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1, 1, ...
- iOS实现Android中Gone的功能
实现隐藏view但不占位置的需求是很常见的(Android里的view.GONE),可iOS里并没有这玩意,只有hidden.于是自己写了一个一般情况下用的category,特殊情况就得看情况做了.其 ...
- jmeter-Java-MongoDB 数据库增删改查操作
在日常测试过程中会发现有些测试数据是通过数据库来获取的,一般常用的数据比如SQL .Oracle,此类数据库jmeter有专门的插件进行使用JDBC,今天跟大家说一说关于Mongodb这个数据库jme ...
- python cookbook第三版学习笔记十二:类和对象(三)创建新的类或实例属性
先介绍几个类中的应用__getattr__,__setattr__,__get__,__set__,__getattribute__,. __getattr__:当在类中找不到attribute的时候 ...
- 整合spring+mybatis遇到的问题01
报错如下:No matching bean of type [com.mybaties.test.service.UserService] found for dependency: expected ...
- (转)log4j(四)——如何控制不同风格的日志信息的输出?
一:测试环境与log4j(一)——为什么要使用log4j?一样,这里不再重述 1 老规矩,先来个栗子,然后再聊聊感受 import org.apache.log4j.*; //by godtrue p ...
- sql ————视图
视图与表的区别: 区别:1.视图是已经编译好的sql语句.而表不是 2.视图没有实际的物理记录.而表有. 3.表是内容,视图是窗口 4.表只用物理空间而视图不占用物理空间,视图只是逻辑概念的存在,表可 ...