• Ef Core实现数据审计与软删除


    Ef Core数据审计与软删除

    以下都是干货,不整那没用的

    软删除的作用这里就不说了,懂得都懂。
    审计就是记录实体最后一次被修改的时间,修改人的id,创建的时间,创建人的id,删除的时间,删除的人的id

    审计接口

    IAuditInsert.cs

    /// 
    /// 审计添加接口
    /// 
    public interface IAuditInsert
    {
        long? CreatorUserId { get; set; }
        DateTime CreationTime { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    IAuditModif.cs

    /// 
    /// 审计修改接口
    /// 
    public interface IAuditModif
    {
        public DateTime? LastModificationTime { get; set; }
        public long? LastModifierUserId { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    IEntity.cs

    /// 
    /// 实体基类接口
    /// 
    /// 主键
    public interface IEntity<TPrimaryKey> : IEntity
    {
        TPrimaryKey Id { get; set; }
    }
    public interface IEntity
    {
        
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    审计父类

    AuditInsertEntity.cs

    /// 
    /// 审计添加基类
    /// 
    /// 主键类型
    public class AuditInsertEntity<TPrimaryKey> :Entity<TPrimaryKey> , IAuditInsert
    {
        public long? CreatorUserId { get; set; }
        public DateTime CreationTime { get ; set ; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    AuditModifEntity.cs

    /// 
    /// 审计修改基类
    /// 
    /// 主键类型
    public class AuditModifEntity<TPrimaryKey> : Entity<TPrimaryKey>,IAuditModif
    {
        public DateTime? LastModificationTime { get; set; }
        public long? LastModifierUserId { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    Entity.cs

     /// 
     /// 实体基类泛型
     /// 
     /// 主键类型
     public abstract class Entity<TPrimaryKey> : IEntity<TPrimaryKey>
     {
         public TPrimaryKey Id { get; set; }
     }
     /// 
     /// 实体基类
     /// 
     public class Entity : IEntity
     {
         public int Id { get; set; }
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    AuditdEntity.cs

    /// 
    /// 基础审计实体
    /// 
    /// 
    public abstract class AuditdEntity<TPrimaryKey> : AuditInsertEntity<TPrimaryKey>, IAuditModif
    {
        public DateTime? LastModificationTime { get ; set ; }
        public long? LastModifierUserId { get ; set ; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    软删除接口

    这里,我将软删除与审计直接结合了,严谨一点的话,应该只有一个IsDeleted才对,DeletionTimeDeleterUserId属于审计范畴内。

    ISoftDelete.cs

    /// 
    /// 软删除接口
    /// 
    public interface ISoftDelete
    {
        bool IsDeleted { get; set; }
        DateTime? DeletionTime { get; set; }
        long? DeleterUserId { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    AuditSoftDelete.cs

    /// 
    /// 软删除基类
    /// 
    public abstract class AuditSoftDelete : ISoftDelete
    {
        bool IsDeleted { get; set; }
        DateTime? DeletionTime { get ; set ; }
        long? DeleterUserId { get ; set ; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    全功能审计

    IFullAuditedEntity .cs接口

    /// 
    /// 审计接口
    /// 
    /// 
    public interface IFullAuditedEntity<TPrimaryKey> :IEntity<TPrimaryKey> ,IAuditInsert, IAuditModif, ISoftDelete
    {
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    FullAuditedEntity.cs实现类

    /// 
    /// 全功能审计基类
    /// 
    /// 
    public class FullAuditedEntity<TPrimaryKey> : AuditdEntity<TPrimaryKey>, IFullAuditedEntity<TPrimaryKey>, ISoftDelete
    {
        public bool IsDeleted { get; set ; }
        public DateTime? DeletionTime { get; set; }
        public long? DeleterUserId { get ; set ; }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    具体实现

    1. 主要逻辑在于重写SaveChangesAsync方法,在SaveChanges方法执行前,通过遍历被跟踪的实体,判断是否继承了我们写好的审计接口来做相应的处理。
    2. 创建时间,修改时间这类数据都很好获取,首先需要用户进行登录,然后给用户颁发一个token并且设置好用户的id,后续请求解析用户带过来的token中的信息,获取用户信息这里就需要利用到IHttpContextAccessor。这里建议通过属性注入的方式来获取IHttpContextAccessor。如果用的是构造函数注入,可能会出现很多问题。
    3. 属性注入的具体实现,将在末尾写到。
    4. 这里就不详细讲token的生成和颁发了,需要的可以自行了解。

    DbContext

    public class SystemDbContext:DbContext
    {
        public IHttpContextAccessor _httpContextAccessor { get; set; }
        public SystemDbContext(DbContextOptions<CarRentalSystemDbContext> options):base(options)
        {
    
        }
    
        #region Override
    
        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.ConfigurationQueryFilter();
        }
        public override int SaveChanges(bool acceptAllChangesOnSuccess)
        {
            ConfigurationOnBeforeSaving();
            return base.SaveChanges(acceptAllChangesOnSuccess);
        }
    
        public override Task<int> SaveChangesAsync(bool acceptAllChangesOnSuccess, CancellationToken cancellationToken = new CancellationToken())
        {
            ConfigurationOnBeforeSaving();
            return base.SaveChangesAsync(acceptAllChangesOnSuccess, cancellationToken);
        }
    
        #endregion
    
        #region Private Method
        private void ConfigurationOnBeforeSaving()
        {
            var userId = _httpContextAccessor.HttpContext.User.FindFirst(ClaimTypes.NameIdentifier).Value;
            var uid = long.Parse(userId);
            foreach (var entry in ChangeTracker.Entries())
            {
                var entityType = entry.Entity.GetType();
                switch (entry.State)
                {
                    case EntityState.Deleted:
                        if (entry.Entity is ISoftDelete)
                        {
                            entry.State = EntityState.Modified;
                            entry.CurrentValues["IsDeleted"] = true;
                            entry.CurrentValues["DeleterUserId"] = uid;
                            entry.CurrentValues["DeletionTime"] = DateTime.Now;
                        }
                        break;
                    case EntityState.Modified:
                        if (entry.Entity is IAuditModif)
                        {
                            if (entry.Entity is IAuditInsert)
                            {
                                entry.Property("CreatorUserId").IsModified = false;
                                entry.Property("CreationTime").IsModified = false;
                            }
                            entry.CurrentValues["LastModifierUserId"] = uid;
                            entry.CurrentValues["LastModificationTime"] = DateTime.Now;
                        }
                        break;
                    case EntityState.Added:
                        if (entry.Entity is IEntity)
                        {
                            var idType = entry.CurrentValues["Id"]?.GetType();
                            if (idType != null && idType == typeof(Guid))
                            {
                                entry.CurrentValues["Id"] = Guid.NewGuid();
                            }
                        }
    
                        if (entry.Entity is IAuditInsert)
                        {
                            entry.CurrentValues["CreatorUserId"] = uid;
                            entry.CurrentValues["CreationTime"] = DateTime.Now;
                        }
                        break;
                }
            }
        }
        #endregion
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82

    软删除

    实现思路

    主要实现是判断当被Ef 跟踪的实体的状态为Deleted并实现了ISoftDelete接口时,我们将状态手动改为EntityState.Modified,然后将字段IsDeleted的值设置为true

    自动过滤

    在代码OnModelCreating方法中,有一个扩展方法modelBuilder.ConfigurationQueryFilter();是我们自己扩展的,我们在其中定义了一个全局过滤器,具体代码如下:

    public static class DbContextQueryFilter
    {
        public static void ConfigurationQueryFilter(this ModelBuilder modelBuilder)
        {
            var entityTypes = modelBuilder.Model.GetEntityTypes();
            foreach (var entityType in entityTypes)
            {
                if (typeof(ISoftDelete).IsAssignableFrom(entityType.ClrType))
                {
                    var parameter = Expression.Parameter(entityType.ClrType, "del");
                    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));
                }
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    属性注入

    以下代码均在Web

    这里我们使用的是AutoFac第三方容器。

    所需要的包:AutofacAutofac.Extensions.DependencyInjection

    1. 新建一个 Autofac 的模块,将DbContext支持属性注入,使用PropertiesAutowired()

      public class BusinessModule : Autofac.Module
      {
          protected override void Load(ContainerBuilder builder)
          {
              builder.RegisterType<HttpContextAccessor>().As<IHttpContextAccessor>();
              builder.RegisterType<SystemDbContext>().PropertiesAutowired().InstancePerLifetimeScope();
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
    2. Startup.cs中添加ConfigureContainer方法

      public void ConfigureContainer(ContainerBuilder builder)
      {
          builder.RegisterModule(new BusinessModule());
      }
      
      • 1
      • 2
      • 3
      • 4

    使用

    你可以根据你的需要让你的实体类继承审计基类,如果你需要所有的审计功能,你可以像如下一样:

    public class TestModel : FullAuditedEntity<Guid>
    {
        public string Msg { get; set; }
    }
    
    • 1
    • 2
    • 3
    • 4

    数据库中如下:
    在这里插入图片描述

  • 相关阅读:
    影像组学特征提取代码错误
    pgsql的窗口函数简述
    有关异常的处理、捕获、抛出、自定义
    密码学在 Web3 钱包中的应用:私钥是什么?bitget钱包为例
    Vue-07-vue-router路由
    Java随记 —— Servlet 教程笔记
    数据结构——归并排序
    File类和IO流的相关面试(二)
    算法---格雷编码
    Blazor WebAssembly 渐进式 Web 应用程序 (PWA) 离线处理数据
  • 原文地址:https://blog.csdn.net/hyx1229/article/details/127213192