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()
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
代码在创建此触发器之前有效.我在C#中使用LINQ to SQL.我没有看到改变的方式SCOPE_IDENTITY来@@IDENTITY.我该如何工作?
Guf*_*ffa 17
用@@identity而不是scope_identity().
while scope_identity()返回当前范围@@identity中最后创建的id ,返回当前会话中最后创建的id.
scope_identity()通常建议在该@@identity字段上使用该函数,因为您通常不希望触发器干扰id,但在这种情况下,您可以这样做.
我对使用@@ identity有严重的保留意见,因为它可能会返回错误的答案.
但是有一种解决方法可以强制@@ identity拥有scope_identity()值.
为了完整起见,首先我将列出我在网上看到的这个问题的其他几个解决方法:
使触发器返回行集.然后,在执行插入的包装器SP中,执行INSERT Table1 EXEC sp_ExecuteSQL ...另一个表.然后scope_identity()将起作用.这很麻烦,因为它需要动态SQL,这很痛苦.此外,请注意,动态SQL在调用SP的用户的权限下运行,而不是SP所有者的权限.如果原始客户端可以插入到表中,他仍然应该具有该权限,只要知道如果拒绝直接插入表的权限就可能会遇到问题.
如果存在另一个候选键,则使用这些键获取插入行的标识.例如,如果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
通常,审计表的额外插入会破坏所有内容,因为它具有标识列,因此@@ 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;
这当然允许scope_identity()继续工作.唯一的缺点是标识表上的回滚插入确实使用了所使用的标识值(标识值仍然增加了插入尝试中的行数).
我一直盯着这几分钟并且现在没有绝对的确定性,但我认为这保留了包容性开始时间和独家结束时间的含义.如果结束时间是包容性的(这对我来说很奇怪),则比较需要使用<=而不是<.
| 归档时间: | 
 | 
| 查看次数: | 20256 次 | 
| 最近记录: |