Liam Wang资料

本文主要介绍Liam Wang资料 方法和在新技术下所面对的“挑战”,方便大家深入理解Liam Wang资料 过程。本文也将分享Liam Wang资料 所遇到的问题和应对策略,怎么解决怎么做的问题。
通过深入本文可以理解代码原理,进行代码文档的下载,也可以查看相应 Demo 部署效果。

原文:https://bit.ly/2Cy3J5f
作者:Jon P Smith
翻译:王亮
声明:我翻译技术文章不是逐句翻译的,而是根据我自己的理解来表述的。其中可能会去除一些本人实在不知道如何组织但又不影响理解的句子。

Liam Wang

这篇文章是关于如何使用 EF Core 实现软删除的,即表面上删除了数据,但数据并没有被物理删除,在需要的时候你还是可以把它读取出来的。软删除有很多好处,但也有一些值得注意的问题。这篇文章会教你使用 EF Core 实现一般的软删除和复杂的级联软删除。在此过程中,我还会介绍如何编写可重用代码来提高软删除解决方案的开发效率。

我假设你对 EF Core 已经有了一定的认识。但在真正讲软删除实现的方案之前,我们先来了解一下如何使用 EF Core 实现删除和软删除的一些基本知识。

本文是“深入理解 EF Core”系列中的第三篇。以下是本系列文章列表:

  • 深入理解 EF Core:当 EF Core 从数据库读取数据时发生了什么?
  • 深入理解 EF Core:当 EF Core 写入数据到数据库时发生了什么?
  • 深入理解 EF Core:使用查询过滤器实现数据软删除(本文)

概要

∮. 你可以使用全局查询过滤器(现在称为查询过滤器)为你的 EF Core 应用程序添加软删除功能。

∮. 在应用程序中使用软删除的主要好处是可以恢复无意的删除和保留历史记录。

∮. 在应用程序中添加软删除功能包含以下三个部分:

  1. 向每个想要软删除的实体类添加一个新的软删除属性。
  2. 在应用程序的 DbContext 中配置查询过滤器。
  3. 创建用于设置或重置软删除属性的代码。

∮. 你可以将软删除与查询过滤器的用途(如多租户使用)结合使用,但是在查找软删除条目时需要更加小心。

∮. 不要软删除一对一的实体类,因为它会导致问题。

∮. 对于具有关联关系的实体类,你需要考虑当顶级实体类被软删除时,依赖关系会发生什么。

∮. 我介绍了一种实现级联软删除的方法,它适用于需要软删除其依赖关系的实体。

为什么需要软删除”>为什么需要软删除

当你硬删除(也叫物理删除)数据时,数据会从你的数据库中彻底消失。此外,硬删除还可能硬删除依赖于所删除行的行(译注:默认未设置级联删除规则的情况下,删除一行数据时,其它通过外键关联该行的数据都会被级联删除)。就像俗话说的那样,“当它离开了,它就永远离开了”——除非你有备份,否则无法取回它。

但现在对数据重视度越来越高的环境下,我们更需要“我使它离开了,但我还可以让它再回来”。在 Windows 上,它是回收站;如果你在编辑器中删除了一些文本,你可以用 ctrl-Z 取回它,等等。软删除就是是 EF Core 版本的实体类回收站(实体类是通过 EF Core 映射到数据库的类的术语),它从正常使用中消失了,但是你可以取回它。

我的客户的两个应用程序广泛地使用了软删除。任何“删除”的普通用户确实设置了软删除标志,但一个管理员用户可以重置软删除标志为“取回”用户。事实上,我的一个客户用“删除”来表示软删除,用“销毁”来表示硬删除。保存被软删除的数据的另一个好处是历史记录——即使是被软删除的数据,你也可以看到过去发生了什么变化。大多数客户的软删除数据在数据库中保留一段时间,只在数月甚至数年后才把这些数据备份或真正删除。

你可以使用 EF Core 查询过滤器实现软删除功能。查询过滤器也用于多租户系统,其中每个租户的数据只能由属于同一租户的用户访问。在这种情况下,数据被软删除,意味着 EF Core 查询过滤器在隐藏信息时非常安全的。

我还应该说,使用软删除也有一些缺点。最主要的缺点是性能。使用软删除在每个实体类的查询中包含一个额外隐藏的 SQL WHERE 子句。

与硬删除相比,软删除处理依赖关系的方式也有所不同。默认情况下,如果您软删除一个实体类,那么它的依赖关系不会被软删除,而实体类的硬删除通常会删除依赖关系。这意味着如果我软删除了一个 Book 实体类,那么这本书的评论仍然是可见的,这在某些情况下可能是个问题。在本文的最后,我将向您展示如何处理这个问题,并讨论一个可以进行级联软删除的库。

为你的应用添加软删除

在本节中,我将逐一介绍在应用程序中添加软删除的如下步骤:

  1. 向需要软删除的实体类添加软删除属性
  2. 向 DbContext 中添加代码,以对这些实体类应用查询过滤器
  3. 如何设置/重置软删除

在下一节中,我将详细描述这些阶段。我假设一个典型的 EF Core 类具有普通的读/写属性,但是你可以将它适应其他实体类样式,比如域驱动设计(DDD)风格的实体类。

1. 添加软删除属性

对于标准的软删除实现,你需要一个布尔标志来控制软删除。例如,这里有一个名叫 SoftDeleted 属性的 Book 实体。

public class Book : ISoftDelete {     public int BookId { get; set; }     public string Title { get; set; }     //... 其它无关属性      public bool SoftDeleted { get; set; } } 

你可以通过它的名字 SoftDeleted 来区分软删除属性,如果它的值是 true 则该实体软删除了。这意味着当你创建一个新实体时,它不会被软删除。

我还添加了一个 Book 类的 ISoftDelete 接口(第 1 行),这个接口表示该类必须有一个可以读写的公共 SoftDeleted 属性。这个接口将使得在 DbContext 中配置软删除查询过滤器变得更加容易。

2. 配置查询过滤器

你必须告诉 EF Core 哪个实体类需要一个查询过滤器,该过滤器是查询表达式,用来把不需要被看到的实体过滤掉。你可以在 DbContext 中使用以下代码手动完成此操作。

public class EfCoreContext : DbContext {     public EfCoreContext(DbContextOptions<EfCoreContext> option)         : base(options)     {}      protected override OnModelCreating(ModelBuilder modelBuilder)     {         // 省略其它和软删除无关的代码          modelBuilder.Entity<Book>().HasQueryFilter(p => !p.SoftDeleted);     } } 

这很好,但是让我向你展示一种自动添加查询过滤器的方法。

在 DbContext 中的 OnModelCreating 方法中,你可以通过 Fluent API 配置 EF Core。但是也有一种方法可以判断每个实体类并决定如何配置它。在下面的代码中,你可以看到 foreach 循环依次遍历每个实体类,检查实体类是否实现了 ISoftDelete 接口,如果实现了,它将调用我创建的扩展方法来应用正确的软删除过滤器配置。

protected override void OnModelCreating(ModelBuilder modelBuilder) {     // 省略其它无关的代码      foreach (var entityType in modelBuilder.Model.GetEntityTypes())     {         // 省略其它无关的代码          if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))         {             entityType.AddSoftDeleteQueryFilter();         }     } } 

有许多配置可以直接应用于 GetEntityTypes 方法返回的类型,但是设置查询过滤器需要更多的工作。这是因为查询过滤器中的 LINQ 查询需要实体类的类型来创建正确的 LINQ 表达式。为此,我创建了一个小型扩展类,它可以动态创建正确的 LINQ 表达式来配置查询过滤器。

public static class SoftDeleteQueryExtension {     public static void AddSoftDeleteQueryFilter(         this IMutableEntityType entityData)     {         var methodToCall = typeof(SoftDeleteQueryExtension)             .GetMethod(nameof(GetSoftDeleteFilter),                 BindingFlags.NonPublic | BindingFlags.Static)             .MakeGenericMethod(entityData.ClrType);         var filter = methodToCall.Invoke(null, new object[] { });         entityData.SetQueryFilter((LambdaExpression)filter);     }      private static LambdaExpression GetSoftDeleteFilter<TEntity>()         where TEntity : class, ISoftDelete     {         Expression<Func<TEntity, bool>> filter = x => !x.SoftDeleted;         return filter;     } } 

我真的很喜欢这个操作,因为它可以节省我的时间,也避免我忘记配置每一个查询过滤器。

3. 如何设置/重置软删除

将“软删除”属性设置为 true 很容易,对应的场景是用户选择一个条目并单击(软)“删除”,这会返回实体的主键。用代码实现如下:

var entity = context.Books.Single(x => x.BookId == id); entity.SoftDeleted = true; context.SaveChanges(); 

重置软删除属性在实际的业务场景中有点复杂。首先,你很可能想要向用户显示一个已删除实体的列表——把它想象成显示某个实体类类型的实例回收站,例如 Book。要做到这一点,需要在你的查询中使用 IgnoreQueryFilters 方法,这意味着你将得到所有的实体(包括那些没有被软删除的和被软删除的),然后再根据需要选出那些 SoftDeleted 属性为 true 的。

var softDelEntities = _context.Books.IgnoreQueryFilters()     .Where(x => x.SoftDeleted).ToList(); 

相应的,当你收到一个重设 SoftDeleted 属性的请求时(它通常包含实体类的主键),则要加载此条目时,需要在查询中使用 IgnoreQueryFilters 方法。

var entity = context.Books.IgnoreQueryFilters()      .Single(x => x.BookId == id); entity.SoftDeleted = false; context.SaveChanges(); 

使用软删除注意事项

首先,需要说的是查询过滤器是非常安全的。我的意思是,如果查询过滤器返回 false,那么特定的实体/行将不会包含在查询(包括 Find 和 Include 等)返回的结果集中。你可以使用直接 SQL 绕过它,但除此之外,EF Core 会隐藏你软删除的数据。

但有几点你需要注意。

小心软删除过滤器与其它过滤器的混合使用

查询过滤器非常适合于软删除,但是查询过滤器更适合于控制对数据组的访问。例如,假设您想要构建一个 Web 应用程序来为多个公司提供服务,比如工资单。在这种情况下,你需要确保 A 公司看不到 B 公司的数据,反之亦然。这种类型的系统称为多租户应用程序,而查询过滤器非常适合此类场景。

可以参考我的另一篇关于使用查询过滤器实现数据访问控制的文章 bit.ly/3hg6Ptg

问题是,每个实体类型只允许使用一个查询过滤器,因此,如果您想在多租户系统中使用软删除,那么您必须将这两个部分结合起来形成查询过滤器——下面是查询过滤器的示例:

modelBuilder.Entity<MyEntity>()     .HasQueryFilter(x => !x.SoftDeleted       && x.TenantId == currentTenantId); 

这看上去很好,但是当你使用 IgnoreQueryFilters 方法忽略软删除标记进行查询时,它会忽略整个查询过滤器,包括多租户部分。因此,如果不小心,还会显示多租户数据!

答案是为自己构建一个特定于应用程序的 IgnoreSoftDeleteFilter 方法,如下所示:

public static IQueryable<TEntity> IgnoreSoftDeleteFilter<TEntity>(     this IQueryable<TEntity> baseQuery, string currentTenantId)     where TEntity : class, ITenantId {     return baseQuery.IgnoreQueryFilters()         .Where(x => x.TenantId == currentTenantId) } 

这将忽略所有筛选器,然后把多租户筛选器添加回去。这将使它更容易更安全地处理显示/重置被软删除的实体。

不要软删除一对一关系的实体类

我曾被邀请帮助客户开发一个非常有趣的系统,它对每个实体类使用软删除。我的客户发现你真的不应该删除一对一关系的实体。他发现的问题是,如果你软删除一个一对一关系,并试图添加一个替换的一对一实体,那么它将失败。这是因为一对一关系有一个唯一的外键,而且这个外键已经被软删除实体设置好了,所以在数据库级别上,你无法提供另一个一对一关系,因为已经存在一个。

一对一关系很少,所以在您的系统中它可能不是问题。但如果您确实需要软删除一对一关系中的实体,那么我建议将其转换为一对多关系,确保只有一个实体关闭了软删除,我将在下一个问题中介绍。

译注:对于大多数一对一场景,当软删除一个实体时,与其一对一关联的实体应当也标记为软删除。

注意多版本数据的软删除

在一些业务案例中,你可以创建一个实体,然后软删除它,然后创建一个新版本。例如,假设您正在为订单 1234 创建发票,然后您被告知订单已经停止,因此你将其软删除(这样您可以保留历史记录)。然后,其他人(不知道软删除版本的人)被告知创建 1234 的发票。现在你有两个版本的发票 1234。这就可能会导致业务上的有问题的发票,特别是当有人重置软删除的数据版本时。

你有以下方式处理这种情况:

  • 将 DateTime 类型的 LastUpdated 属性添加到你的 Invoice 实体类中,使用的是最新的条目,而不是软删除条目。
  • 每个新条目都有一个版本号,因此在我们的示例中,第一个发票的版本号可以是 1234-1,依次为 1234-2。那么,就像 LastUpdated 的版本一样,版本号最高且没有被软删除的发票才是要使用的。
  • 通过使用唯一过滤索引,确保只有一个非软删除版本。这是通过为所有未被软删除的条目创建一个惟一的索引来实现的,这意味着如果你试图重置已被软删除的发票,但那里已经存在一个已被非软删除的发票,那么你将会得到一个异常。但同时,你可以有很多历史软删除版本。Microsoft SQL Server RDBMS, PostgreSQL RDBMS, SQLite RDBMS 都有这个特性(PostgreSQL 和 SQLite 称为部分索引),据说 MySQL 出有类似的东西。下面的代码是 SQL Server 创建唯一过滤索引的示例:
CREATE UNIQUE INDEX UniqueInvoiceNotSoftDeleted ON [Invoices] (InvoiceNumber) WHERE SoftDeleted = 0 

关于处理因索引问题而出现的异常,请参阅我的文章“Entity Framework Core – validating data and capture SQL error”(地址:bit.ly/3jpRA2W),这篇文章展示了如何将 SQL 异常转换为用户友好的错误表示。

如何处理与软删除关联的实体

到目前为止,我们一直在
Liam Wang资料部分资料来自网络,侵权毕设源码联系删除

区块链毕设网(www.qklbishe.com)全网最靠谱的原创区块链毕设代做网站
部分资料来自网络,侵权联系删除!
资源收费仅为搬运整理打赏费用,用户自愿支付 !
qklbishe.com区块链毕设代做网专注|以太坊fabric-计算机|java|毕业设计|代做平台 » Liam Wang资料

提供最优质的资源集合

立即查看 了解详情