You can see the Demo here, source code below.
You can run stored procedures in Lightswitch, but you have to wrap them in a RIA service and treat them as Query, i.e. the DomainService must return an IQueryable (or variation of).
This is not an article about how to run the Stored procedures (but you can download the source below to have a look), but more about how transactions are dealt with within those procedures and Lightswitch.
The following relates to SqlServer, but the same should apply to any DB flavour.
If you have a bunch of stored procedures you need to run, and they do CRUD operations, it's best practice to wrap those procedures in transactions and add error handling.
Essentially, you want to be able to Commit/Rollback these operations depending on the business logic in your procedure and also handle any exceptions that may have been encountered.
It's also good practice to keep a record of any messages that are written (i.e. debug output) in those procedures which can give some indication of what the procedure was doing before the error occurred.
I tend do put the following 3 out parameters on all Stored Procedures
@ErrorCode int out - Denotes what error was raised, could be user initiated or come from error_number()
@ErrorMessage nvarchar(max) out - Denotes the error message, could be user initiated or come from error_message()
I also have Try/Catch blocks and usually Raise a user defined exception in the Try block with a Severity greater than 10, which automatically gets propogated to the Catch block where I can set the messages and deal with any transactions (normally this means rolling back).
Because all my procedures use this logic, I have a pretty good "Trace" of what's happened in my procedures, this is especially true if Procedure A calls Procedure B which calls Procedure C etc.
Which brings me on to Transactions, in SqlServer they are handled in Begin Tran and End Tran blocks and can be nested, but only the first Transaction will actually persist or Rollback the data.
You can keep a track of the transaction count using @@TranCount which gives you, you got it, the transaction level count.
So, in SqlServer the Commits and Rollbacks are handled like this .....
Commit
If it is the first trasaction level i.e. @@Trancount = 1, the commit is performed and data is persisted.
If it is a nested transaction i.e. @@Trancount > 1, then committing simply decrements @@Trancount
This ensures that only the outer most transaction does the actual commit.
General guidlines when dealing with transactions in stored procedures suggest that :-
"If a stored procedure does not initiate the outermost transaction, it should not issue a ROLLBACK."
The upshot is that a transaction should be rolled back at the same level at which it was started, so only the calling procedure that starts a transaction should ever roll back. If the client code started the transaction, none of the procedures should roll back, but raise an error to the client.
What this means is that, if you didn't start the transaction, you shouldn't end it, otherwise you'll get error ....
Transaction count after EXECUTE indicates a mismatching number of BEGIN and COMMIT statements
On commits, everything works fine, as the Procedure begins it's Transaction @@trancount = 2, when it commits @@trancount gets decremented back to 1 and the domainService then does it's commit and everyone is happy.
The problem lies with Rollbacks, guidlines suggest that if the Procedure needs to rollback and it did not initiate the starting transaction, then it shouldn't issue a rollback, but instead raise an error to the caller (along with trace info), in our case the client app, it's then upto the client app to do the Rollback.
That's where the problem is, Lightswitch started the transaction, so it's upto Lightswitch to roll it back.
I put together a test harness to make it easier to see what I mean, the source code is attached below.
There are ways to deal with this
All work, 2. gets around the Transaction count error, but Rollback seems safer, so I favour that.
If anyone has a more elegant solution, I'd love to know.
If you want to try this at home :-
Any comments always welcome
D
![]()
There is no right way ... but there may be a better way