本文翻译自《Entity Framework Core: Soft Delete using Query Filters》,由于水平有限,故无法保证翻译完全正确,欢迎指出错误。谢谢!
注意:我使用的是 Entity Framework Core 2.0 (2.0.0-preview2-final)。正式版发布后,功能可能存在变动。
继续探索 Entity Framework Core 2.0,今天我将探讨如何轻松使用软删除(或逻辑删除)。我的意思是以透明的方式实现软删除,例如,您是物理上的删除行。
要实现软删除,您需要添加一列以指示该行数据是否被逻辑删除。如果您想知道该行被删除,可以使用布尔列,如果您想知道删除的时间,可以使用日期列。其次是更改所有查询,使用此列过滤结果集;您还需要将删除语句替换成为更新语句。
现在我们来看看如何用 Entity Framework Core 来实现这两件事!
实体框架核心提供了非常灵活的映射。在上一篇关于跟踪列 (英文原文) 的博客中,您将找到映射列的 3 种方法。在介绍中,我已经说过软删除应该是透明的,所以我决定在类型中不暴露
属性。类型定义如下:
- IsDeleted
- public class Blog {
- public int BlogId {
- get;
- set;
- }
- public string Url {
- get;
- set;
- }
- public List < Post > Posts {
- get;
- set;
- }
- }
- public class Post {
- public int PostId {
- get;
- set;
- }
- public string Title {
- get;
- set;
- }
- public string Content {
- get;
- set;
- }
- public int BlogId {
- get;
- set;
- }
- public Blog Blog {
- get;
- set;
- }
- }
现在,我们需要向 Entity Framework Core 指明类型有一个附加列:
- public class BloggingContext: DbContext {
- public DbSet < Blog > Blogs {
- get;
- set;
- }
- public DbSet < Post > Posts {
- get;
- set;
- }
- protected override void OnModelCreating(ModelBuilder modelBuilder) {
- base.OnModelCreating(modelBuilder);
- modelBuilder.Entity < Post > ().Property < bool > ("IsDeleted");
- }
- }
Entity Framework Core 使用
存储所有的更改。您可以在 EF 生成 SQL 语句和执行这些语句之前修改
- ChangeTracker
。
- ChangeTracker
- public class BloggingContext: DbContext {
- public override int SaveChanges(bool acceptAllChangesOnSuccess) {
- OnBeforeSaving();
- return base.SaveChanges(acceptAllChangesOnSuccess);
- }
- public override Task < int > SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken =
- default(CancellationToken)) {
- OnBeforeSaving();
- return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
- }
- private void OnBeforeSaving() {
- foreach(var entry in ChangeTracker.Entries < Post > ()) {
- switch (entry.State) {
- case EntityState.Added:
- entry.CurrentValues["IsDeleted"] = false;
- break;
- case EntityState.Deleted:
- entry.State = EntityState.Modified;
- entry.CurrentValues["IsDeleted"] = true;
- break;
- }
- }
- }
- }
现在生成以下代码执行的 SQL 语句:
- using(var context = new BloggingContext()) {
- var post = new Post {
- Blog = blog
- };
- context.Posts.Add(post);
- context.SaveChanges();
- }
- exec sp_executesql N 'SET NOCOUNT ON;
- INSERT INTO [Posts] ([BlogId], [Content], [IsDeleted], [Title])
- VALUES (@p1, @p2, @p3, @p4);
- SELECT [PostId]
- FROM [Posts]
- WHERE @@ROWCOUNT = 1 AND [PostId] = scope_identity();
- -- @p3 is 0 (false)
- ',
- N '@p1 int,@p2 nvarchar(4000),@p3 bit,@p4 nvarchar(4000)',
- @p1 = 1,
- @p2 = NULL,
- @p3 = 0,
- @p4 = NULL
- context.Posts.Remove(post);
- context.SaveChanges();
- exec sp_executesql N 'SET NOCOUNT ON;
- UPDATE [Posts] SET [BlogId] = @p0, [Content] = @p1, [IsDeleted] = @p2, [Title] = @p3
- WHERE [PostId] = @p4;
- SELECT @@ROWCOUNT;
- ',
- N '@p4 int,@p0 int,@p1 nvarchar(4000),@p2 bit,@p3 nvarchar(4000)',
- @p4 = 1,
- @p0 = 1,
- @p1 = NULL,
- @p2 = 1,
- @p3 = NULL
插入和删除请求已经被处理,您现在还必须更改所有查询语句。
Entity Framework Core 2.0 引入了一个新的概念:查询过滤器。查询过滤器总是在生成的查询语句后面追加一个的
子句。这意味着,您可以在模型创建时声明一个实体的过滤器,然后将此过滤器隐式添加到使用该表的生成的每个查询语句中。
- where
- public class BloggingContext: DbContext {
- protected override void OnModelCreating(ModelBuilder modelBuilder) {
- base.OnModelCreating(modelBuilder);
- modelBuilder.Entity < Post > ().Property < bool > ("IsDeleted");
- modelBuilder.Entity < Post > ().HasQueryFilter(post = >EF.Property < bool > (post, "IsDeleted") == false);
- }
- }
让我们看看查询过滤器的作用:
- var posts = context.Posts.ToList();
- SELECT[p]. [PostId],
- [p]. [BlogId],
- [p]. [Content],
- [p]. [IsDeleted],
- [p]. [Title] FROM[Posts] AS[p] WHERE[p]. [IsDeleted] = 0--Query filter
查询过滤器也可以用于关联查询:
- var blogs = context.Blogs.Include(_ = >_.Posts);
- SELECT[_]. [BlogId],
- [_]. [Url] FROM[Blogs] AS[_] ORDER BY[_]. [BlogId]
- SELECT[p]. [PostId],
- [p]. [BlogId],
- [p]. [Content],
- [p]. [IsDeleted],
- [p]. [Title] FROM[Posts] AS[p] INNER JOIN(SELECT[_0]. [BlogId] FROM[Blogs] AS[_0]) AS[t] ON[p]. [BlogId] = [t]. [BlogId] WHERE[p]. [IsDeleted] = 0--Query filter ORDER BY[t]. [BlogId]
通过查询过滤器实现软删除非常容易 :)
如果您要还原已删除的行,您必须能够查询到这些数据。这意味着您需要临时删除查询过滤器。EF 已经添加了一种新方法
来表明您不希望将查询过滤器用于当前查询。
- IgnoreQueryFilters
- var deletedPosts = context.Posts.IgnoreQueryFilters().Where(post = >EF.Property < bool > (post, "IsDeleted") == true);
恢复已删除的帖子有点啰嗦,您需要更改跟踪器中查询并更新
属性。
- IsDeleted
- var deletedPosts = context.Posts.IgnoreQueryFilters().Where(post = >EF.Property < bool > (post, "IsDeleted") == true);
- foreach(var deletedPost in deletedPosts) {
- var postEntry = context.ChangeTracker.Entries < Post > ().First(entry = >entry.Entity == deletedPost);
- postEntry.Property("IsDeleted").CurrentValue = false;
- }
- context.SaveChanges();
使用 Entity Framework Core 2.0 实现软删除模式非常简单,并且可以是透明的。实际上,您可以无需更改 LINQ 代码的情况下,将软删除添加到现有模型中。
来源: http://www.cnblogs.com/tdfblog/p/entity-framework-core-soft-delete-using-query-filters.html