Wrapping not related BO's in a Transaction

Wrapping not related BO's in a Transaction

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


Sarosh posted on Tuesday, June 26, 2007

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

JoeFallon1 replied on Tuesday, June 26, 2007

Here is one way:

 

http://forums.lhotka.net/forums/thread/9066.aspx

 

Joe

 

RockfordLhotka replied on Tuesday, June 26, 2007

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.

Sarosh replied on Tuesday, June 26, 2007

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

RockfordLhotka replied on Tuesday, June 26, 2007

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

Sarosh replied on Tuesday, June 26, 2007

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();

}

}

}

RockfordLhotka replied on Tuesday, June 26, 2007

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();

}

}

}



McManus replied on Wednesday, June 27, 2007

Hi,

Regarding issue 2, the attribute is called Transactional.

Cheers,
Herman

Sarosh replied on Wednesday, June 27, 2007

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

ajj3085 replied on Wednesday, June 27, 2007

[Transactional( TransactionalTypes.TransactionScope )] is what you're looking for.

You likely need to import the namespace if intellisense can't find that attribute. 

Sarosh replied on Wednesday, June 27, 2007

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

ajj3085 replied on Wednesday, June 27, 2007

Transactional is what you need.  Also, besure to having a using Csla; statement.

cultofluna replied on Tuesday, July 03, 2007

Hello,

An older, but very interesting post. I have the same situation and trying to get it working. Normally I use ADO transactions, and that's what's my question is about.

When using System.Transactions are ADO transaction obsolete? I'm trying to call the Save function for two objects that are ADO transaction based, but do they disturb the System Transaction?

I get the following error:

DataPortal.Update mislukt (Csla.DataPortalException: DataPortal.Update mislukt (System.Transactions.TransactionException: The transaction has already been implicitly or explicitly committed or aborted.

Any thoughts?

Regards,

cultofluna replied on Tuesday, July 03, 2007

Hello,

I've solved the above problem. I was sloppy on replacing the ADO transactions and try catch structures.

But than I got a new problem :) I'm trying to create a CommandBase object that calls the Save for two BO's.

        [Transactional(TransactionalTypes.TransactionScope)]
        protected override void DataPortal_Execute()
        {
            _inslag.Status = WmsMutationStatusses.Afgedrukt;
            _inslag.Save();
           
            _locatie.IntakeId = (int)_inslag.SysId;
            _locatie.Save();
        }

Two questions about this structur:

1. Rollback
The first save is no problem. The second BO however has an invalid Stored Procedure name and runs into an error. I now this is an user error, however shouldn't this do a rollback for the first BO as well?

2. Multiple connections
Both BO's open the same connection during the Save. In the forum I read that this means DTC starts a distributed transaction. Is this a problem and perhaps the problem for my first question? I can't pass the connection for two separate BO's can I?

Any thoughts?

Regards,

ajj3085 replied on Thursday, July 05, 2007

I would think that TS would have promoted the transaction to a distributed one.  Does the error on the second BO cause an exception to be thrown?  If it doesn't, then TS will commit the transaction.  You'll have to check for errors and manually throw an exception in that case for the entire transaction to rollback.

DTC isn't a problem as long as you don't mind the performance hit.  DTC transactions are much more expensive than a simple Transaction Scope transaction.  You could modify each BO to look in LocalContext for a connection and use that if it exists.  You'll then need to open the transaction in your DP_Execute for the command and make sure to close it when the commands complete (errors or not).

HTH

dlutz52 replied on Thursday, March 19, 2009

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

RockfordLhotka replied on Thursday, March 19, 2009

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.

dlutz52 replied on Sunday, March 22, 2009

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