Data Portal and Transactions.

Data Portal and Transactions.

Old forum URL: forums.lhotka.net/forums/t/1729.aspx


dlswimmer posted on Thursday, November 09, 2006

Hello all,

   I am currently moving a project from CSLA 1 to 2.0 and I was wondering if there was functionality to do what I require. 

   I am looking for a way to use the existing Data Portal Functionality, while also using transactions.  I have the Expert C# 2005 Business Objects book and was reading about the TransactionalTypes.  I see that there are three options, but I don't see a way to do what I need.  What I need to do in my site, is to maintain a transaction across a few updates or deletes so it can be rolled back if there were any problems.  From what I see in CSLA, the transactions are only kept open for the given update or delete, etc.  Does anyone have a solution for me, or am I going to have to bypass the Data Portal functionality?

Thank you for your help.

David

Bayu replied on Thursday, November 09, 2006

You can temporarily store the transaction object in the ApplicationContext.

You explicitly ask for it, so I guess you know what you are doing. Otherwise I would say it is quite a 'dangerous' thing to have transactions that span multiple user interactions and BOs, you will have to program your logic extremely carefully to ensure that you don't end up with 'loose' transactions.

Are you sure about your object design? Shouldn't your design be remodeled so all the functionality falls within the scope of one root object and hence one (transactional) update that starts from the root object and then cascades to any children?

Other than that: ApplicationContext will do the trick.

Regards,
Bayu

guyroch replied on Thursday, November 09, 2006

I agree with Bayu in that you are walking a dangerous path here.

However, you should also consider just creating and an addional factory (+ criteria class) method that accepts a dbtransation as an input parameter and then proceed to you dp_xyz methods with the said dbTransaction.

Use with caution...

 

JoeFallon1 replied on Thursday, November 09, 2006

Rocky came out with LocalContext in 2.1. This is a better choice than ApplicationContext because it "stays local".

e.g. Does not get serialized back through the DataPortal. I think AppContext would not work for a tr object.

I think this can be leveraged to use a single transaction across multiple BOs. I intend to pursue this further when I get a chance but the skeleton code I have looks like it may work.

I added some Public transaction methods to my subclass of BusinessBase like:

BeginTransaction

EndTransaction

RollbackTransaction

TransactionExists

I also have a private Sub CleanupTranasaction which is called by End and Rollback transaction.

The idea is to always call BeginTransaction in a BO but then check if the TransactionExists in the LocalContext.

If it does, then return the already running transaction, otherwise start a new one.

I store a reference to the BO which really begins the transaction and so when other BOs call EndTransaction, nothing happens until the BO that started the transaction calls it. So EndTransaction basically checks the LocalContext to see if the BO that called End is the same that actually started the transaction.

Using this style of code a single BO just works as normal.

If I have a Controller BO that has 3 Root objects and I want to call Save on all three BOs then there will be 4 calls to BeginTransaction but the only one that will end the tr is the one in the Controller. The others will simply "enlist" in the LocalContext tr and when they call EndTransaction nothing happens because they did not really start it.

I think this solves a longstanding concern of mine - and seem to be what you are looking for too.

The original problem is: A single BO saves itself and its children inside a tr. But the next BO uses a different tr and that is an issue if they are to be saved together.

The use of Enterprise Services and Transaction scope also address this issue but have their own drawbacks. I have no plans to use either of them yet.

Note: I have not actually had the chance to test this code but it looks like it should work.

Joe

 

 

 

dlswimmer replied on Friday, November 10, 2006

This was what I was looking for.  I was certainly not looking to save objects across multiple user interactions.  What I need is a way to save multiple BO's that are effected by a single action.  The problem with the parent/child releationship is that not all the object I want to save together have a direct tie.  I think that JoeFallon1 gave a resonable answer, and I just want to follow up on a few things. 

So are you saying to esentially use the parent/child relationship by using a "dummy" root object, and add all the BO's the are to be saved together as children of the root object?  If you could, please give a few more details as this sounds like an interesting approach.

Thank you very much

David

JoeFallon1 replied on Friday, November 10, 2006

David,

For a given Use Case I have a BO which inherits from BusinessBase (I called it a Unit Of Work - but a better name may be Use Case Controller.) This BO is exposes many other BOs which are not child BOs - they are Root BOs which may contain their own children. The idea is that the UOW BO controls the interactions between all the BOs and exposes them to the UI as needed. If the UOW BO is valid then all of its contained BOs must also be valid.

The Transaction concept that I am thinking about will only apply to Root BOs since my Codesmith templates already pass the tr around as a parameter to any contained child collections and children.

The key idea is - that each Root BO will always use the same pattern to save itself within a transaction. The "home run" idea is that the UOW is the only one that will actually Begin and Commit the real underlying tr. The other BOs that get saved will enlist in the tr in the manner I described above. Bottom line - if a BO begins a transaction then it is the only one that really commits it.

This is my untested code:

In a Root BO:

 Protected Overrides Sub DataPortal_Update()
      Dim tr As IDbTransaction

      Try
        tr = Me.BeginTransaction()
        DoUpdate(tr)
        Me.EndTransaction(tr)
      Catch ex As Exception
        Me.RollbackTransaction(tr)
        Throw
      End Try
    End Sub

In my subclass of BusinessBase:

 Public Function BeginTransaction() As IDbTransaction
      Return BOUtil.BeginTransaction(Me)
    End Function

    Public Sub EndTransaction(ByVal tr As IDbTransaction)
      BOUtil.EndTransaction(tr, Me)
    End Sub

    Public Sub RollbackTransaction(ByVal tr As IDbTransaction)
      BOUtil.RollbackTransaction(tr, Me)
    End Sub

In a Utility class named BOUtil:

   Public Shared Function TransactionExists() As Boolean
      Return LocalContext.Contains("tr")
   End Function

    Public Shared Function BeginTransaction(ByVal BO As Core.IBusinessObject) As IDbTransaction
      Dim tr As IDbTransaction
      Dim cn As IDbConnection

      If TransactionExists() Then
        tr = CType(LocalContext.Item("tr"), IDbTransaction)
      Else
        cn = DAL.CreateConnection
        cn.Open()
        tr = cn.BeginTransaction(IsolationLevel.Serializable)
        LocalContext.Add("tr", tr)
        LocalContext.Add("StartedTrans", BO)
      End If

      Return tr
    End Function

    Public Shared Sub EndTransaction(ByVal tr As IDbTransaction, ByVal BO As Core.IBusinessObject)
      If LocalContext.Item("StartedTrans") Is BO Then
        Dim cn As IDbConnection = tr.Connection
        tr.Commit()
        CleanupTransaction(cn)
      End If
    End Sub

    Public Shared Sub RollbackTransaction(ByVal tr As IDbTransaction, ByVal BO As Core.IBusinessObject)
      If LocalContext.Item("StartedTrans") Is BO Then
        Dim cn As IDbConnection = tr.Connection
        tr.Rollback()
        CleanupTransaction(cn)
      End If
    End Sub

    Private Shared Sub CleanupTransaction(ByVal cn As IDbConnection)
      cn.Close()
      LocalContext.Remove("tr")
      LocalContext.Remove("StartedTrans")
    End Sub

Joe

 

Copyright (c) Marimer LLC