Transactions
What are transactions?
In short, it’s a way to bind multiple programming instructions that must succeed or fail together.
Transactions are, ultimately, a feature of the database. A SQL transaction block lets you wrap any number of commands together into a single set. If any of the commands fail, ALL of the commands within this block can be rolled back. This is very important for data integrity. Imagine you were creating a PO, and there was a failure to add the row to POHeader table (e.g. you assigned the VendorName to the VendorNum field, which doesn’t like letters). You would not want the system to proceed to write the line records to PODetail. They would point to a PO number that doesn’t exist in the header table. This is the definition of an orphaned record.
The TransactionScope class extends this protection and functionality to the business layer as well. Failure of any method call within a transaction will roll back all changes within said transaction, even the ones that succeeded prior to the error.
When should you use a Transaction?
The glib answer is “every time the system nags you for one.” Note that it won’t do that every time, since many method calls create an internal transaction. If you’re doing something simple, that is, changing one field in one record (e.g. writing to a UD field), you can probably skip it if the system doesn’t demand it.
A better answer is “every time you’re doing something that depends on the success of previous and/or subsequent updates to work correctly”.
How do you use a Transaction?
When creating a function, there is a checkbox on the right side of the function tab labelled “Requires Transaction”. Check it and your whole function is now (mostly) protected. If you run into one failure, it’s going to try and roll back everything.
What if I’m comfortable with a little bit of failure?
Let’s say you’re building a batch job that’s going to loop through a whole bunch of records and try to update them. Any one of those updates can fail, but you want it to keep going because the next record is independent. The answer is to create a transaction for each iteration through your loop.
The basic template for such a function is below.
CallService<Erp.Contracts.SomeModuleSvcContract>(svc =>
{
foreach(var thing in things)
{
try //So the whole thing doesn't die on a single error.
{
using (var txScope = IceContext.CreateDefaultTransactionScope())
//Creates a Transaction for just this one iteration.
{
svc.MethodCall();
svc.AnotherCall();
txScope.Complete(); //Close out transaction
}
}
catch (Exception e)
{
ErrMsg += SomeKeyField + ":" + e.Message + Environment.NewLine;
//ErrMsg would be an output on your function.
//You could log any number of ways too.
//This was just what I used on the code I copied this from, which was a batch process.
}
}
}
Note that if you’re going to declare a TxScope in your code, make sure to uncheck the box on the function tab. Use one or the other, not both. Avoid transactions within transactions.