I’m performing an UPDATE with OUTPUT query:
UPDATE BatchReports
SET IsProcessed = 1
OUTPUT inserted.BatchFileXml, inserted.ResponseFileXml, deleted.ProcessedDate
WHERE BatchReports.BatchReportGUID = @someGuid
This statement is well and fine; until a trigger is defined on the table. Then my UPDATE statement will get the error 334:
The target table ‘BatchReports’ of the DML statement cannot have any enabled triggers if the statement contains an OUTPUT clause without INTO clause
Now this problem is explained in a blog post by the SQL Server team — UPDATE with OUTPUT clause – Triggers – and SQLMoreResults:
The error message is self-explanatory
And they also give solutions:
The application was changed to utilize the INTO clause
Except I cannot make head nor tail of the entirety of the blog post.
So let me ask my question: What should I change my UPDATE to so that it works?
Given the kludge needed to make
UPDATEwithOUTPUTwork in SQL Server 2008 R2, I changed my query from:to:
Basically I stopped using
OUTPUT. This isn’t so bad as Entity Framework itself uses this very same hack!Hopefully
201220142016201820192020 will have a better implementation.Update: using OUTPUT is harmful
The problem we started with was trying to use the
OUTPUTclause to retrieve the "after" values in a table:That then hits the well-know limitation ("won’t-fix" bug) in SQL Server:
Workaround Attempt #1
So we try something where we will use an intermediate
TABLEvariable to hold theOUTPUTresults:Except that fails because you’re not allowed to insert a
timestampinto the table (even a temporary table variable).Workaround Attempt #2
We secretly know that a
timestampis actually a 64-bit (aka 8 byte) unsigned integer. We can change our temporary table definition to usebinary(8)rather thantimestamp:And that works, except that the value are wrong.
The timestamp
RowVersionwe return is not the value of the timestamp as it existed after the UPDATE completed:0x0000000001B716920x0000000001B71693That is because the values
OUTPUTinto our table are not the values as they were at the end of the UPDATE statement:This means:
The same is true of any trigger that modifies any value in the row. The
OUTPUTwill not OUTPUT the value as of the end of the UPDATE.This means you cannot trust OUTPUT to return any correct values ever.
This painful reality is documented in the BOL:
How did Entity Framework solve it?
The .NET Entity Framework uses rowversion for Optimistic Concurrency. The EF depends on knowing the value of the
timestampas it exists after they issue an UPDATE.Since you cannot use
OUTPUTfor any important data, Microsoft’s Entity Framework uses the same workaround that I do:Workaround #3 – Final – Do not use OUTPUT clause
In order to retrieve the after values, Entity Framework issues:
Don’t use
OUTPUT.Yes it suffers from a race condition, but that’s the best SQL Server can do.
What about INSERTs
Do what Entity Framework does:
Again, they use a
SELECTstatement to read the row, rather than placing any trust in the OUTPUT clause.NOTE: In 2022, Entity Framework added a pre-mature optimization bug.
OUTPUTin T-SQLOUTPUTby Entity FrameworkMicrosoft explains how to undo the bug added by #27372:
With the down-side that you have to retroactively apply it to every table you have, or ever will have.