Transaction Management

Business services always runs in a transaction context. Its either main transaction or manually created transactions. In this section you can learn how we deal with database transactions.

Using Main Transaction

Before every service call, Gazel creates a database connection and begins a transaction. If there occurs an exception the transaction is automatically rolled back. If there are no exceptions than transaction is committed.

public class Company { ... public virtual void AddEmployee(string name) { context.New<Employee>().With(this, name); throw new Exception(); //Above insert will be rolled back } ... }

Creating New Transactions

Most of the time one transaction will be enough for business services. However there are cases when you will need a record to be updated or inserted and committed to database.

public class Company { ... public virtual void AddEmployee(string name) { context.WithNewTransaction().Do(() => { context.New<Employee>().With(this, name); }); throw new Exception(); //Above insert will not be rolled back } ... }

In above example context.WithNewTransaction().Do(() => { ... }) creates a new database connection and begins a transaction on this new connection to provide you with a new transaction context.

When an exception occurs in a transaction scope, Gazel catches the exception, rollbacks the transcation, closes the connection and rethrows the exception.

public class Company { ... public virtual void AddEmployee(string name) { context.WithNewTransaction().Do(() => { context.New<Employee>().With(this, name); throw new Exception(); //Above insert will be rolled back }); } ... }

Using Persistent Objects

In previous example we used a variable (name) from outer scope. This is not a problem when variables are non-persistent objects such as string, int, a manager object or any class or struct in your modules.

Consider you need to pass a persistent object to AddEmployee method.

public class Company { ... public virtual void AddEmployee(string name, Branch employeeBranch) { context.WithNewTransaction().Do(() => { //employeeBranch object will cause trouble context.New<Employee>().With(this, name, employeeBranch); }); } ... }

When you pass a persistent object directly to a new transaction context, this means that a Branch instance from outer scope will be assigned to an Employee instance from inner scope. This causes an unexpected state for NHibernate. To assign a persistent object to another persistent object, they need to be loaded from the same transaction scope and ISession instance. To achieve this you need to pass Branch object with a different way.

public class Company { ... public virtual void AddEmployee(string name, Branch employeeBranch) { context.WithNewTransaction(employeeBranch).Do(eb => { //correct way to pass a persistent object to new transaction context.New<Employee>().With(this, name, eb); }); } ... }

employeeBranch is passed to new transaction scope using context.WithNewTransaction(employeeBranch).Do(eb => { ... }). When you do this, Gazel gets the id of given employeeBranch, loads it in new transaction scope and passes it as a parameter (eb).

You can pass up to 15 parameters to WithNewTransaction method.

Nested Transactions

You can create as many nested transactions as you want like below;

//this level uses main transaction context.WithNewTransaction().Do(() => { //this level uses first transaction context.WithNewTransaction().Do(() => { //this level uses second transaction context.WithNewTransaction().Do(() => { //this level uses third transaction }); }); });

When you create nested transactions, beware that you are using more than one database connections at a time.

Example: Update Balance of an Account

Below is an example to demonstrate an update to a balance in an account.

public class Account { ... public virtual void Withdraw(Money amount) { context.WithNewTransaction(this).Do(@this => { @this.Balance -= amount; }); } ... }

If you want to lock a record, you can make use of IRepository<T>.Lock method.

public class Account { ... public virtual void Withdraw(Money amount) { context.WithNewTransaction(this).Do(@this => { //use repository of @this object. //"this.repository" uses outer scope whereas "@this.repository" uses inner scope. @this.repository.Lock(@this); @this.Balance -= amount; }); } ... }

To make it more readable, lets extract balance operation to another method.

public class Account { ... public virtual void Withdraw(Money amount) { context.WithNewTransaction(this).Do(@this => { @this.LockAndChangeBalance(amount); }); } private void LockAndChangeBalance(Money amount) { repository.Lock(this); Balance -= amount } ... }