Entity Framework Core 捕获数据库变动

简介: Entity Framework Core 捕获数据库变动

在实际项目中我们往往需要记录存储在数据库中数据的变动(例如修改数据前记录下数据的原始值),这样一来在发生误操作时可以将数据恢复到变动前的状态,也可以追溯到数据的修改人。大部分开发人员会自己定义记录数据变动的代码,但是这样不仅费时费力有时还会影响到这个业务的性能。当然,我们也可以利用数据库触发器来记录这些操作,在 SQL Server 数据库 2017 以上版本中给我们提供了跟踪数据库数据更改的功能,利用这个功能可以准确的记录数据库数据的变动。这个功能虽然强大但是某些时候我们使用的数据库并不是 SQL Server 数据库,或者某些情况下我们不适合使用 SQL Server 数据库所提供的这个功能。那么这个时候该怎么办呢?如果你使用的是 Entity Framework Core 2.0 及以上版本来开发项目的话,那这个问题就好解决了。在 Entity Framework Core 中,只要捕获到了数据变更记录,我们就可以将数据随时还原到变更前的状态,在这里数据库变更记录被称为审计数据。那么我们先来看两个问题:


  1. 审计数据是在什么时候产生并写入数据库的呢?
  2. 数据的新旧值是如何获取到的呢?

要解答上述两个问题,那就跟着我一起来看看怎么利用 Entity Framework Core 来捕获审计数据。

零、创建审计模型

捕获审计数据并存入数据库的第一步是创建审计模型,只有具有了审计模型的审计数据才能正确的存入数据库。

public class Audit
{
    public int Id { get; set; }
    public string TableName { get; set; }
    public DateTime DateTime { get; set; }
    [NotMapped]
    public Operation Operation { get; set; }
    public string OperationString
    {
        get { return Operation.ToString(); }
        private set { Operation = (Operation)Enum.Parse(typeof(Operation), value, true); }
    }
    public string Key { get; set; }
    public string Old { get; set; }
    /// <summary>
    /// 操作后的数据
    /// </summary>
    public string New { get; set; }
}
/// <summary>
/// 操作类型
/// </summary>
public enum Operation
{
    Add = 0,
    Delete = 1,
    Modified = 2
}

上述代码创建的审计模型包含被操作表的名称 TableName 、操作的类型 Operation 、被操作数据的主键 Key 、 操作前的数据 Old 以及操作后的数据 New ,其中操作类型包含了增删改。


一、创建审计数据存储

现在我们有了审计模型,但是只有审计模型还不行,我们还需要创建和存储审计数据相关的类,下面我们就来一起创建这个类。

public class AuditDb
{
    public EntityEntry _entityEntry { get; set; }
    public AuditDb(EntityEntry entityEntry)
    {
        this._entityEntry = entityEntry;
    }
    public string TableName { get; set; }
    public Operation Operation { get; set; }
    public Dictionary<string, object> keys { get; } = new Dictionary<string, object>();
    public Dictionary<string, object> olds { get; } = new Dictionary<string, object>();
    public Dictionary<string, object> news { get; } = new Dictionary<string, object>();
    public List<PropertyEntry> propertyEntries { get; } = new List<PropertyEntry>();
    public bool HasPropertyEntries => propertyEntries.Any();
    public Audit ToAudit()
    {
        Audit audit = new Audit
        {
            TableName = TableName,
            Operation = Operation,
            DateTime = DateTime.Now,
            Key = JsonConvert.SerializeObject(keys),
            Old = olds.Count == 0 ? null : JsonConvert.SerializeObject(olds),
            New = news.Count == 0 ? null : JsonConvert.SerializeObject(news)
        };
        return audit;
    }
}

这个类主要是用于存储表名称,被操作数据的主键Id,被操作前的数据和被操作后的数据。在上面的代码中我们看到我们将被操作数据的主键Id、被操作前的数据和被操作后的数据的变量都定义成了字典类型,这是因为我们的程序中有可能出现批量操作的问题。在将上述信息转换成 Audit 时提示我们对被操作前的数据和被操作后的数据进行了一个长度判断,这是因为当我们新增数据的时候是没有旧数据的,当我们对数据没有进行任何更改就提交数据的时候是不存在新数据的。


二、重写 SaveChanges

这个例子重写的是 SaveChanges ,对于 SaveChangesAsync 同样适用。我们需要在 OnBeforSaveBehavior 方法中创建 AuditDb 列表。

public class EFContext : DbContext
{
    public override int SaveChanges(bool acceptAllChangesOnSuccess)
    {
        List<AuditDb> auditDbs = OnBeforeSaveBehavior();
        var result = base.SaveChanges(acceptAllChangesOnSuccess);
        return result;
    }
    List<AuditDb> OnBeforeSaveBehavior()
    {
        ChangeTracker.DetectChanges();
        List<AuditDb> auditDbs = new List<AuditDb>();
        foreach (EntityEntry entity in ChangeTracker.Entries())
        {
            if (entity.Entity is Audit || entity.State == EntityState.Detached || entity.State == EntityState.Unchanged)
            {
                continue;
            }
            AuditDb auditDb = new AuditDb(entity)
            {
                TableName = entity.Metadata.Name
            };
            auditDbs.Add(auditDb);
            foreach (var property in entity.Properties)
            {
                if (property.IsTemporary)
                {
                    auditDb.propertyEntries.Add(property);
                    continue;
                }
                var propertName = property.Metadata.Name;
                if (property.Metadata.IsPrimaryKey())
                {
                    auditDb.keys[propertName] = property.CurrentValue;
                    continue;
                }
                switch (entity.State)
                {
                    case EntityState.Deleted:
                        auditDb.Operation = Operation.Delete;
                        auditDb.olds[propertName] = property.OriginalValue;
                        break;
                    case EntityState.Modified:
                        if (property.IsModified)
                        {
                            auditDb.Operation = Operation.Modified;
                            auditDb.olds[propertName] = property.OriginalValue;
                            auditDb.news[propertName] = property.CurrentValue;
                        }
                        break;
                    case EntityState.Added:
                        auditDb.Operation = Operation.Add;
                        auditDb.news[propertName] = property.CurrentValue;
                        break;
                }
            }
        }
        List<Audit> audits = new List<Audit>();
        foreach (var item in auditDbs.Where(p => !p.HasPropertyEntries))
        {
            audits.Add(item.ToAudit());
        }
        return auditDbs.Where(p => p.HasPropertyEntries).ToList();
    }
}

到目前为止,捕获审计数据的所有代码已经完成,这里需要注意的一点是部分实体属性是由数据库生成的,例如当前日期、Id等,这些值需要等待 SaveChanges 方法执行完毕后方可获得,也就是说在这种情况下保存审计数据必须在 SaveChanges 方法之后。


三、总结

通过前面的代码示例和讲解,我们就可以解答前面提出的两个问题了,除了部分数据是由数据库自动生成的情况下,大部分情况下在调用SaveChanges方法之前,我们通过上下文中的ChangeTracker属性来获取旧值和新值并保存。上述代码理解起来比较简单,适用于大部分情况,可以直接放在项目中使用。


目录
相关文章
|
8月前
|
SQL 数据库 开发工具
实时计算 Flink版产品使用合集之数据库中有新增索引,同步任务没有报错,索引的变动是否有影响
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
8月前
|
关系型数据库 MySQL API
实时计算 Flink版产品使用合集之可以通过mysql-cdc动态监听MySQL数据库的数据变动吗
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStream API、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
205 0
|
8月前
|
监控 Oracle 关系型数据库
Flink CDC(Change Data Capture)是一种用于捕获数据库变更的技术
Flink CDC(Change Data Capture)是一种用于捕获数据库变更的技术
99 8
|
2月前
|
消息中间件 资源调度 关系型数据库
如何在Flink on YARN环境中配置Debezium CDC 3.0,以实现实时捕获数据库变更事件并将其传输到Flink进行处理
本文介绍了如何在Flink on YARN环境中配置Debezium CDC 3.0,以实现实时捕获数据库变更事件并将其传输到Flink进行处理。主要内容包括安装Debezium、配置Kafka Connect、创建Flink任务以及启动任务的具体步骤,为构建实时数据管道提供了详细指导。
149 9
|
6月前
|
开发框架 前端开发 JavaScript
基于SqlSugar的数据库访问处理的封装,支持.net FrameWork和.net core的项目调用
基于SqlSugar的数据库访问处理的封装,支持.net FrameWork和.net core的项目调用
|
5月前
|
数据库 C# 开发者
WPF开发者必读:揭秘ADO.NET与Entity Framework数据库交互秘籍,轻松实现企业级应用!
【8月更文挑战第31天】在现代软件开发中,WPF 与数据库的交互对于构建企业级应用至关重要。本文介绍了如何利用 ADO.NET 和 Entity Framework 在 WPF 应用中访问和操作数据库。ADO.NET 是 .NET Framework 中用于访问各类数据库(如 SQL Server、MySQL 等)的类库;Entity Framework 则是一种 ORM 框架,支持面向对象的数据操作。文章通过示例展示了如何在 WPF 应用中集成这两种技术,提高开发效率。
85 0
|
5月前
|
Java 前端开发 Spring
技术融合新潮流!Vaadin携手Spring Boot、React、Angular,引领Web开发变革,你准备好了吗?
【8月更文挑战第31天】本文探讨了Vaadin与Spring Boot、React及Angular等主流技术栈的最佳融合实践。Vaadin作为现代Java Web框架,与其他技术栈结合能更好地满足复杂应用需求。文中通过示例代码展示了如何在Spring Boot项目中集成Vaadin,以及如何在Vaadin项目中使用React和Angular组件,充分发挥各技术栈的优势,提升开发效率和用户体验。开发者可根据具体需求选择合适的技术组合。
113 0
|
5月前
|
SQL 关系型数据库 数据库连接
Entity Framework Core 入门教程来袭!快速上手强大的 ORM 工具,开启高效数据库开发之旅!
【8月更文挑战第31天】Entity Framework Core(EF Core)是一个轻量且可扩展的对象关系映射(ORM)框架,允许开发者使用 .NET 语言操作数据库而无需直接编写 SQL 语句。本教程涵盖 EF Core 的安装、数据库上下文创建、数据库连接配置及常见数据库操作(如添加、查询、更新和删除),并介绍如何利用数据库迁移功能安全地更改数据库结构。通过本教程,你可以快速掌握 EF Core 的基本用法,提高开发效率。
314 0
|
8月前
|
SQL Oracle 关系型数据库
实时计算 Flink版产品使用合集之从Oracle数据库同步数据时,checkpoint恢复后无法捕获到任务暂停期间的变更日志,如何处理
实时计算Flink版作为一种强大的流处理和批处理统一的计算框架,广泛应用于各种需要实时数据处理和分析的场景。实时计算Flink版通常结合SQL接口、DataStreamAPI、以及与上下游数据源和存储系统的丰富连接器,提供了一套全面的解决方案,以应对各种实时计算需求。其低延迟、高吞吐、容错性强的特点,使其成为众多企业和组织实时数据处理首选的技术平台。以下是实时计算Flink版的一些典型使用合集。
|
5月前
|
SQL 关系型数据库 数据库
EF Core连接PostgreSQL数据库
EF Core连接PostgreSQL数据库
71 0