Темпоральные таблицы в EF Core. Часть 4
1. Что это?
2. Настройка
3. Запросы
Безопасная миграция в производственной среде
Изменение существующей таблицы на темпоральную — более деликатный процесс, чем создание её с нуля. Безопаснее всего создать ручную миграцию с чистым SQL:
public partial class TemporalOnOrders : Migration
{
protected override void Up(MigrationBuilder mb)
{
// Добавляем столбцы периода
mb.Sql(@"
ALTER TABLE [Orders] ADD
[ValidFrom] DATETIME2 GENERATED ALWAYS AS ROW START HIDDEN
CONSTRAINT DF_Orders_ValidFrom DEFAULT '2000-01-01 00:00:00.0000000',
[ValidTo] DATETIME2 GENERATED ALWAYS AS ROW END HIDDEN
CONSTRAINT DF_Orders_ValidTo DEFAULT '9999-12-31 23:59:59.9999999',
PERIOD FOR SYSTEM_TIME ([ValidFrom], [ValidTo]);
");
// Включаем версионирование
mb.Sql(@"
ALTER TABLE [Orders]
SET (SYSTEM_VERSIONING = ON (
HISTORY_TABLE = [audit].[OrdersHistory],
DATA_CONSISTENCY_CHECK = ON));
");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
mb.Sql(@"
ALTER TABLE [Orders] SET (SYSTEM_VERSIONING = OFF);
ALTER TABLE [Orders] DROP PERIOD FOR SYSTEM_TIME;
ALTER TABLE [Orders] DROP COLUMN [ValidFrom];
ALTER TABLE [Orders] DROP COLUMN [ValidTo];
DROP TABLE IF EXISTS [audit].[OrdersHistory];
");
}
}
Замечание: В производственной среде вы, наверное, не захотите удалять историю, а просто отключите версионирование.
Производительность записи
Темпоральные таблицы не бесплатны. Каждое обновление или удаление требует от SQL Server записи дополнительной строки в таблицу истории. Для таблиц с высокой нагрузкой на запись это может быть значительным. В типичных сценариях OLTP накладные расходы - 5–15%. Для сценариев с высокой нагрузкой следует оценить, оправдывает ли необходимая детализация истории эти затраты.
Рост таблиц истории
Таблицы истории со временем могут значительно увеличиваться в размерах. Можно архивировать старые данные:
public async Task ArchiveOldHistoryAsync()
{
var cutoff = DateTime.UtcNow.AddYears(-2);
await _db.Database.ExecuteSqlRawAsync(@"
-- Перемещаем в архив
INSERT INTO [audit].[OrdersHistoryArchive]
SELECT * FROM [audit].[OrdersHistory]
WHERE [ValidTo] < {0};
-- Удаляем историю (надо отключить версионирование)
ALTER TABLE [Orders] SET (SYSTEM_VERSIONING = OFF);
DELETE FROM [audit].[OrdersHistory]
WHERE [ValidTo] < {0};
ALTER TABLE [Orders] SET (SYSTEM_VERSIONING = ON (
HISTORY_TABLE = [audit].[OrdersHistory]));
", cutoff);
}
Производительность чтения
Функции TemporalAll() и TemporalBetween() сканируют таблицу истории. Для панелей аудита с большими таблицами истории всегда используйте агрессивную фильтрацию и правильные индексы.
EF Core создаёт кластерный индекс по столбцам периода в таблице истории. Для запросов к конкретным сущностям на определённый момент времени добавьте некластерный индекс:
CREATE NONCLUSTERED INDEX IX_OrdersHistory_IdValidFrom
ON [audit].[OrdersHistory] ([Id], [ValidFrom] DESC);
Окончание следует…
Источник: https://thecodeman.net/posts/temporal-tables-efcore-auditing-history