Entity Framework 核心的审计跟踪

Audit trail with Entity Framework Core

我有一个 ASP.NET 核心 2.0,在 SQL 服务器数据库上使用 Entity Framework 核心。

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

例如,如果我有 table 动物,我想要一个并行 table "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");
    
    }
    

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

阅读 documentation:

Event Output

To configure the output persistence mechanism please see Configuration and Data Providers sections.

然后,在 Configuration 的文档中:

If you don't specify a Data Provider, a default FileDataProvider will be used to write the events as .json files into the current working directory. (emphasis mine)

总而言之,请按照文档配置您要使用的数据提供程序。

如果您要将审核 table (Audit_Animals) 映射到与审核的 Animals table 相同的 EF 上下文,您可以使用 EntityFramework 数据提供程序 包含在同一个 Audit.EntityFramework 库中。

查看文档 here:

Entity Framework Data Provider

If you plan to store the audit logs in the same database as the audited entities, you can use the EntityFrameworkDataProvider. Use this if you plan to store the audit trails for each entity type in a table with similar structure.

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

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

更新:

.NET 6 和 Entity Framework Core 6.0 支持 SQL 服务器临时 table 开箱即用。

查看此答案以获取示例:

原文:

如果您使用 SQL Server 2016< 或 Azure SQL,您可以查看时间 tables(system-versioned 时间 tables)。 =37=]

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

来自文档:

Database feature that brings built-in support for providing information about data stored in the table at any point in time rather than only the data that is correct at the current moment in time. Temporal is a database feature that was introduced in 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 该版本不支持级联操作。

完整的升级指南在这里:

首先添加一个新的空迁移。我更喜欢使用包管理器控制台 (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 应该包含每个 table 您需要的历史记录。

然后运行Update-Database命令。

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

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.

,你甚至可以获得历史记录

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

如果您需要访问数据,您可以使用您喜欢的 ORM 来获取它或通过 SQL 进行审核。