概要

我们在项目开发中,通常会有数据审计的项目需求。即业务数据中要包含创建日期,修改日期,修改人等信息等。有些业务数据需要物理删除,有些数据需要逻辑删除。

通常审计数据并不大量参与业务运算,只是为审计提供技术支持。如果我们在项目开发中,花费大量时间在这些审计数据的处理上,显然得不偿失。

本文提出了一个简单的审计数据处理模型,通过EntityFramework加以实现。

审计数据的需求总结

  1. 核心所有业务数据都要包括数据的创建人,创建时间,修改时间和修改人。
  2. 大部分核心数据要支持逻辑删除,所以要包括删除人,删除时间和删除标记位。
  3. 部分非核心业务数据需要支持物理删除。

审计数据模型的实现

在现实开发中,为了实现审计数据功能,我们需要为核心的业务数据类增加相应的审计数据栏位。但是没有必要每个类逐一定义这些栏位,我们可以使用OOP的继承,多态等特性,简化开发。

本文以一个银行分行的管理系统为例,来说明我们的审计数据实现模型。银行分行的业务员数据包括分行(Branch),ATM机和系统的用户User。

审计数据模型的类图

  1. 上图分为两部分,一部分是审计模型,另一部分是该模型在业务数据上的应用。
  2. IEntity是一个接口,包括创建人,创建时间,修改时间和修改人。
  3. Entity是一个抽象类,实现IEnitty接口,因此也包含创建人,创建时间,修改时间和修改人四个栏位。
  4. IDeletable是一个接口,继承IEntity接口,新增了删除人,删除时间和删除标记位。以支持逻辑删除。
  5. DeletableEntity是一个抽象类,继承Entity类并实现IDeletable接口。

审计数据模型的应用

业务数据类 继承Entity 继承DeletableEntity 实现IDeletableEntity接口
ATM
Branch
User
  1. ATM机器升级换代很快,因此需求是直接物理删除数据就可以,每次维护的数据可以记录到数据中。
  2. 分行数据需要逻辑删除,数据本身需要被追溯,可以直接继承DeletableEntity 类
  3. User数据需求和分行数据一样,但是User类已经继承了IdentityUser类,因此只能实现IDeletableEntity接口。

审计数据模型中的拦截器

通过继承关系和接口的使用,我们避免了为每个数据类都人工定义审计相关的栏位。

但是审计数据的存取依然是一个问题,我们如果在每个类的存取代码中都增加审计数据的处理,显然做就过于繁琐了。

本文的解决方法是在EntityFramework中,定义一个拦截器去实现。而我们模型中定义的类和接口,在拦截器中成了我们重要的路标。

核心代码如下:

 public class BankContext : DbContext{private void AddAuditData() =>this.ChangeTracker.Entries().ToList().ForEach(entry =>{if (entry.State == EntityState.Deleted){if (entry.Entity is IDeletableEntity deletableEnitty){deletableEnitty.DeletedBy = 2;deletableEnitty.DeletedOn = DateTime.Now;deletableEnitty.IsDeleted = true;entry.State = EntityState.Modified;return;}}if (entry.Entity is IEntity entity){if (entry.State == EntityState.Added){entity.CreatedBy = 2;entity.CreatedOn = DateTime.Now;}else if (entry.State == EntityState.Modified){entity.ModifiedBy = 2;entity.ModifiedOn = DateTime.Now;}}});public override int SaveChanges(){AddAuditData();return base.SaveChanges();}}
  1. 使用DBContext中的ChangeTracker对象,找到我们存取数据的对象。
  2. 如果用户要删除该对象,但是该类实现了IDeletableEntity 接口,我们将对象状态改为Modified,填充删除人,删除时间和删除标记位。
    A. 删除人即为当前用户,不同系统,获取方式不同,因此本文将其略去,默认写成一个Id为2的用户。
    B. 这样用户上层代码就不用再区分物理删除和逻辑删除。所有删除代码都按照物理删除的方式写就行。
  3. 如果当前存取对象实现了IEntity 接口:
    A. 如果是新建数据,则填充创建人和创建时间的栏位
    B. 如果是修改数据,则填充修改人和修改时间的栏位
  4. 复写SaveChanges方法,在数据存取前调用我们定义的方法AddAuditData
  5. SaveChanges还有其他重载方法,因为修改方式类似,所以不再赘述。

审计数据模型的实验验证

数据表生成

执行EF的migration指令,生成数据Branch,ATM和User数据表。完整类型定义请参考附录。

执行Migration指令:

dotnet ef migrations add InitialCreate
dotnet ef database update

执行结果:


从执行结果中我们可以看出,生成的SQL语句中已经包含的审计相关的栏位,我们不希望每个类都定义审计栏位的需求已经实现。

数据存取

创建用户

using (var context = new BankContext()){var u = new User(){Name = "Tom",       };context.Useres.Add(u);context.SaveChanges();
}

CreatedBy和CreatedOn的审计数据被拦截器自动填充

修改用户

using (var context = new BankContext(connectionString)){var u = context.Useres.Where( b=>b.Id == 1).FirstOrDefault();u.Name = "abc";context.Update(u);context.SaveChanges();}

ModifiedBy和ModifiedOn的审计数据被拦截器自动填充

删除用户

 using (var context = new BankContext()){var u = context.Useres.Where( b=>b.Id == 1).FirstOrDefault();context.Useres.Remove(u);context.SaveChanges();}

物理删除被自动转化为逻辑删除,删除的审计数据栏位被自动填充

创建分行

using (var context = new BankContext()){var branch = new Branch(){Name = "天津新天地支行",Address = "天津市滨海新区南海路12号新天地华庭A座1门103号",};context.Branches.Add(branch);context.SaveChanges();}

CreatedBy和CreatedOn的审计数据被拦截器自动填充

修改分行

using (var context = new BankContext()){var branch = context.Branches.Where( b=>b.Id == 1).FirstOrDefault();context.Update(branch);context.SaveChanges();
}

ModifiedBy和ModifiedOn的审计数据被拦截器自动填充

删除分行

using (var context = new BankContext()){var branch = context.Branches.Where( b=>b.Id == 1).FirstOrDefault();context.Branches.Remove(branch);context.SaveChanges();
}

物理删除被自动转化为逻辑删除,删除的审计数据栏位被自动填充

创建ATM

using (var context = new BankContext()){var atm = new ATM(){Name = "取款机-1",};context.ATMs.Add(atm);context.SaveChanges();}

CreatedBy和CreatedOn的审计数据被拦截器自动填充

修改ATM

using (var context = new BankContext()){var atm = context.ATMs.Where( b=>b.Id == 1).FirstOrDefault();atm.Name = "abc";context.Update(atm);context.SaveChanges();
}

ModifiedBy和ModifiedOn的审计数据被拦截器自动填充

删除ATM

using (var context = new BankContext()){var atm = context.ATMs.Where( b=>b.Id == 1).FirstOrDefault();context.ATMs.Remove(atm);context.SaveChanges();
}

根据需求,ATM数据需要物理删除,因此ATM类没有继承DeletableEntity或实现IDeletableEntity接口,因此直接删除

附录

ATM类

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IQueryableIEnumerable
{[Table("t_atm")]public class ATM : Entity{[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column(Order = 0)]public int Id { get; set; }[Required, Column(Order = 1)]public string Name { get; set; }[Timestamp, Column(Order = 20)]public byte[] RowVersion { get; set; }}
}

Branch类

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace IQueryableIEnumerable
{[Table("t_branch")]public class Branch : DeletableEnitty{[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column(Order = 0)]public int Id { get; set; }[Required, Column(Order = 1)]public string Name { get; set; }[Required, Column("Addr", Order = 2)]public string Address { get; set; } [Timestamp, Column(Order = 20)]public byte[] RowVersion { get; set; }}
}

User类

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System;
namespace IQueryableIEnumerable
{[Table("t_user")]public class User : IDeletableEntity{[Key, DatabaseGenerated(DatabaseGeneratedOption.Identity), Column(Order = 0)]public int Id { get; set; }[Required, Column(Order = 1)]public string Name { get; set; }public DateTime? DeletedOn { get; set; }public int? DeletedBy { get; set; }public bool IsDeleted { get; set; } = false;public int CreatedBy { get; set; }public DateTime CreatedOn { get; set; }public int? ModifiedBy { get; set; }public DateTime? ModifiedOn { get; set; }[Timestamp, Column(Order = 20)]public byte[] RowVersion { get; set; }}
}

BankContext类:

using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
namespace IQueryableIEnumerable
{public class BankContext : DbContext{private readonly ILoggerFactory loggerFactory= LoggerFactory.Create(ConventionForeignKeyExtensions => ConventionForeignKeyExtensions.AddConsole());private readonly string ConnectionString;public DbSet<Branch> Branches { get; set; }public DbSet<User> Useres { get; set; }public DbSet<ATM> ATMs { get; set; }public BankContext() : base(){ConnectionString = @"your connection string";}public BankContext(DbContextOptions<BankContext> options) : base(options){ConnectionString = @"your connection string";}protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder){optionsBuilder.UseLoggerFactory(loggerFactory);optionsBuilder.UseSqlServer(ConnectionString);}public override int SaveChanges(){AddAuditData();return base.SaveChanges();}private void AddAuditData() =>this.ChangeTracker.Entries().ToList().ForEach(entry =>{if (entry.State == EntityState.Deleted){if (entry.Entity is IDeletableEntity deletableEnitty){deletableEnitty.DeletedBy = 2;deletableEnitty.DeletedOn = DateTime.Now;deletableEnitty.IsDeleted = true;entry.State = EntityState.Modified;return;}}if (entry.Entity is IEntity entity){if (entry.State == EntityState.Added){entity.CreatedBy = 2;entity.CreatedOn = DateTime.Now;}else if (entry.State == EntityState.Modified){entity.ModifiedBy = 2;entity.ModifiedOn = DateTime.Now;}}});}}

审计数据在EntityFramework中的解决方案相关推荐

  1. 审计MySQL 8.0中的分类数据查询

    面临的挑战 通常,涉及到敏感信息时用户需要使用审计日志.不仅仅是在表上运行Select,还包括访问表中的特定单元格.通常,这类数据将包含一个分类级别作为行的一部分,定义如何处理.审计等策略. 诸如此类 ...

  2. 数据科学工作中存在的7大问题与解决方案

    注:在本文中,虽然我使用数据科学家一词,但是诸如机器学习工程师,数据分析师,数据工程师,BI分析师之类的热门职务也承担着类似的责任,可以在此处互换使用. 本文编译自Dan_Friedman的技术博客. ...

  3. Kafka中产生数据积压的原因以及解决方案

    Kafka中产生数据积压的原因以及解决方案 1.kafka中数据积压的原因 kafka作为消息队列,其中数据积压也是经常遇到的问题之一.我们都知道,数据积压的直接原因,一定是系统中的某个部分出现了性能 ...

  4. 大数据在政府中的应用案例

    本文来自网易云社区. 政府掌握着全社会量最大.最核心的数据.了解政府大数据应用的案例和数据有助于释放政府数据的价值,也有利于民众对政府的了解.下面来看看大数据在政府中的应用案例. 大数据在政府中的应用 ...

  5. DTCC:数据库安全重点在数据拷贝过程中

    本文讲的是DTCC:数据库安全重点在数据拷贝过程中,2017年5月11日-13日,2017中国数据库技术大会于北京国际会议中心盛大开幕.作为国内最受关注的数据库技术大会,本届大会以"数据驱动 ...

  6. 人工智能和大数据的开发过程中需要注意这12点

    https://www.toutiao.com/i6636522371094151694/ 2018-12-19 10:16:15 人工智能是近年来科技发展的重要方向,在大数据时代,对数据采集.挖掘. ...

  7. 中小企业数据异地备份容灾解决方案

    中小企业数据异地备份容灾解决方案                [url]www.eisoo.com[/url] 详情 [email]wmx@eisoo.com[/email] 一. 背景: 随着信息 ...

  8. “冷热通道气流遏制系统”在数据中心机房中的应用

    前言: 对于采用冷热通道气流遏制系统与未采用气流遏制措施的传统数据中心相比,气流遏制解决方案能够消除热点,提高节能效果,对已有数据中心来说,最佳气流遏制解决方案取决于数据中心的约束条件,下面本文讲解& ...

  9. 超优 Vue+Element+Spring 中后端解决方案

    今日推荐 推荐一款开源 Java 版的视频管理系统 推荐3个快速开发平台 前后端都有 项目经验又有着落了 14个项目 转载:toutiao.com/i6911704074815767048 作者:we ...

最新文章

  1. P3168 [CQOI2015]任务查询系统 差分+主席树
  2. 《SAP HANA平台应用开发》—第3章3.1节信息建模
  3. 中国互联网的抑郁:抄与被抄都很痛
  4. 浏览器卡怎么办_SD卡无法格式化怎么修复?简单修复方法介绍
  5. springboot的yml配置文件绑定时必须和相应的类中的属性类型对应,不然启动报错
  6. 此项目的默认Web访问模式设置为文件共享, 但是无法从路径(此为转贴)
  7. Python使用matplotlib绘制正多边形逼近圆周
  8. JAVA day10、11、12 飞机大战
  9. 王校长撩妹不成反被锤爆?再有钱的舔狗也只是舔狗【Python爬虫实战:微博评论采取】
  10. JavaScript判断数组的方法
  11. 程序员做自媒体变现,可以选择的平台有哪些?各自的特点是什么?
  12. 龙之谷手游微信连接授权服务器失败,龙之谷手游ios微信授权失败怎么办_龙之谷手游ios微信授权失败解决办法-66街机网...
  13. 4.1.5 消费者获取记录
  14. 1.从第一道面试题谈起
  15. Windows改装成Linux,Windows系统改装成Linux系统
  16. Swing设置窗体背景图片
  17. 非常感人非常激发人的潜能的信,你一定要看!
  18. 毕业设计之 ---基于大数据分析的航空公司客户价值分析
  19. 什么是WIFI真机同步?
  20. 北京市工作居住证只有电子版没有纸质版

热门文章

  1. 恋舞OL辅助蜂窝版教程
  2. 域名解析需要多久生效?域名解析常见问题解答
  3. 关于Bootstrap 的 data-toggle 标签
  4. 矩阵和向量的导数运算
  5. 电子通信类顶级会议及期刊1(自用更新版)
  6. 几何图形构成的矢量化极简风格美术
  7. java高性能线程读取大文件并分段分流翻译入库
  8. docker 使用理解 全流程
  9. B.FRIENDit壁虎忍者RF600+MA003无线键盘鼠标套装 超薄静音键盘 无线鼠标键盘 台式笔记本电脑办公巧克力键盘黑色
  10. linux驱动篇-touchscreen-完整版