使用Entity Framework Code First Migrations添加数据库触发器

26

我使用Entity Framework Migrations来控制我的数据库模型,它很好用,到目前为止我都可以处理所有的问题。但现在我需要添加一个数据库触发器,并且希望使用EF Migrations来完成,而不是为此单独编写SQL脚本(这对客户来说会很困惑,特别是在我们说服他们我们可以用EF Migrations解决所有问题之后)。我的触发器很简单,长这样:

CREATE OR REPLACE TRIGGER [name] BEFORE UPDATE ON myTable ...

是否有一个命令可以将触发器添加到EF迁移中?


看看我想出来的解决方案:EntityFramework.Triggers。它也在NuGet上。 - Nick Strupat
看起来很有前途。你知道它是否支持多个数据库系统(如Oracle、SQL Server等)吗? - StefanG
我只在SQL Server上进行了测试,但它是基于Entity Framework以提供程序无关的方式构建的。它依赖于DbContext - Nick Strupat
@NickStrupat 为了澄清,这实际上并没有创建数据库触发器,而是使用事件在EF中模拟类似于它们的东西,对吗? - Trevortni
@Trevortni,那是正确的。 - Nick Strupat
2个回答

45

您可以在迁移的Up方法中添加一个Sql("SQL COMMAND HERE")方法调用。不要忘记将删除语句添加到Down方法中。如果需要,可以仅通过运行Add-Migration而不对模型进行任何更改来创建空迁移。

public partial class Example : DbMigration
{
    public override void Up()
    {
        Sql("CREATE OR REPLACE TRIGGER [name] BEFORE UPDATE ON myTable ...");
    }

    public override void Down()
    {
        Sql("DROP TRIGGER [name]");
    }
}

3
您可以在 UP() 中添加 IF NOT EXISTS(select * from sys.triggers where name = 'name') ,使其具有幂等性。 - eoghank
17
@eoghank,我不确定我同意你的观点。迁移的理念是它们应该在所有方向上可重复,并且因此可以防止发生这种情况。理论上,如果我们需要存在性检查,那么我们就没有正确使用迁移 - 就像我们对表格不进行存在性检查一样,我们也不应该对触发器进行检查吗? - Andy Brown
8
针对 EF Core,使用 migrationBuilder.Sql("blah blah...");。 - wye
如果编写这样的迁移(无论是为 ef、ef core 还是 fluent migrator),我将无法使用“Down”部分。例如:第一次迁移创建触发器。第二次迁移更新触发器。我迁移数据库。很好...第二次迁移没有失败,因为它替换了第一次迁移中的触发器,但现在...我想降级一步。哎呀!技术上说,我处于第一次迁移,但是由于第二次迁移的 Down 步骤删除了触发器,所以现在没有触发器。 - korulis
2
@korulis,第二次迁移是更新,因此它的“Down”应该更新为原始状态(而不是删除)。 - Aleksey Cherenkov
对于MSSQL,应该使用"CREATE OR ALTER...." - undefined

7

最近我遇到了类似的问题,没有找到不手动编写SQL的解决方案。因此,我编写了一个小的软件包,可以使用Ef Core实体生成器编写如下迁移:

modelBuilder.Entity<Transaction>()
    .AfterInsert(trigger => trigger
        .Action(triggerAction => triggerAction
            .Upsert(transaction => new { transaction.UserId },
                insertedTransaction => new UserBalance { UserId = transaction.UserId, Balance = insertedTransaction.Sum },
                (insertedTransaction, oldBalance) => new UserBalance { Balance = oldBalance.Balance + insertedTransaction.Sum })));

这段代码将被翻译成SQL并应用于迁移,看起来会是这样的:
public partial class AddTriggers : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql("CREATE FUNCTION LC_TRIGGER_AFTER_INSERT_TRANSACTION() RETURNS trigger as $LC_TRIGGER_AFTER_INSERT_TRANSACTION$ BEGIN INSERT INTO user_balances (user_id, balance) VALUES (NEW.user_id, NEW.sum) ON CONFLICT (user_id) DO UPDATE SET balance = user_balances.balance + NEW.sum; RETURN NEW;END;$LC_TRIGGER_AFTER_INSERT_TRANSACTION$ LANGUAGE plpgsql;CREATE TRIGGER LC_TRIGGER_AFTER_INSERT_TRANSACTION AFTER INSERT ON transactions FOR EACH ROW EXECUTE PROCEDURE LC_TRIGGER_AFTER_INSERT_TRANSACTION();");
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.Sql("DROP TRIGGER LC_TRIGGER_AFTER_INSERT_TRANSACTION ON transactions;DROP FUNCTION LC_TRIGGER_AFTER_INSERT_TRANSACTION();");
    }
}

也许对某些人会有帮助。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接