我有一种情况,我需要根据某些条件(实际上是外表中的标志)写保护表的某些行。考虑这个架构:
CREATE TABLE Batch (
Id INT NOT NULL PRIMARY KEY,
DateCreated DATETIME NOT NULL,
Locked BIT NOT NULL
)
CREATE UNIQUE INDEX U_Batch_Locked ON Batch (Locked) WHERE Locked=0
CREATE TABLE ProtectedTable (
Id INT NOT NULL IDENTITY PRIMARY KEY,
Quantity DECIMAL(10,3) NOT NULL,
Price Money NOT NULL,
BatchId INT NULL)
ALTER TABLE ProtectedTable ADD CONSTRAINT FK_ProtectedTable_Batch FOREIGN KEY (BatchId) REFERENCES Batch(id)
如果行链接到具有Locked = 1的批次,我想阻止更改数量和价格。我还需要阻止删除该行。
注意:U_Batch_Locked确保最多可以随时解锁一个批次。
我尝试过使用触发器(yikes)但由于触发器回滚事务而导致更多问题。此更新通常发生在C#客户端中,该客户端在单个事务中执行多个更新(在多个表上)。无论错误如何,客户端都会继续进行更新,并且如果发生任何错误,则会在事务结束时将其回滚。这样,它可以收集所有/大多数约束违规,并让用户在尝试再次保存更改之前修复它们。但是,由于触发器在不满足约束时回滚事务,后续更新将启动它们自己的自动事务并实际上已提交。客户端在批处理结束时发出的最终回滚失败。
我也发现了这篇博文:Using ROWVERSION to enforce business rules,虽然它似乎是我需要的,但它要求外键具有相反的方向(即受保护的表是父子关系中的父项)在我的情况下,受保护的表是孩子)
以前有人做过这样的事吗?这似乎不是一个不常见的业务要求,但我还没有看到适当的解决方案。我知道我可以在客户端实现这一点,但这留下了出错的空间:如果有人使用直接SQL(错误地)更改了这些内容,如果升级/迁移脚本或客户端本身有错误并且无法执行constaint?
答案 0 :(得分:1)
经过大量搜索和反复试验,我最终使用INSTEAD OF
触发器来检查约束,并在必要时引发错误并跳过操作。参考原始问题中的模式,以下是必需的触发器:
更新触发器:
CREATE TRIGGER ProtectedTable_LockU ON ProtectedTable
INSTEAD OF UPDATE
AS
SET NOCOUNT ON;
IF UPDATE(Quantity) OR UPDATE(Price)
AND EXISTS(
SELECT *
FROM inserted i INNER JOIN deleted d ON d.Id = i.Id
LEFT JOIN Batch b ON b.Id = d.BatchId
WHERE b.Locked <> 0 AND
(i.Quantity <> d. Quantity OR i.Price <> d.Price))
BEGIN
RAISERRROR('[CK_ProtectedTable_DataLocked]: Attempted to update locked data', 16, 1)
RETURN
END
UPDATE pt
SET Quantity = i.Quantity,
Price = i.Price,
BatchId = i.BatchId
FROM ProtectedTable pt
INNER JOIN deleted d ON d.Id = pt.ID
INNER JOIN inserted i ON i.Id = d.Id
删除触发器:
CREATE TRIGGER ProtectedTable_LockD ON ProtectedTable
INSTEAD OF DELETE
AS
SET NOCOUNT ON;
IF EXISTS(
SELECT *
FROM deleted d
LEFT JOIN Batch b ON b.Id = d.BatchId
WHERE b.Locked <> 0)
BEGIN
RAISERRROR('[CK_ProtectedTable_DataLocked]: Attempted to delete locked data', 16, 1)
RETURN
END
DELETE pt
FROM ProtectedTable pt
INNER JOIN deleted d ON d.Id = pt.ID
当然可以创建一个视图并将触发器放在视图上。这样,您还可以避免INSTEAD OF
触发器与FOREIGN KEY
ON DELETE
/ ON UPDATE
规则之间的冲突。
它很丑陋,我根本不喜欢它,但它是实际工作的唯一解决方案,其行为或多或少类似于常规数据库约束(在修改之前检查数据库约束数据)。我还没有对此进行过广泛的测试,我不确定它是否存在任何问题。我也担心竞争条件(例如,如果在执行触发器期间批量修改了什么?我是否可以包含锁定提示以确保完整性?)
PS:无论应用程序设计不好,这仍然是一个合理的要求。在某些情况下,它也是合法的(你必须证明在某些条件得到满足后,某些记录无法改变)。这就是为什么问题偶尔出现在SO中,并且没有明确的解决方案。我发现这个解决方案比使用AFTER
触发器更好,因为它的行为更像是普通的数据库约束。
希望这可以帮助处于类似情况的其他人。
答案 1 :(得分:0)
不是直接授予用户对表的访问权限,而是使用VIEW
选项创建一个简单的CHECK OPTION
,并且只授予用户通过该视图应用修改的权限。类似的东西:
CREATE VIEW BatchData
WITH SCHEMABINDING
WITH CHECK OPTION
AS
SELECT pt.Id, pt.Quantity, pt.Price, pt.BatchId
FROM
dbo.ProtectedTable pt
INNER JOIN
dbo.Batch b
ON
pt.BatchId = b.Id
WHERE
b.Locked = 0
只要所有插入,更新和删除都通过此视图进行引导(如上所述,您通过权限约束),那么它们只能应用于打开的批处理