使用 Entity Framework Core 进行审计跟踪

Posted

技术标签:

【中文标题】使用 Entity Framework Core 进行审计跟踪【英文标题】:Audit trail with Entity Framework Core 【发布时间】:2018-07-25 04:00:30 【问题描述】:

我有一个在 SQL Server 数据库上使用实体框架核心的 ASP.NET 核心 2.0。

我必须跟踪和审核用户对数据所做的所有事情。我的目标是有一个自动机制来记录所有正在发生的事情。

例如,如果我有表 Animals,我想要一个并行表“Audit_animals”,您可以在其中找到有关数据、操作类型(添加、删除、编辑)和创建此操作的用户的所有信息。

我之前已经在Django + mysql中做了这个,但是现在环境不同了。我找到了this,它看起来很有趣,但我想知道是否有更好的方法以及在 EF Core 中执行此操作的最佳方法。

更新

我正在尝试this 并且发生了一些事情,但我遇到了一些问题。

我添加了这个:

    services.AddMvc().AddJsonOptions(options => 
    
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            ); 
    
    public Mydb_Context(DbContextOptions<isMultiPayOnLine_Context> options) : base(options)
    
        Audit.EntityFramework.Configuration.Setup()
            .ForContext<Mydb_Context>(config => config
                .IncludeEntityObjects()
                .AuditEventType("Mydb_Context:Mydb"))
            .UseOptOut()
    
    
    public MyRepository(Mydb_Context context)
    
        _context = context;
        _context.AddAuditCustomField("UserName", "pippo");
    
    
    

我还创建了一个表格来插入审核(仅用于测试此工具),但我得到的唯一内容就是您在图像中看到的内容。包含我创建的数据的 json 文件列表....为什么??

【问题讨论】:

您是否查看过 SQL Server 的变更数据捕获功能? 如果您使用的是 SQL Server 2016,可能还值得查看 Temporal Tables - 请注意,临时表不能替代更改数据捕获 (CDC) 功能。 CDC 使用事务日志来查找更改,并且这些更改通常会保留很短的时间(取决于您的 ETL 时间范围)。临时表存储历史表中的实际更改,并且它们打算在那里停留更长的时间。 @RossBush 好的,但是我这个 SQL Server 的变更数据捕获不能跟踪登录我的 ASP.NET 核心服务的用户,对吧? @CalC 这个解决方案也让我有些怀疑。它如何跟踪 logger 用户? 听起来您需要在应用程序级别而不是数据库进行审计。也许继承你的 dbContext 并覆盖 savechanges 方法。 【参考方案1】:

阅读documentation:

事件输出

要配置输出持久性机制,请参阅Configuration 和Data Providers 部分。

然后,在Configuration 的文档中:

如果您不指定数据提供者,默认的FileDataProvider 将用于将事件作为.json 文件 写入当前工作目录。 (强调我的)

不管长短,请按照文档配置您要使用的数据提供者。

【讨论】:

很好,但它没有指定我必须如何创建这个 MyCustomDataProvider 或者,至少有一个例子,但我只能找到一个使用文件的例子 好吧好吧,我几乎明白了。它现在将 json 数据保存在我的数据库中的一个表中,但是当我想保存我的真实数据时它会崩溃......我使用自动增量的主键,所以我不设置 ID 值。当我调用 SaveChangesAsync 方法时,我得到“无法跟踪 'DynamicIdModel' 类型的实体,因为主键属性 'Id' 为空。”你知道如何避免这种情况吗?? @PieroAlberto 您使用的是哪个数据提供者?随时打开new issue on github @thepirat000 嗨,pirat,可能是我第一次使用它时犯了一些错误。编辑代码我解决了这个问题并且知道像魅力一样工作。感谢您的工具,非常有用;)【参考方案2】:

如果要将审计表 (Audit_Animals) 映射到与审计的 Animals 表相同的 EF 上下文,则可以使用同一 Audit.EntityFramework 库中包含的 EntityFramework Data Provider

查看文档here:

实体框架数据提供者

如果您打算将审核日志存储在 与被审计实体相同的数据库,您可以使用 EntityFrameworkDataProvider。如果您打算存储审计,请使用此选项 具有相似结构的表中的每个实体类型的踪迹。

还有另一个库可以以类似的方式审核 EF 上下文,看看:zzzprojects/EntityFramework-Plus。

无法推荐其中一个,因为它们提供不同的功能(我是 audit.net 库的所有者)。

【讨论】:

【参考方案3】:

更新:

.NET 6 和 Entity Framework Core 6.0 支持开箱即用的 SQL Server 时态表。

有关示例,请参见此答案:

https://***.com/a/70017768/3850405

原文:

如果您使用SQL Server 2016Azure SQL,您可以查看临时表(系统版本化的临时表)。

https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables?view=sql-server-ver15

来自文档:

提供内置支持的数据库功能 有关在任何时间点存储在表中的数据的信息,而不是 而不仅仅是当前时刻正确的数据。 时态是 ANSI SQL 2011 中引入的数据库功能。

目前有一个开箱即用的未解决问题:

https://github.com/dotnet/efcore/issues/4693

目前有可用的第三方选项,但由于它们不是来自 Microsoft,因此当然存在未来版本不支持它们的风险。

https://github.com/Adam-Langley/efcore-temporal-query

https://github.com/findulov/EntityFrameworkCore.TemporalTables

我是这样解决的:

如果您使用包含的 Visual Studio 2019 LocalDB (Microsoft SQL Server 2016 (13.1.4001.0 LocalDB),如果您使用 cascading DELETEUPDATE,则需要升级。这是因为该版本不支持具有级联操作的 Temporal tables

此处的完整升级指南:

https://***.com/a/64210519/3850405

首先添加一个新的空迁移。我更喜欢使用包管理器控制台(PMC):

Add-Migration "Temporal tables"

应该是这样的:

public partial class Temporaltables : Migration

    protected override void Up(MigrationBuilder migrationBuilder)
    

    

    protected override void Down(MigrationBuilder migrationBuilder)
    

    

然后像这样编辑迁移:

public partial class Temporaltables : Migration

    List<string> tablesToUpdate = new List<string>
        
           "Images",
           "Languages",
           "Questions",
           "Texts",
           "Medias",
        ;

    protected override void Up(MigrationBuilder migrationBuilder)
    
        migrationBuilder.Sql($"CREATE SCHEMA History");
        foreach (var table in tablesToUpdate)
        
            string alterStatement = $@"ALTER TABLE [table] ADD SysStartTime datetime2(0) GENERATED ALWAYS AS ROW START HIDDEN
     CONSTRAINT DF_table_SysStart DEFAULT GETDATE(), SysEndTime datetime2(0) GENERATED ALWAYS AS ROW END HIDDEN
     CONSTRAINT DF_table_SysEnd DEFAULT CONVERT(datetime2 (0), '9999-12-31 23:59:59'),
     PERIOD FOR SYSTEM_TIME (SysStartTime, SysEndTime)";
            migrationBuilder.Sql(alterStatement);
            alterStatement = $@"ALTER TABLE [table] SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = History.[table]));";
            migrationBuilder.Sql(alterStatement);
        
    

    protected override void Down(MigrationBuilder migrationBuilder)
    
        foreach (var table in tablesToUpdate)
        
            string alterStatement = $@"ALTER TABLE [table] SET (SYSTEM_VERSIONING = OFF);";
            migrationBuilder.Sql(alterStatement);
            alterStatement = $@"ALTER TABLE [table] DROP PERIOD FOR SYSTEM_TIME";
            migrationBuilder.Sql(alterStatement);
            alterStatement = $@"ALTER TABLE [table] DROP DF_table_SysStart, DF_table_SysEnd";
            migrationBuilder.Sql(alterStatement);
            alterStatement = $@"ALTER TABLE [table] DROP COLUMN SysStartTime, COLUMN SysEndTime";
            migrationBuilder.Sql(alterStatement);
            alterStatement = $@"DROP TABLE History.[table]";
            migrationBuilder.Sql(alterStatement);
        
        migrationBuilder.Sql($"DROP SCHEMA History");
    

tablesToUpdate 应该包含您需要历史记录的每个表。

然后运行Update-Database 命令。

原始来源,用方括号等转义表进行了一些修改:

https://intellitect.com/updating-sql-database-use-temporal-tables-entity-framework-migration/

测试CreateUpdateDelete 然后将显示完整的历史记录。

[HttpGet]
public async Task<ActionResult<string>> Test()

    var identifier1 = "OATestar123";

    var identifier2 = "OATestar12345";

    var newQuestion = new Question()
    
        Identifier = identifier1
    ;
    _dbContext.Questions.Add(newQuestion);
    await _dbContext.SaveChangesAsync();

    var question = await _dbContext.Questions.FirstOrDefaultAsync(x => x.Identifier == identifier1);
    question.Identifier = identifier2;
    await _dbContext.SaveChangesAsync();

    question = await _dbContext.Questions.FirstOrDefaultAsync(x => x.Identifier == identifier2);
    _dbContext.Entry(question).State = EntityState.Deleted;
    await _dbContext.SaveChangesAsync();

    return Ok();

测试了几次,但日志如下所示:

这个解决方案有一个巨大的 IMAO 优势,它不是特定于对象关系映射器 (ORM) 的,如果您编写简单的 SQL,您甚至可以获得历史记录。

默认情况下,历史记录表也是只读的,因此审计跟踪损坏的可能性较小。收到错误:Cannot update rows in a temporal history table ''

如果您需要访问数据,您可以使用首选的 ORM 来获取数据或通过 SQL 进行审计。

【讨论】:

以上是关于使用 Entity Framework Core 进行审计跟踪的主要内容,如果未能解决你的问题,请参考以下文章

Entity Framework Core 性能优化

在 Entity Framework Core 中使用 [ComplexType]

在 Entity Framework Core 中使用 SQL 视图

使用 Entity Framework Core 更新相关数据

使用 Entity Framework Core 自动增加部分主键

如何使用 Entity Framework Core 模拟异步存储库