• 【EF】Entity Framework Core 软删除与查询过滤器


    本文翻译自《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 来实现这两件事!

    添加IsDeleted列

    实体框架核心提供了非常灵活的映射。在上一篇关于跟踪列(英文原文)的博客中,您将找到映射列的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 使用ChangeTracker存储所有的更改。您可以在EF生成SQL语句和执行这些语句之前修改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

     
  • 相关阅读:
    Qt之模型/视图(自己定义button)
    Spring入门--控制反转(IOC)与依赖注入(DI)
    iOS关闭键盘简单实现(objc/swift)
    事件传递机制总结
    Skill of vim
    Java中ArrayList和LinkedList区别
    java截取url中的值
    MySQL主从复制与主主复制
    MySQL读写分离
    Mysql-Proxy代理配置
  • 原文地址:https://www.cnblogs.com/yanglang/p/7212164.html
Copyright © 2020-2023  润新知