SQL Server - получить вставленное значение идентификатора записи при использовании представления вместо триггера

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

-- Table
CREATE TABLE tblItem (
    ItemId int identity(1,1) primary key,
    Name varchar(20)
)
go

-- View
CREATE VIEW vwItem 
AS
    SELECT *
    FROM tblItem
    -- RLS Filtering Condition
go

-- Instead Of Insert Trigger
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
    -- RLS Security Checks on inserted Table

    -- Insert Records Into Table
    INSERT INTO tblItem (Name)
    SELECT Name
    FROM inserted;
END
go

Если я хочу вставить запись и получить ее идентификатор, перед реализацией триггера RLS вместо триггера я использовал:

DECLARE @ItemId int;

INSERT INTO tblItem (Name)
VALUES ('MyName');

SELECT @ItemId = SCOPE_IDENTITY();

С триггером SCOPE_IDENTITY() больше не работает — он возвращает NULL. Я видел предложения по использованию предложения OUTPUT для возврата личности, но я не могу заставить его работать так, как мне нужно. Если я помещу предложение OUTPUT во вставку представления, в него ничего не будет введено.

-- Nothing is added to @ItemIds
DECLARE @ItemIds TABLE (ItemId int);

INSERT INTO vwItem (Name)
OUTPUT INSERTED.ItemId INTO @ItemIds
VALUES ('MyName');

Если я помещу предложение OUTPUT в триггер оператора INSERT, триггер вернет таблицу (я могу просмотреть ее из SQL Management Studio). Кажется, я не могу зафиксировать это в вызывающем коде; либо с помощью предложения OUTPUT в этом вызове, либо с помощью SELECT * FROM().

-- Modified Instead Of Insert Trigger w/ Output
CREATE TRIGGER IO_vwItem_Insert ON vwItem
INSTEAD OF INSERT
AS BEGIN
    -- RLS Security Checks on inserted Table

    -- Insert Records Into Table
    INSERT INTO tblItem (Name)
    OUTPUT INSERTED.ItemId
    SELECT Name
    FROM inserted;
END
go

-- Calling Code
INSERT INTO vwItem (Name)
VALUES ('MyName');

Единственное, что я могу придумать, это использовать функцию IDENT_CURRENT(). Поскольку это не работает в текущей области, возникает проблема одновременной вставки пользователей и путаницы. Если вся операция заключена в транзакцию, предотвратит ли это проблему параллелизма?

BEGIN TRANSACTION

DECLARE @ItemId int;

INSERT INTO tblItem (Name)
VALUES ('MyName');

SELECT @ItemId = IDENT_CURRENT('tblItem');

COMMIT TRANSACTION

Есть ли у кого-нибудь предложения о том, как это сделать лучше?

Я знаю людей, которые прочитают это и скажут: «Триггеры — это ЗЛО, не используйте их!» Хотя я ценю ваши убеждения, пожалуйста, не делайте этого «предложения».


person CuppM    schedule 23.10.2009    source источник


Ответы (2)


Вы можете попробовать SET CONTEXT_INFO из триггера, который будет прочитан CONTEXT_INFO() в клиенте.

Мы используем его другим способом для передачи информации в триггер, но он будет работать в обратном порядке.

person gbn    schedule 23.10.2009
comment
См. мой связанный вопрос об использовании CONTEXT_INFO(): stackoverflow.com/questions/1616229/contextinfo -и-конвертировать - person Rob Garrison; 24.10.2009
comment
@Rob: я добавил ответ на это - person gbn; 24.10.2009

Вы в этом случае пробовали @@identity? Вы упомянули как scope_Identity(), так и identity_current(), но не @@identity.

person HLGEM    schedule 23.10.2009
comment
Хорошая мысль. Обычная проблема с областью действия может помочь в этом случае. - person gbn; 23.10.2009
comment
Чем @@IDENTITY лучше, чем IDENT_CURRENT()? Насколько я понимаю, хотя ни один из них не ограничен областью действия вызывающего кода, @@IDENTITY — это последнее значение идентификатора, вставленное независимо от того, где. Итак, если у меня есть триггер аудита в таблице, который вставляет запись в таблицу аудита, @@IDENTITY может вернуть идентификатор этой строки (если я правильно понимаю). Вот почему я решил, что IDENT_CURRENT() лучше, потому что он, по крайней мере, ограничивает область действия конкретной таблицей. - person CuppM; 23.10.2009
comment
@CuppM: @@IDENTITY относится к сеансу, а не к области. IDENT_CURRENT() не является ни тем, ни другим и может быть использована любым сеансом/областью - person gbn; 23.10.2009
comment
@gbn: Ах. Таким образом, с триггером аудита @@IDENTITY вернет идентификатор записи аудита? - person CuppM; 23.10.2009
comment
@CuppM: Теоретически. Я хотел бы сказать, что моя идея CONTEXT_INFO лучше, но не думаю, что смогу... :-( - person gbn; 23.10.2009
comment
Если вы звоните из-за пределов триггера, @@identity действительно вернет изношенную вещь, если у вас также есть триггер аудита, но я думаю, что в этом контексте вы получите правильное значение, поскольку другой триггер еще не запущен, как вы можете только один вместо триггера. Но вам придется проверить, чтобы увидеть. Никогда не пробовал это делать, поэтому я предложил это просто как попытку, не будучи уверенным, что это сработает. Конечно, вы могли бы запретить афтер триггеры на столе и всю их логику вынести в триггер вместо триггера. - person HLGEM; 23.10.2009
comment
Помните, что ничего из этого не работает, если вы вставляете более одной записи за раз. Я считаю, что триггеры (когда они используются) всегда должны быть написаны (и протестированы) для работы с наборами. - person Rob Garrison; 27.10.2009