Using Transactions
Introduction
Although short- and long-running transaction are similar in many ways, there are also some crucial differences. This page outlines how to choose what transaction to use and how to mix transactions.
Choosing Transaction
It's important to chose the right transaction. In most situation, the choice is clear - if you are going to attach a transaction to a view-model, use a long-running transaction, otherwise, use a short-running transaction. When the choice is not clear, consider these factors:
Side Effects
Since Db.Transact
and Db.TransactAsync
can run more than once because of conflicts, they should not have any side effects, such as HTTP calls or writes to a file. `Db.Scope` can have side effects, as long as it's not in an iterator.
Rollbacks
The only way to rollback changes in Db.Transact
and Db.TransactAsync
is to throw an exception in the transaction. The alternative is to use Db.Scope
with Transaction.Rollback
.
Conflics
If conflicts are likely, use Db.Transact
or Db.TransactAsync
because these handle conflicts while Db.Scope
doesn't.
Mixing Transactions
Transactions can be mixed as outer and inner transactions - one transaction wraps around the other. These are the possible combinations and their effects:
Outer
Inner
Effect
Long
Long
Execute inner as part of outer
Long
Short
Execute inner as a separate transaction
Short
Long
Not supported. Run-time error
Short
Short
Execute inner as part of outer
Transactions can be mixed as outer and inner transactions - one transaction wraps around the other. These are the possible combinations and their effects:
Long-Running in Long-Running
With a long-running transaction inside a long-running transaction, they act as if they were one transaction:
[Database]
public class Person {}
[Database]
public class Animal {}
class Program
{
static void Main()
{
Db.Scope(() =>
{
new Person();
Db.Scope(() =>
{
Transaction.Current.Commit(); // Commits the Person
new Animal();
});
Transaction.Current.Commit(); // Commits the Animal
});
}
}
Short-Running in Long-Running
Short-running transactions in long-running transactions are executed separately:
using Starcounter;
using System.Linq;
[Database]
public class Person {}
[Database]
public class Animal
{
public string Specie { get; set; }
}
class Program
{
static void Main()
{
Db.Scope(() =>
{
new Person();
Db.Transact(() =>
{
new Animal() { Specie = "Dog" };
}); // Animal is commited to the database - transaction is done
// The Animal committed can be accessed in the outer transaction
var animal = Db.SQL("SELECT a FROM Animal a").First();
// Rolls back the Person but not the Animal
Transaction.Current.Rollback();
});
}
}
Long-Running in Short-Running
Using long-running transactions in short-running transactions is not supported, it will throw ScErrTransactionLockedOnThread (SCERR4031)
:
Db.Transact(() =>
{
Db.Scope(() => // SCERR4031
{
new Person();
});
});
Short-Running in Short-Running
Short-running in short-running transactions work the same as with long-running in long-running transactions: the inner transaction is executed as a part of the outer:
using Starcounter;
[Database]
public class Person {}
[Database]
public class Animal {}
class Program
{
static void Main()
{
Db.Transact(() =>
{
Db.Transact(() =>
{
new Person();
}); // Person is not commited
new Animal();
}); // Animal and Person are commited
}
}
ScErrReadOnlyTransaction
If an operation is done on the database without a transaction an exception will be thrown:
The transaction is readonly and cannot be changed to write-mode. (ScErrReadOnlyTransaction (SCERR4093))
For example:
[Database]
public class Person {}
class Program
{
static void Main()
{
new Person(); // SCERR4093
}
}
To fix this, wrap the operation in a transaction:
[Database]
public class Person {}
class Program
{
static void Main()
{
Db.Transact(() => new Person());
}
}
Last updated