tags:

views:

53

answers:

2

I want to have something like sub-transactions, in that you can mark a point where you would start the sub-transaction, then at the point of descision for that bit, you can either roll-back (abort the sub-bit) or carry on, effectively commiting, when the out transation commits. Of course, if you abort the outer transaction, the marked bit aborts too.

How can I do that with NHibernate but the transaction is being closed during the fisrt commit and thus i`m having the error message

no open transaction to commit

My code is as follows:

API.Data.Session session = API.Data.SessionManager.GetSession();
        session.BeginTransaction();
         try
        {
            Project project = Project.Load(ID);
            ...........
            Save(project);
            .....................

           session.CommitTransaction();
        }
         catch
         {
             session.RollbackTransaction();
             throw;
         }

    public void save(Project project)
    {
Data.SessionManager.GetSession().BeginTransaction();
                try
                {
                    Save();
                    LogIssueChange(test);
                    Data.SessionManager.GetSession().CommitTransaction();
                }
                catch
                {
                    Data.SessionManager.GetSession().RollbackTransaction();
                    throw;
                }
    }
+1  A: 

You can't, NHibernate does not support nested transactions. You might be able to achieve this with the Systems.Transaction namespace.


Edit: DeferableTransaction implementation is below. I have this as an extension method on ISession. That said, usage has been very rare.

Extension methods:

    public static DeferableTransaction BeginOrDeferTransaction(this ISession session)
    {
        return new DeferableTransaction(session);
    }

    public static DeferableTransaction BeginOrDeferTransaction(this ISession session, IsolationLevel isolationLevel)
    {
        return new DeferableTransaction(session, isolationLevel);
    }

Implementation:

/// <summary>
/// Begins a transaction or defers to an existing transaction. The IsolationLevel will not be changed
/// on an existing transaction.
/// </summary>
public class DeferableTransaction : IDisposable
{
    private readonly bool _ownedTransaction;
    private readonly ITransaction _transaction;

    public DeferableTransaction(ISession session) 
        : this(session, IsolationLevel.ReadCommitted)
    {}

    public DeferableTransaction(ISession session, IsolationLevel isolationLevel)
    {
        if (session.Transaction.IsActive)
        {
            _ownedTransaction = false;
            _transaction = session.Transaction;
        }
        else
        {
            _ownedTransaction = true;
            _transaction = session.BeginTransaction(isolationLevel);
        }
    }

    public bool OwnedTransaction
    {
        get { return _ownedTransaction; }
    }

    public void CommitOrDefer()
    {
        if (_ownedTransaction)
        {
            _transaction.Commit();
        }
    }

    public void RollbackOrDefer()
    {
        if (_ownedTransaction)
        {
            _transaction.Rollback();
        }
    }

    public void Enlist(IDbCommand command)
    {
        _transaction.Enlist(command);
    }

    public bool IsActive
    {
        get { return _transaction.IsActive; }
    }

    public bool WasCommitted
    {
        get { return _transaction.WasCommitted; }
    }

    public bool WasRolledBack
    {
        get { return _transaction.WasRolledBack; }
    }

    public void Dispose()
    {
        if (_ownedTransaction)
        {
            _transaction.Dispose();
        }
    }
}
Jamie Ide
Isn`t there any other method? I don`t want to interact directly with the database
No, there's no other way. I do not put transactions in repository/service methods at all with NHibernate so that multiple repository methods can participate in the same transaction. There's very rarely a need for nested transactions outside of distributed systems. I do have an extension method for ISession called DeferableTransaction that uses similar semantics and allows a transaction to defer to an existing transaction or start a new one. I will post it if you're interested.
Jamie Ide
@Jamie: can't speak for the OP, but I'd be interested in seeing your impl
DanP
+1  A: 

The native support is provided via Nested Transactions and the TransactionScope class. Note that AFAIK this is supported by Sql Server and Oracle although it may work for other databases if they support distributed transactions as well as the API that plugs to System.Data supports it as well.

see these SO questions http://stackoverflow.com/questions/tagged/nhibernate+transactionscope

Jaguar