Don't complicate it, I'd go with triggers. There is no shame in using them, this is what they are there for.
To avoid lots of logic in the triggers, I add an "Editable" bit column into the header table, then basically use a divide with Editable to either work or cause a divide by zero error, which I CATCH and convert to a Invoice is not editable, no changes permitted
message. There are no EXISTS used to eliminate extra overhead. Try this:
CREATE TABLE testInvoices
(
InvoiceID INT not null IDENTITY(1,1) PRIMARY KEY
,Editable bit not null default (1) --1=can edit, 0=can not edit
,yourData char(2) not null default ('xx')
)
go
CREATE TABLE TestFees
(
FeeID INT IDENTITY(1,1) PRIMARY KEY
,InvoiceID INT REFERENCES testInvoices(InvoiceID)
,Amount MONEY
)
go
CREATE TRIGGER trigger_testInvoices_instead_update
ON testInvoices
INSTEAD OF UPDATE
AS
BEGIN TRY
--cause failure on updates when the invoice is not editable
UPDATE t
SET Editable =i.Editable
,yourData =i.yourData
FROM testInvoices t
INNER JOIN INSERTED i ON t.InvoiceID=i.InvoiceID
WHERE 1=CONVERT(int,t.Editable)/t.Editable --div by zero when not editable
END TRY
BEGIN CATCH
IF ERROR_NUMBER()=8134 --catch div by zero error
RAISERROR('Invoice is not editable, no changes permitted',16,1)
ELSE
BEGIN
DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
END
END CATCH
GO
CREATE TRIGGER trigger_testInvoices_instead_delete
ON testInvoices
INSTEAD OF DELETE
AS
BEGIN TRY
--cause failure on deletes when the invoice is not editable
DELETE t
FROM testInvoices t
INNER JOIN DELETED d ON t.InvoiceID=d.InvoiceID
WHERE 1=CONVERT(int,t.Editable)/t.Editable --div by zero when not editable
END TRY
BEGIN CATCH
IF ERROR_NUMBER()=8134 --catch div by zero error
RAISERROR('Invoice is not editable, no changes permitted',16,1)
ELSE
BEGIN
DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
END
END CATCH
GO
CREATE TRIGGER trigger_TestFees_instead_insert
ON TestFees
INSTEAD OF INSERT
AS
BEGIN TRY
--cause failure on inserts when the invoice is not editable
INSERT INTO TestFees
(InvoiceID,Amount)
SELECT
f.InvoiceID,f.Amount/i.Editable --div by zero when invoice is not editable
FROM INSERTED f
INNER JOIN testInvoices i ON f.InvoiceID=i.invoiceID
END TRY
BEGIN CATCH
IF ERROR_NUMBER()=8134 --catch div by zero error
RAISERROR('Invoice is not editable, no changes permitted',16,1)
ELSE
BEGIN
DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
END
END CATCH
GO
CREATE TRIGGER trigger_TestFees_instead_update
ON TestFees
INSTEAD OF UPDATE
AS
BEGIN TRY
--cause failure on updates when the invoice is not editable
UPDATE f
SET InvoiceID =ff.InvoiceID
,Amount =ff.Amount/i.Editable --div by zero when invoice is not editable
FROM TestFees f
INNER JOIN INSERTED ff ON f.FeeID=ff.FeeID
INNER JOIN testInvoices i ON f.InvoiceID=i.invoiceID
END TRY
BEGIN CATCH
IF ERROR_NUMBER()=8134 --catch div by zero error
RAISERROR('Invoice is not editable, no changes permitted',16,1)
ELSE
BEGIN
DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
END
END CATCH
GO
CREATE TRIGGER trigger_TestFees_instead_delete
ON TestFees
INSTEAD OF DELETE
AS
BEGIN TRY
--cause failure on deletes when the invoice is not editable
DELETE f
FROM TestFees f
INNER JOIN DELETED ff ON f.FeeID=ff.FeeID
INNER JOIN testInvoices i ON f.InvoiceID=i.invoiceID AND 1=CONVERT(int,i.Editable)/i.Editable --div by zero when invoice is not editable
END TRY
BEGIN CATCH
IF ERROR_NUMBER()=8134 --catch div by zero error
RAISERROR('Invoice is not editable, no changes permitted',16,1)
ELSE
BEGIN
DECLARE @ErrorMessage nvarchar(400), @ErrorNumber int, @ErrorSeverity int, @ErrorState int, @ErrorLine int
SELECT @ErrorMessage = N'Error %d, Line %d, Message: '+ERROR_MESSAGE(),@ErrorNumber = ERROR_NUMBER(),@ErrorSeverity = ERROR_SEVERITY(),@ErrorState = ERROR_STATE(),@ErrorLine = ERROR_LINE()
RAISERROR (@ErrorMessage, @ErrorSeverity, @ErrorState, @ErrorNumber,@ErrorLine)
END
END CATCH
GO
here is a simple test script to test out the different combinations:
INSERT INTO testInvoices VALUES(default,default) --works
INSERT INTO testInvoices VALUES(default,default) --works
INSERT INTO testInvoices VALUES(default,default) --works
INSERT INTO TestFees (InvoiceID,Amount) VALUES (1,111) --works
INSERT INTO TestFees (InvoiceID,Amount) VALUES (1,1111) --works
INSERT INTO TestFees (InvoiceID,Amount) VALUES (2,22) --works
INSERT INTO TestFees (InvoiceID,Amount) VALUES (2,222) --works
INSERT INTO TestFees (InvoiceID,Amount) VALUES (2,2222) --works
update testInvoices set Editable=0 where invoiceid=3 --works
INSERT INTO TestFees (InvoiceID,Amount) VALUES (3,333) --error<<<<<<<
UPDATE TestFees SET Amount=1 where feeID=1 --works
UPDATE testInvoices set Editable=0 where invoiceid=1 --works
UPDATE TestFees SET Amount=11111 where feeID=1 --error<<<<<<<
UPDATE testInvoices set Editable=1 where invoiceid=1 --error<<<<<<<
UPDATE testInvoices set Editable=0 where invoiceid=2 --works
DELETE TestFees WHERE invoiceid=2 --error<<<<<
DELETE FROM testInvoices where invoiceid=2 --error<<<<<
UPDATE testInvoices SET Editable='A' where invoiceid=1 --error<<<<<<< Msg 245, Level 16, State 1, Line 1 Conversion failed when converting the varchar value 'A' to data type bit.