更新:
.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 DELETE 或 UPDATE,则需要升级。这是因为该版本不支持具有级联操作的 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/
测试Create、Update 和Delete 然后将显示完整的历史记录。
[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 进行审计。