而不是SQL Server中的触发器丢失SCOPE_IDENTITY?

kas*_*ter 22 t-sql sql-server sql-server-2008 linq-to-sql

我有一个表,我创建了一个INSTEAD OF触发器来强制执行一些业务规则.

问题是,当我将数据插入此表时,SCOPE_IDENTITY()返回一个NULL值,而不是实际插入的标识.

插入+范围代码

INSERT INTO [dbo].[Payment]([DateFrom], [DateTo], [CustomerId], [AdminId])
VALUES ('2009-01-20', '2009-01-31', 6, 1)

SELECT SCOPE_IDENTITY()
Run Code Online (Sandbox Code Playgroud)

触发:

CREATE TRIGGER [dbo].[TR_Payments_Insert]
   ON  [dbo].[Payment]
   INSTEAD OF INSERT
AS 
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
    -- interfering with SELECT statements.
    SET NOCOUNT ON;

    IF NOT EXISTS(SELECT 1 FROM dbo.Payment p
              INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
              WHERE (i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo)
              ) AND NOT EXISTS (SELECT 1 FROM Inserted p
              INNER JOIN Inserted i ON p.CustomerId = i.CustomerId
              WHERE  (i.DateFrom <> p.DateFrom AND i.DateTo <> p.DateTo) AND 
              ((i.DateFrom >= p.DateFrom AND i.DateFrom <= p.DateTo) OR (i.DateTo >= p.DateFrom AND i.DateTo <= p.DateTo))
              )

    BEGIN
        INSERT INTO dbo.Payment (DateFrom, DateTo, CustomerId, AdminId)
        SELECT DateFrom, DateTo, CustomerId, AdminId
        FROM Inserted
    END
    ELSE
    BEGIN
            ROLLBACK TRANSACTION
    END


END
Run Code Online (Sandbox Code Playgroud)

代码在创建此触发器之前有效.我在C#中使用LINQ to SQL.我没有看到改变的方式SCOPE_IDENTITY@@IDENTITY.我该如何工作?

Guf*_*ffa 17

@@identity而不是scope_identity().

while scope_identity()返回当前范围@@identity中最后创建的id ,返回当前会话中最后创建的id.

scope_identity()通常建议在该@@identity字段上使用该函数,因为您通常不希望触发器干扰id,但在这种情况下,您可以这样做.

  • 在我的帖子中添加了更多内容后,您可以看到我在我的 .Net 应用程序中使用了 LINQ to SQL,所以就我所见,我在这里真的没有太多选择,除了可能使用 sproc 插入数据。 (3认同)
  • @Emtucifor:在大多数情况下,你想要scope_identity()返回什么,但不是在这种情况下.这次是@@ identity,这是正确的选择.两者都存在的原因是有时你真的想要不寻常的结果. (3认同)
  • Guffa,你只需要@@ identity,因为scope_identity()没有执行我们所有人期望它的函数:你插入一个有一个标识列的表,然后想要那个标识列值.Scope_Identity()应该保护您不需要搜索然后仔细检查表上的任何触发器,以确保它们没有插入带有标识列的另一个表.但是,您使用@@ identity的无限制建议将打破表上的另一个后触发器,或者触发第二个表的触发器. (3认同)
  • 我意识到你不能假设当插入一个带有INSTEAD OF触发器的表时,行甚至会被添加到表中.它们可以放在另一个表或许多其他表中,或者根本不插入任何地方.但是,调用脚本不知道这一点,不应该知道这一点.INSTEAD OF触发器的要点是使基础数据操作的混乱内容对正在执行插入的客户端透明.在我看来,应该有一些方法在INSTEAD OF触发器中显式设置scope_identity. (2认同)

Aar*_*ton 12

由于您使用的是SQL 2008,我强烈建议您使用OUTPUT子句而不是其中一个自定义标识函数.SCOPE_IDENTITY目前在并行查询方面存在一些问题,导致我完全反对它.@@ Identity没有,但它仍然不像OUTPUT那样明确,灵活.Plus OUTPUT可处理多行插入.看看BOL文章,其中有一些很好的例子.

  • 此外,如果表上存在INSTEAD OF INSERT触发器,则OUTPUT子句将始终为标识列返回0. (4认同)

Eri*_*ikE 8

我对使用@@ identity有严重的保留意见,因为它可能会返回错误的答案.

但是有一种解决方法可以强制@@ identity拥有scope_identity()值.

为了完整起见,首先我将列出我在网上看到的这个问题的其他几个解决方法:

  1. 使触发器返回行集.然后,在执行插入的包装器SP中,执行INSERT Table1 EXEC sp_ExecuteSQL ...另一个表.然后scope_identity()将起作用.这很麻烦,因为它需要动态SQL,这很痛苦.此外,请注意,动态SQL在调用SP的用户的权限下运行,而不是SP所有者的权限.如果原始客户端可以插入到表中,他仍然应该具有该权限,只要知道如果拒绝直接插入表的权限就可能会遇到问题.

  2. 如果存在另一个候选键,则使用这些键获取插入行的标识.例如,如果Name上有唯一索引,则可以插入,然后使用Name从刚刚插入的表中选择(多行多行)ID.虽然如果另一个会话删除您刚刚插入的行,这可能会出现并发问题,但如果有人在应用程序使用它之前删除了您的行,则不会比原始情况更糟糕.

现在,即使您的SP或其他触发器在主插入后插入到标识承载表中,可以确定如何确保触发器安全,以便@@ Identity可以返回正确的值.

另外,请在您的代码中添加关于您正在做什么的评论以及为什么触发器的未来访问者不会破坏事物或浪费时间来解决问题.

CREATE TRIGGER TR_MyTable_I ON MyTable INSTEAD OF INSERT
AS
SET NOCOUNT ON

DECLARE @MyTableID int
INSERT MyTable (Name, SystemUser)
SELECT I.Name, System_User
FROM Inserted

SET @MyTableID = Scope_Identity()

INSERT AuditTable (SystemUser, Notes)
SELECT SystemUser, 'Added Name ' + I.Name
FROM Inserted

-- The following statement MUST be last in this trigger. It resets @@Identity
-- to be the same as the earlier Scope_Identity() value.
SELECT MyTableID INTO #Trash FROM MyTable WHERE MyTableID = @MyTableID
Run Code Online (Sandbox Code Playgroud)

通常,审计表的额外插入会破坏所有内容,因为它具有标识列,因此@@ Identity将返回该值,而不是从插入到MyTable的值.但是,最终选择会根据我们之前保存的Scope_Identity()创建一个新的@@ Identity值,该值是正确的.这也证明它可以防止MyTable表上任何可能的额外AFTER触发器.

更新:

我只是注意到这里不需要INSTEAD OF触发器.这可以满足您的一切需求:

CREATE TRIGGER dbo.TR_Payments_Insert ON dbo.Payment FOR INSERT
AS 
SET NOCOUNT ON;
IF EXISTS (
   SELECT *
   FROM
      Inserted I
      INNER JOIN dbo.Payment P ON I.CustomerID = P.CustomerID
   WHERE
      I.DateFrom < P.DateTo
      AND P.DateFrom < I.DateTo
) ROLLBACK TRAN;
Run Code Online (Sandbox Code Playgroud)

这当然允许scope_identity()继续工作.唯一的缺点是标识表上的回滚插入确实使用了所使用的标识值(标识值仍然增加了插入尝试中的行数).

我一直盯着这几分钟并且现在没有绝对的确定性,但我认为这保留了包容性开始时间和独家结束时间的含义.如果结束时间是包容性的(这对我来说很奇怪),则比较需要使用<=而不是<.