Темпоральные таблицы в EF Core. Часть 5
1. Что это?
2. Настройка
3. Запросы
4. Добавление версионирования и производительность
Распространенные ошибки и способы их избежать
Ошибка 1: Скафолдинг темпоральных таблиц при обратном проектировании
Если вы используете шаблон
dotnet ef dbcontext для существующей темпоральной БД, EF Core может некорректно определить конфигурацию таблиц. Всегда проверяйте созданный шаблон DbContext и добавляйте конфигурацию IsTemporal() вручную, если это необходимо.Ошибка 2: Мягкое удаление и темпоральные таблицы
Если ваша сущность использует паттерн мягкого удаления (флаг IsDeleted), темпоральные таблицы будут отслеживать изменения этих флагов. Т.е. «удалённые» строки по-прежнему будут находиться в основной таблице, просто с
IsDeleted = true — и каждый раз, когда кто-то запрашивает историю, ему нужно будет это учитывать. Подумайте, нужны ли вам оба механизма, или достаточно жёсткого удаления и темпоральных таблиц для соответствия вашим требованиям.Ошибка 3: Массовые операции в обход EF Core
Это позволяет обойти отслеживание изменений EF Core, но при этом работает с темпоральными таблицами, поскольку они задаются на уровне SQL Server:
await _db.Database.ExecuteSqlRawAsync(
"UPDATE Orders SET Status = 'Archived' WHERE CreatedAt < {0}",
DateTime.UtcNow.AddYears(-1));
Если вы используете стороннюю библиотеку для пакетной обработки запросов, которая напрямую вызывает SqlBulkCopy, имейте в виду, что она обходит триггеры, но НЕ темпоральные таблицы — они обрабатываются на уровне движка.
Ошибка 4: Миграции EF Core на темпоральные таблицы требуют осторожности
При добавлении нового столбца в темпоральную таблицу EF Core также должен добавить его в таблицу истории. EF Core обрабатывает это автоматически в миграциях, но если вы когда-либо вручную изменяли таблицу истории, миграция завершится неудачей. Никогда не изменяйте схему таблицы истории напрямую.
Ошибка 5: Забывание о UTC
Временные метки в таблицах всегда в формате UTC. Если ваше приложение использует местное время, преобразования могут вызвать трудно отслеживаемые ошибки в запросах к историческим данным:
// ❌ Используется местное время
var asOf = DateTime.Now.AddDays(-1);
// ✅ Всегда используйте UTC
var asOf = DateTime.UtcNow.AddDays(-1);
var snapshot = await _db.Orders
.TemporalAsOf(asOf)
.FirstOrDefaultAsync(o => o.Id == orderId);
Когда НЕ следует использовать темпоральные таблицы
- Таблицы с высокой частотой записи (например, телеметрия в реальном времени, состояние сессии)
Накладные расходы на запись и рост истории будут проблемой. Вместо этого используйте выделенную БД временных рядов или хранилище событий.
- Конфиденциальность персональных данных/GDPR
Темпоральные таблицы затрудняют удаление данных — таблица истории сохраняет старые версии. Если вам необходимо поддерживать запросы «права на забвение», вам потребуется реализовать процесс удаления вручную, который отключает версионирование, очищает историю и снова включает его.
- Таблицы с BLOB-объектами или большими столбцами NVARCHAR(MAX)
Каждое обновление копирует всю строку в историю, включая большие поля. Это может привести к чрезвычайно быстрому росту таблицы истории.
- Требования к согласованности между базами данных
Временные метки являются индивидуальными для каждой БД. Если бизнес-транзакция охватывает несколько БД, временные записи будут иметь немного разные метки времени, что делает восстановление данных на определённый момент времени между базами данных ненадёжным.
Источник: https://thecodeman.net/posts/temporal-tables-efcore-auditing-history