仅当在数据库查询上执行 DELETE 而不是在 EF Core 上执行 DELETE 时,AFTER DELETE 触发器才会触发

问题描述 投票:0回答:1

我有两个具有一对多关系的主从表。我想让触发器在删除发生后触发,无论是作为级联操作(通过其 PK 删除主文件)还是通过特定操作(通过其 PK 删除详细信息)。

这是我的触发器:

CREATE TRIGGER [dbo].[tr_update_stocks]
ON [dbo].[WarehouseComponentLoadRows]
AFTER INSERT, UPDATE, DELETE
AS
BEGIN
    SET NOCOUNT ON

    IF ((SELECT TRIGGER_NESTLEVEL()) > 1) 
       RETURN;

    DECLARE @Id BIGINT
    DECLARE @WarehouseId INT
    DECLARE @ComponentId INT
    DECLARE @ColorId INT
    DECLARE @Quantity DECIMAL (18,6)

    DECLARE deleted_cursor CURSOR FAST_FORWARD FOR
        SELECT 
            Id, WarehouseId, 
            ComponentId, ColorId,
            Quantity
        FROM 
            DELETED

    OPEN deleted_cursor
    FETCH NEXT FROM deleted_cursor into @Id, @WarehouseId, @ComponentId, @ColorId, @Quantity

    WHILE @@FETCH_STATUS = 0
    BEGIN
        UPDATE [dbo].[WarehouseComponents]
        SET [dbo].[WarehouseComponents].[StockQuantity] = [dbo].[WarehouseComponents].[StockQuantity] - @Quantity,
            [dbo].[WarehouseComponents].[AvailableQuantity] = [dbo].[WarehouseComponents].[AvailableQuantity] - @Quantity
        FROM
            [dbo].[WarehouseComponents]
        WHERE 
            [dbo].[WarehouseComponents].[WarehouseId] =  @WarehouseId 
            AND [dbo].[WarehouseComponents].[ComponentId] =  @ComponentId 
            AND [dbo].[WarehouseComponents].[ColorId] =  @ColorId

        FETCH NEXT FROM deleted_cursor INTO @Id, @WarehouseId, @ComponentId, @ColorId, @Quantity
  END

  CLOSE deleted_cursor
  DEALLOCATE deleted_cursor

  DECLARE inserted_cursor CURSOR FAST_FORWARD FOR
      SELECT Id
          , WarehouseId
          , ComponentId
          , ColorId
          , Quantity
      FROM INSERTED

  OPEN inserted_cursor
  FETCH NEXT FROM inserted_cursor into @Id, @WarehouseId, @ComponentId, @ColorId, @Quantity

  WHILE @@FETCH_STATUS = 0
  BEGIN

  UPDATE [dbo].[WarehouseComponents]
  SET
      [dbo].[WarehouseComponents].[StockQuantity] = [dbo].[WarehouseComponents].[StockQuantity] + @Quantity,
      [dbo].[WarehouseComponents].[AvailableQuantity] = [dbo].[WarehouseComponents].[AvailableQuantity] + @Quantity
  FROM
      [dbo].[WarehouseComponents]
  WHERE [dbo].[WarehouseComponents].[WarehouseId] =  @WarehouseId AND [dbo].[WarehouseComponents].[ComponentId] =  @ComponentId AND [dbo].[WarehouseComponents].[ColorId] =  @ColorId

  FETCH NEXT FROM inserted_cursor into @Id, @WarehouseId, @ComponentId, @ColorId, @Quantity
END

CLOSE inserted_cursor
DEALLOCATE inserted_cursor
END
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] 
     ENABLE TRIGGER [tr_update_stocks]
GO

在这里你可以找到我的主表:

CREATE TABLE [dbo].[WarehouseComponentLoads]
(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [SupplierId] [int] NOT NULL,
    [Date] [datetime2](7) NOT NULL,
    [CreatedAt] [datetime2](7) NULL,
    [CreatedBy] [nvarchar](255) NULL,
    [UpdatedAt] [datetime2](7) NULL,
    [UpdatedBy] [nvarchar](255) NULL
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[WarehouseComponentLoads] 
    ADD CONSTRAINT [PK_WarehouseComponentLoads] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
                WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                      SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, 
                      ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponentLoads_SupplierId] 
ON [dbo].[WarehouseComponentLoads] ([SupplierId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

ALTER TABLE [dbo].[WarehouseComponentLoads] WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponentLoads_Subjects_SupplierId] 
        FOREIGN KEY([SupplierId])
        REFERENCES [dbo].[Subjects] ([Id])
GO

ALTER TABLE [dbo].[WarehouseComponentLoads] CHECK CONSTRAINT [FK_WarehouseComponentLoads_Subjects_SupplierId]
GO

这是我的详细信息表:

CREATE TABLE [dbo].[WarehouseComponentLoadRows]
(
    [Id] [bigint] IDENTITY(1,1) NOT NULL,
    [WarehouseComponentLoadId] [int] NOT NULL,
    [WarehouseId] [int] NOT NULL,
    [ComponentId] [int] NOT NULL,
    [ColorId] [int] NOT NULL,
    [Quantity] [decimal](18, 6) NOT NULL
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] 
    ADD CONSTRAINT [PK_WarehouseComponentLoadRows] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
                WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                      SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, 
                      ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponentLoadRows_ColorId] 
ON [dbo].[WarehouseComponentLoadRows] ([ColorId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponentLoadRows_ComponentId] 
ON [dbo].[WarehouseComponentLoadRows] ([ComponentId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponentLoadRows_WarehouseComponentLoadId] 
ON [dbo].[WarehouseComponentLoadRows] ([WarehouseComponentLoadId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponentLoadRows_WarehouseId] 
ON [dbo].[WarehouseComponentLoadRows] ([WarehouseId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows]  WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponentLoadRows_Colors_ColorId] 
        FOREIGN KEY([ColorId])
        REFERENCES [dbo].[Colors] ([Id])
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] CHECK CONSTRAINT [FK_WarehouseComponentLoadRows_Colors_ColorId]
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponentLoadRows_Components_ComponentId] 
        FOREIGN KEY([ComponentId])
        REFERENCES [dbo].[Components] ([Id])
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] CHECK CONSTRAINT [FK_WarehouseComponentLoadRows_Components_ComponentId]
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponentLoadRows_WarehouseComponentLoads_WarehouseComponentLoadId] 
        FOREIGN KEY([WarehouseComponentLoadId])
        REFERENCES [dbo].[WarehouseComponentLoads] ([Id])
            ON DELETE CASCADE
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] CHECK CONSTRAINT [FK_WarehouseComponentLoadRows_WarehouseComponentLoads_WarehouseComponentLoadId]
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponentLoadRows_Warehouses_WarehouseId] 
        FOREIGN KEY([WarehouseId])
        REFERENCES [dbo].[Warehouses] ([Id])
GO

ALTER TABLE [dbo].[WarehouseComponentLoadRows] CHECK CONSTRAINT [FK_WarehouseComponentLoadRows_Warehouses_WarehouseId]
GO

在这里您可以找到要更新的丝袜表:

CREATE TABLE [dbo].[WarehouseComponents]
(
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [WarehouseId] [int] NOT NULL,
    [ComponentId] [int] NOT NULL,
    [ColorId] [int] NOT NULL,
    [StockQuantity] [decimal](18, 6) NOT NULL,
    [AvailableQuantity] [decimal](18, 6) NOT NULL
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[WarehouseComponents] 
    ADD CONSTRAINT [PK_WarehouseComponents] 
        PRIMARY KEY CLUSTERED ([Id] ASC)
                WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
                      SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, 
                      ONLINE = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponents_ColorId] 
ON [dbo].[WarehouseComponents] ([ColorId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF,  
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponents_ComponentId] 
ON [dbo].[WarehouseComponents] ([ComponentId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

CREATE NONCLUSTERED INDEX [IX_WarehouseComponents_WarehouseId] 
ON [dbo].[WarehouseComponents] ([WarehouseId] ASC)
       WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, 
             SORT_IN_TEMPDB = OFF, DROP_EXISTING = OFF, ONLINE = OFF, 
             ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
GO

ALTER TABLE [dbo].[WarehouseComponents] WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponents_Colors_ColorId] 
        FOREIGN KEY([ColorId])
        REFERENCES [dbo].[Colors] ([Id])
GO

ALTER TABLE [dbo].[WarehouseComponents] CHECK CONSTRAINT [FK_WarehouseComponents_Colors_ColorId]
GO

ALTER TABLE [dbo].[WarehouseComponents] WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponents_Components_ComponentId] 
        FOREIGN KEY([ComponentId])
        REFERENCES [dbo].[Components] ([Id])
GO

ALTER TABLE [dbo].[WarehouseComponents] CHECK CONSTRAINT [FK_WarehouseComponents_Components_ComponentId]
GO

ALTER TABLE [dbo].[WarehouseComponents] WITH CHECK 
    ADD CONSTRAINT [FK_WarehouseComponents_Warehouses_WarehouseId] 
        FOREIGN KEY([WarehouseId])
        REFERENCES [dbo].[Warehouses] ([Id])
GO

ALTER TABLE [dbo].[WarehouseComponents] CHECK CONSTRAINT [FK_WarehouseComponents_Warehouses_WarehouseId]
GO

基本上我遇到了这个问题:每当我使用 EF 7 插入或更新整个主从实体时,一切都正常。如果我在详细信息表中插入、更新或删除一行,一切都会顺利。但是,如果我从上下文中通过主实体的 PK 删除主实体并保存更改,则就像触发器不会触发,虽然主从记录已正确删除,但长袜不会更新。这仅发生在 EF 上下文中。如果我尝试通过一个简单的查询从 SQL Server 中按 PK 删除主行,级联行为将被正确应用,并且触发器将按预期触发,从而调整袜子。

请考虑我的上下文的流畅 API 包含特定配置,以使 EF 知道我正在使用触发器,如下所示:

 modelBuilder.Entity<WarehouseComponentLoadRow>()
             .ToTable(tb => tb.HasTrigger("tr_update_stocks"));

同样有趣的是,如果我尝试“手动”删除子行,然后删除同一事务范围内的父行(即,在调用

SaveChangesAsync
之前),详细信息行将与主行一起删除,甚至袜子也会被删除。已按预期更新。

问题仅出现在从上下文中级联删除时。我有什么遗漏的吗?

sql-server triggers sql-delete cascade ef-core-7.0
1个回答
3
投票

EF Core 无法绕过您的触发器。触发逻辑肯定有bug。

你的触发器可能正确,也可能不正确,但它使用了大量的坏东西:

  • 游标,而不是连接更新。
  • 使用三部分的列名称。
  • 缺少表别名以使其更具可读性。
  • 在不传递对象 ID 的情况下检查
    TRIGGER_NESTLEVEL
  • 有人希望
    (WarehouseId, ComponentId, ColorId)
    是两个表中的主键或唯一键,否则您最终可能会更新多行。

你的触发器应该是这样的:

CREATE OR ALTER TRIGGER dbo.tr_update_stocks
ON dbo.WarehouseComponentLoadRows
AFTER INSERT, UPDATE, DELETE
AS

IF @@ROWCOUNT = 0 OR TRIGGER_NESTLEVEL(OBJECT_ID('dbo.tr_update_stocks')) > 1
    RETURN;

SET NOCOUNT ON;


UPDATE wc
  SET
      StockQuantity     += i.DiffQuantity,
      AvailableQuantity += i.DiffQuantity
FROM
    dbo.WarehouseComponents wc
JOIN (
    SELECT
      i.WarehouseId,
      i.ComponentId,
      i.ColorId,
      SUM(i.Quantity) AS DiffQuantity
    FROM (
        SELECT
          i.WarehouseId,
          i.ComponentId,
          i.ColorId,
          i.Quantity
        FROM inserted i

        UNION ALL

        SELECT
          d.WarehouseId,
          d.ComponentId,
          d.ColorId,
          -d.Quantity
        FROM deleted d
    ) i
    GROUP BY
      i.WarehouseId,
      i.ComponentId,
      i.ColorId
) i ON d.WarehouseId = wc.WarehouseId
   AND d.ComponentId = wc.ComponentId
   AND d.ColorId = wc.ColorId;
© www.soinside.com 2019 - 2024. All rights reserved.