EF Core Triggers

Библиотека для написания триггеров с использованием плавного синтаксиса на C# и EF Core.

Теги

TriggersSQL.NETC#

Ключевые особенности

ЯзыкC#
Фреймворк.NET Standard 2.1 / .NET 6 / .NET 8 / .NET 9 / .NET 10
Тип проектаБиблиотека
СтатусГотова к использованию
ЛицензияMIT
Nugetпоследняя версия
Загрузкипоследняя версия
GithubLaraue.EfCoreTriggers

Что такое триггеры?

Триггеры базы данных — это специальные процедуры, которые автоматически выполняются в ответ на определенные события базы данных, такие как вставка, обновление или удаление записей. Они используются для обеспечения соблюдения бизнес-правил, поддержания целостности данных, логирования и так далее.

Когда использование триггеров оправдано

В современной разработке приложений разработчики часто стараются избегать триггеров базы данных, предпочитая реализовывать логику на уровне приложения. У этого подхода есть преимущества:

  • Улучшенная масштабируемость приложения: База данных часто является узким местом в приложениях. Дополнительная логика, выполняемая внутри базы данных, потребляет ресурсы. Масштабирование базы данных обычно вертикальное (увеличение мощности одного сервера), в то время как хосты приложений и инфраструктура между ними часто масштабируются более эффективно.
  • Централизованная логика приложения: Хранение логики в одном месте упрощает ее понимание, сопровождение и отладку.

Однако триггеры по-прежнему имеют преимущества, которые оправдывают их использование в определенных ситуациях:

  • Упрощение модификации устаревших систем: Добавление репликации в устаревшие системы может потребовать значительных изменений кода. Когда кодовая база плохо структурирована или не имеет всеобъемлющих тестов, такие изменения могут легко привести к ошибкам и непредвиденным последствиям.
  • Обеспечение целостности данных: В некоторых средах пользователи могут иметь прямой доступ к базе данных. Это может нарушить целостность данных, если изменения не отслеживаются на уровне приложения. Триггеры могут обеспечить дополнительный уровень защиты.
  • Экономически эффективная инфраструктура: Небольшие организации часто стремятся минимизировать затраты на инфраструктуру. Для более простых случаев использования может быть более экономично использовать существующую базу данных и построить систему событий внутри нее, вместо управления выделенной системой обмена сообщениями, такой как Kafka или RabbitMQ.

Проблема с триггерами в EF Core

При работе с триггерами и EF Core рекомендованным образом, необходимо использовать SQL для создания триггера в миграциях:

migrationBuilder.Sql("CREATE TRIGGER ...")

Хотя это работает, управление триггерами становится сложным:

  • Отсутствие видимости: Сложно определить, существует ли триггер, не просматривая файлы миграций или не проверяя базу данных напрямую.
  • Сложности сопровождения: Когда модель сущности, связанная с триггером, изменяется, триггер также должен быть обновлен. Невыполнение этого требования приведет к исключениям во время выполнения.

Видение библиотеки

Эта библиотека направлена на решение этих проблем. Позволяя определять триггеры с использованием плавного синтаксиса, аналогично тому, как определяются индексы и внешние ключи, библиотека будет:

  • Обеспечивать проверку во время компиляции для обнаружения ошибок при изменении моделей.
  • Автоматически пересоздавать триггеры при необходимости.
  • Четко связывать триггеры с соответствующими сущностями в определении модели.

Как использовать

Определите необходимый триггер в построителе модели:

modelBuilder.Entity<Transaction>()
    .AfterUpdate(trigger => trigger
        .Action(action => action
            .Condition(tableRefs => tableRefs.Old.IsVeryfied && tableRefs.New.IsVeryfied) // Триггер выполнится только при выполнении условия
            .Update<UserBalance>(
                (tableRefs, userBalances) => userBalances.UserId == tableRefs.Old.UserId, // Условие, какие записи обновятся
                (tableRefs, oldBalance) => new UserBalance { Balance = oldBalance.Balance + tableRefs.New.Value - tableRefs.Old.Value }))); // Какими значениями обновлять записи, попавшие под условия

Теперь добавьте функциональность триггеров и соответствующий провайдер базы данных в DbContextOptionsBuilder

var options = new DbContextOptionsBuilder<TestDbContext>()
    .UseNpgsql("User ID=test;Password=test;Host=localhost;Port=5432;Database=test;")
    .UsePostgreSqlTriggers() // Select the package for your database, Postgres provider in the example
    .Options;

var dbContext = new TestDbContext(options);

Сгенерируйте миграцию. SQL для создания триггера будет добавлен в секции Up и Down созданной миграции.

Сложности

Конкретные проблемы и решения будут описаны в отдельных статьях:

  • Разбор деревьев выражений
  • Поддержка различных провайдеров баз данных

Хронология

  • Ноябрь 2020: Первоначальная версия с поддержкой Postgres.
  • Декабрь 2020: Добавлена поддержка MS SQL, SQLite, MySQL.
  • ~ Декабрь 2022: Стабильная версия с обширной поддержкой встроенных математических и строковых функций.
  • Декабрь 2025: Базовая функциональность триггеров вынесена в отдельные пакеты. Это позволяет генерировать триггеры без EF Core и упрощает обновление версий.

Дальнейшие улучшения

  • Рефакторинг системы плагинов для перевода методов, чтобы привести ее в соответствие с подходом, используемым в Linq2DB: помечать методы атрибутами для определения того, как они должны транслироваться в SQL.