Hi!
I have a case where I need to save changes to three unrelated (no Parent-Child relation defined) BO's and would like to wrap these within a Transaction. i.e. Changes to all three should be saved or nothing should be saved.
How should I go about this?
I am visualizing something like this:
Begin Transaction //Not sure how to do this
try
{
CategoryListBO.Save()
ScheduleListBO.Save()
RevisionListBO.Save()
Commit() //Not sure how to do this
}
catch (DbException ex)
{
Rollback() //Not sure how to do this
}
1] Not sure how I would create a Transaction Object
2] Not sure how I can point all the BO's to that transaction object.
Thanks
Sarosh
Here is one way:
http://forums.lhotka.net/forums/thread/9066.aspx
Joe
On your client you have three root objects. These objects have been changed by the user or whatever. You need to save all three, but within the context of a transaction.
The solution is to use a Command object, somewhat like this:
<Serializable()> _
Public Class OrderPlacer
Inherits CommandBase
Private _order As Order
Private _customer As Customer
Private _location As Location
Private _transactionId As Integer
Public ReadOnly Property Order() As Order
Get
Return _order
End Get
End Property
Public ReadOnly Property Customer() As Customer
Get
Return _customer
End Get
End Property
Public ReadOnly Property Location() As Location
Get
Return _location
End Get
End Property
Public ReadOnly Property TransactionId() As Integer
Get
Return _transactionId
End Get
End Property
Public Shared Function PlaceOrder(order As Order, customer As Customer, location As Location) As OrderPlacer
Dim cmd As New OrderPlacer(order, customer, location)
' execute clone of object
cmd = DataPortal.Execute(cmd.Clone)
Return cmd
End Function
Private Sub New()
' require use of factory method
End Sub
Private Sub New(order As Order, customer As Customer, location As Location)
_order = order
_customer = customer
_location = location
End Sub
<Transaction(TransactionScope)> _
Protected Overrides Sub DataPortal_Execute()
' save objects
_order = _order.Save()
_customer = _customer.Save()
_location = _location.Save()
' generate transaction id
_transactionId = <something meangingful>
End Sub
End Class
As with any insert/update/delete/execute operation, you only need to clone the object when using a local data portal. If you know you'll only use a remote data portal you can skip the clone call in the factory method.
Hi!
That was exactly what I was looking for!
The only part I am not clear about is:
' generate transaction id
_transactionId = <something meangingful>
what does <something meangingful> mean ?
Is it just some unique int ID or something else?
Thanks.
Sarosh
You can just ignore that if you’d like. The point is that
you can return other information beyond the 3 objects. Or not. As you choose.
There’s no inherent value in that transactionId example –
it is just an example.
Rocky
Hi!
I have created my new wrapper class and everything looks good except for two things.
1] cmd = DataPortal.Execute(cmd.Clone); // Error 7 'BusinessObjects.OrderPlacer' does not contain a definition for 'Clone'
2] [Transaction(TransactionScope)] //Error 7 Attribute 'Transaction' is not valid on this declaration type. It is valid on 'class' declarations only.
What should I do here?
Thanks
Sarosh
Here is the code for my new class.
using
System;using
System.Data;using
System.Data.SqlClient;using
System.Data.Common;using
System.Collections.Generic;using
System.Text;using
Csla;using
Csla.Data;using
Csla.Validation;using
System.EnterpriseServices;namespace
BusinessObjects{
[
Serializable()] class OrderPlacer : CommandBase{
private ScheduleList _ScheduleList; public readonly ScheduleList ScheduleList; private LoadCategoryList _LoadCategoryList; public readonly LoadCategoryList LoadCategoryList; private DutyList _DutyList; public readonly DutyList DutyList; private OrderPlacer(){
//require use of factory method}
private OrderPlacer(ScheduleList scheduleList, LoadCategoryList loadCategoryList, DutyList dutyList){
this._ScheduleList = scheduleList; this._LoadCategoryList = loadCategoryList; this._DutyList = dutyList;}
public static OrderPlacer PlaceOrder(ScheduleList scheduleList, LoadCategoryList loadCategoryList, DutyList dutyList){
OrderPlacer cmd = new OrderPlacer(); //execute clone of object cmd = DataPortal.Execute(cmd.Clone); // Error 7 'BusinessObjects.OrderPlacer' does not contain a definition for 'Clone' return cmd;}
[Transaction(TransactionScope)] //Error 7 Attribute 'Transaction' is not valid on this declaration type. It is valid on 'class' declarations only. protected override void DataPortal_Execute(){
this._ScheduleList = this._ScheduleList.Save(); this._LoadCategoryList = this._LoadCategoryList.Save(); this._DutyList = this._DutyList.Save();}
}
}
Yeah, good point on the clone. CommandBase doesn’t
implement IClonable, but you’ll need to in your class to make this work.
Fortunately CSLA already implements the cloning functionality, so you can just
copy about half a dozen lines of code from BusinessBase into your class (or
better yet create a new base class that subclasses CommandBase) to implement
IClonable.
Regarding issue 2, I probably just mistyped the attribute name –
it might be Transactional? In the middle of travel and can’t check for
you right now.
Rocky
From: Sarosh
[mailto:cslanet@lhotka.net]
Sent: Tuesday, June 26, 2007 3:05 PM
To: rocky@lhotka.net
Subject: Re: [CSLA .NET] RE: Wrapping not related BO's in a Transaction
Hi!
I have created my new wrapper class and everything looks good except for two
things.
1] cmd =
DataPortal.Execute(cmd.Clone);
// Error 7 'BusinessObjects.OrderPlacer' does not contain a definition for
'Clone'
2] [Transaction(TransactionScope)] //Error 7 Attribute 'Transaction' is not valid on this
declaration type. It is valid on 'class' declarations only.
What should I do here?
Thanks
Sarosh
Here is the code for my new class.
using System;
using System.Data;
using System.Data.SqlClient;
using System.Data.Common;
using System.Collections.Generic;
using System.Text;
using Csla;
using Csla.Data;
using Csla.Validation;
using System.EnterpriseServices;
namespace BusinessObjects
{
[Serializable()]
class OrderPlacer : CommandBase
{
private ScheduleList
_ScheduleList;
public readonly ScheduleList ScheduleList;
private LoadCategoryList
_LoadCategoryList;
public readonly LoadCategoryList LoadCategoryList;
private DutyList
_DutyList;
public readonly DutyList DutyList;
private OrderPlacer()
{
//require use of factory method
}
private OrderPlacer(ScheduleList scheduleList, LoadCategoryList loadCategoryList, DutyList dutyList)
{
this._ScheduleList = scheduleList;
this._LoadCategoryList = loadCategoryList;
this._DutyList = dutyList;
}
public static OrderPlacer PlaceOrder(ScheduleList scheduleList, LoadCategoryList loadCategoryList, DutyList dutyList)
{
OrderPlacer cmd = new OrderPlacer();
//execute clone of object
cmd =
DataPortal.Execute(cmd.Clone);
// Error 7 'BusinessObjects.OrderPlacer' does not contain a definition for
'Clone'
return cmd;
}
[Transaction(TransactionScope)] //Error 7 Attribute 'Transaction' is not
valid on this declaration type. It is valid on 'class' declarations only.
protected override void DataPortal_Execute()
{
this._ScheduleList = this._ScheduleList.Save();
this._LoadCategoryList = this._LoadCategoryList.Save();
this._DutyList = this._DutyList.Save();
}
}
}
Hi,
Regarding issue 2, the attribute is called Transactional.
Cheers,
Herman
Hi!
I could not find that one but this one is picked up by intellisense.
[Transaction(TransactionalTypes.TransactionScope)]
but I still get compile time error
//Error 7 Attribute 'Transaction' is not valid on this declaration type. It is valid on 'class' declarations only.
Sarosh
Hi!
[Transaction(TransactionalTypes.TransactionScope)]
As I mentioned in my eariler post intellisense does find it but I still get compile time error
//Error 7 Attribute 'Transaction' is not valid on this declaration type. It is valid on 'class' declarations only.
So I am not sure what to do here.
Sarosh
Rocky --
Will the use of a Commandbase'd object like this work when using the WCFDataPortal?
Do we have to do any modification of the bindings/endpoints to support these transactions?
Thanks,
David Lutz
You do not need to modify the bindings, endpoints or the data portal. This is normal behavior.
What will happen, is that the command object will hold a reference to all the root objects. When you tell the command object to execute, it will serialize to the server, along with any objects it references (so it brings the root objects along for the ride).
On the server, the command object's DataPortal_Execute() would be marked with the Transactional attribute, so it is running in a transaction. This method invokes the Save() methods of the root objects, and of course they run in that same transaction.
The command object is then serialized back to the client, and it brings the updated root objects back with it. You must then replace the client-side references to the old root objects with these new root objects - just like you would replace the old references with the result of a Save() method call.
Rocky --
I love it when you write things like this. ;-)
I can see it now. One is basically wrapping one more CSLA objects into a single CommandBase object. When the Data_Portal.Execute method is called on the CommandBase the whole bundle of objects gets serialized to the remote data portal (WCF in this case) and the transactions, if any, then get fired on the server. The transaction never travels throught the WCF data portal directly.
Thanks for the answer. Thanks for CSLA in general and all your hard work on it. I hope all that read this forum understand our good fortune.
Best regards --
David Lutz
Copyright (c) Marimer LLC