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

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

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

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

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

更新

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

我添加了这个:

  1. services.AddMvc().AddJsonOptions(options => {
    
                options.SerializerSettings.ReferenceLoopHandling = ReferenceLoopHandling.Ignore;
            }); 
    
  2. public Mydb_Context(DbContextOptions<isMultiPayOnLine_Context> options) : base(options)
    {
        Audit.EntityFramework.Configuration.Setup()
            .ForContext<Mydb_Context>(config => config
                .IncludeEntityObjects()
                .AuditEventType("Mydb_Context:Mydb"))
            .UseOptOut()
    }
    
  3. 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 方法。

标签: sql-server asp.net-core entity-framework-core audit audit.net


【解决方案1】:

阅读documentation

事件输出

要配置输出持久性机制,请参阅ConfigurationData 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://stackoverflow.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://stackoverflow.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 进行审计。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-02-07
      • 2011-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多