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
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...
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
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