Command Object Implementation

Command Object Implementation

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


Warren posted on Thursday, July 03, 2008

 My application has a use case which is a server side work flow that I have chosen to implement with a Command object.

The steps in this workflow are:  

Get Balances
Verify Balances
Report Balances
Load Balances

If any of the steps fail, the subsequent steps are not run and the process is terminated with an error condition.  The user can opt to cancel  "Load Balances" based on the output produced in "Report Balances".

I'm not sure if my question is a simple .Net question or CSLA?

I am coding the Command object based on the design Rocky demonstrates  in Chapter 7 except I have 4 stored procedure calls to make, 1 for each step in the workflow. I intend to create 4 factory methods, 1 for each step but am unsure of how to code the DataPortal_Execute() override for the 4 different calls - how would they be distinguished from each other.                                           

Should I be implementing 4 Command objects to do this?  Any help is appreciated.

So far my code looks like this:


<Serializable()> _
Public Class ImportBalanceCmd
   Inherits CommandBase

#Region " Authorization "

   ' See if user is allowed to load Balances
   Public Shared Function CanExecuteObject() As Boolean
      Return Csla.ApplicationContext.User.IsInRole("Administrator")
   End Function

#End Region

#Region " Client Side Code "

   Private mResult As Boolean
   Private mReturnCode As Integer = 0
   Private mReturnMessage As String = String.Empty
   

   Public ReadOnly Property Result() As Boolean
      Get
         Return mResult
      End Get
   End Property

   Public ReadOnly Property ReturnCode() As Integer
      Get
         Return mReturnCode
      End Get
   End Property

   Public ReadOnly Property ReturnMessage() As String
      Get
         Return mReturnMessage
      End Get
   End Property


   Public Sub BeforeServer()
      ' implement code to run on client before server code is called
   End Sub

   Public Sub AfterServer()
      ' implement code to run on client after server code is called
   End Sub

#End Region


#Region " Factory Methods "

   Public Shared Function GetBalances() As Boolean

      Dim cmd As New ImportBalanceCmd
      cmd.BeforeServer()
      cmd = DataPortal.Execute(Of ImportBalanceCmd)(cmd)
      cmd.AfterServer()

   End Function

   Public Sub New()
      'require use of Factory Methods
   End Sub


   Public Sub VerifyBalances()

   End Sub

   Public Sub ReportVerification()

   End Sub

   Public Sub LoadBalances()

   End Sub

#End Region


#Region " Server-Side Code "

   Protected Overrides Sub DataPortal_Execute()

      Using cn As New SqlConnection(Database.DBConnection)
         cn.Open()
         Using cmd As New SqlCommand("usp_GetBalances", cn)
            LocalContext("dpConnection") = cn
            LocalContext("dpCommand") = cmd
            cmd.CommandType = CommandType.StoredProcedure
            cmd.Parameters.AddWithValue("@ReturnCode", mReturnCode).Direction = ParameterDirection.Output
            cmd.Parameters.AddWithValue("@ReturnMessage", mReturnMessage).Direction = ParameterDirection.Output
            cmd.ExecuteNonQuery()
         End Using
      End Using

      mResult = True

   End Sub



#End Region


End Class




















JoeFallon1 replied on Monday, July 07, 2008

A single command object with 4 factory methods seems OK to me.

Just add a new private variable like mMethodName As String and set it in your constructor call from each factory method.

Then you can branch inside DP_Execute based on the method name.

e.g.

Public Shared Function GetSomething(ByVal someParam As String) As MyCommandBO
 
Return DataPortal.Execute(New MyCommandBO("GetSomething", someParam ))
End Function

Private Sub New(ByVal methodName As String, ByVal someParam As String)
  mMethodName= methodName
  mSomeParam = someParam
End Sub

Joe

 

Dawn replied on Tuesday, July 08, 2008

sorry, I'm using c#.

enum WorkflowTypes
{
   GetBalances,
   VerifyBalances,
   ReportBalances,
   LoadBalances
}

private WorkflowTypes workflowType;

// Constructor
private  ImportBalanceCmd(WorkflowTypes workflowType)
{
  this.workflowType = workflowType;
}

// Factory method
public static bool GetBalance()
{
  ImportBalanceCmd command = new ImprotBalanceCmd(WorkflowTypes.GetBalances);
  ......
}

public static bool VerifyBalance()
{
  ImportBalanceCmd command = new ImprotBalanceCmd(WorkflowTypes.VerifyBlances);
  ........
}
  ........
// DataAccess
protectd override void DataPortal_Execute()
{
   switch(workflowType)
   {
        case WorkflowTypes.GetBalances:
            .....
            break;
         case .....


   }
}

Warren replied on Wednesday, July 09, 2008

Thanks for the help Dawn and Joe,

I have been testing out the first process with a Wizard style form which leads the user through each of the 4 processes. Your input comes at a perfect time, I'll carry on implementing the last 3 processes with the single command object.

Cheers

 

Warren replied on Wednesday, July 09, 2008

My command object client side code now looks like this:

   Enum WorkflowTypes
      GetBalances
      VerifyBalances
      ReportBalances
      LoadBalances
   End Enum

   Private mWorkflowType As WorkflowTypes
   Private mReturnCode As Integer
   Private mReturnMessage As String

   Public ReadOnly Property ReturnCode() As Integer
      Get
         Return mReturnCode
      End Get
   End Property

   Public ReadOnly Property ReturnMessage() As String
      Get
         Return mReturnMessage
      End Get
   End Property

   ' Constructor
   Private Sub New(ByVal workflowType As WorkflowTypes)
      Me.mWorkflowType = workflowType
   End Sub

   Public Shared Function GetBalances() As Boolean
      Dim cmd As ImportBalanceCmd = New ImportBalanceCmd(WorkflowTypes.GetBalances)
      cmd = DataPortal.Execute(Of ImportBalanceCmd)(cmd)
      Return cmd.mReturnCode = 0
   End Function

The Data_Portal call to the stored procedure has this code: 

cmd.Parameters.Add("@ReturnCode", SqlDbType.Int).Direction = ParameterDirection.Output
cmd.Parameters.Add("@ReturnMessage", SqlDbType.VarChar, 255).Direction = ParameterDirection.Output
 cmd.ExecuteNonQuery()
 mReturnCode = DirectCast(cmd.Parameters("@ReturnCode").Value, Integer)
 mReturnMessage = DirectCast(cmd.Parameters("@ReturnMessage").Value, String) 

How can I retrieve the value in mReturnMessage if my call to the command object looks like this:

If ImportBalanceCmd.GetBalances Then

Else

 '.Need the return message....

Thanks in advance.

Dawn replied on Thursday, July 10, 2008

You can't get return message like that.

if the mReturn message is the only thing you want from the server, may be you can code you factory method like this:
public static bool GetBalances(out string message)
{
  ImportBalanceCmd cmd = new ImportBalanceCmd(WorkflowTypes.GetBalances)
  cmd = DataPortal.Exectue(cmd);
  message = cmd.ReturnMessage;
  return cmd.ReturnCode == 0;
}
then,
string returnMessage;
if (ImportBalanceCmd.GetBalances(out returnMessage)
{
}
else
{
   //now you get returnMessage
}

but, if you nees other stuff from server, my suggestion is encapsulate them in a class like the command object, then you get every thing.


RockfordLhotka replied on Thursday, July 10, 2008

Warren:

Should I be implementing 4 Command objects to do this?  Any help is appreciated.

Yes, I think so. You have 4 different commands that occur at 4 different times, with user interaction in between them.

I think I'd consider having a fifth object that orchestrates the workflow - because that's really what you are describing. Each time the user re-enters the workflow, this fifth object is the one that would be loaded, and it would invoke the 4 command objects as appropriate.

An even better solution might be to use the Windows Workflow Foundation - hard to say.

Warren replied on Tuesday, July 15, 2008

Hi Rocky,

Due to time constraints, I will not review WWF. I have managed to learn VB.NET, CSLA,  CSLAGen, OOP concepts, Microsoft Enterprise Application Blocks for
Exception Handling and Logging in this one project which I feel good about. I found a VB.Net open source Wizard control which provides a UI centric approach for orchestrating the workflow and allows the user to choose a course of action after each step. For others who may be interested the Wizard can be found at http://www.codeproject.com/KB/vb/GNWizard.aspx.

I think a "purer" OOP design would use 4 seperate command objects, each with a single behavior. On the otherhand, the behaviors are related and there is no use case for using anything but all four 'commands' together.

My lack of expertise in .NET coding and concepts brings up another question:

In order to retrieve both the return code and message from the sproc which is invoked by the command object factory method, I made the message field shared inside the Command Object like this:

Private Shared mReturnMessage As String
  
I get the return code like this:

Public Shared Function GetBalances() As Boolean
      Dim cmd As ImportBalanceCmd = New ImportBalanceCmd(WorkflowTypes.GetBalances)
      cmd = DataPortal.Execute(Of ImportBalanceCmd)(cmd)
      Return cmd.mReturnCode = 0
End Function


Is it considered bad form to use a public shared field which persists the message returned from the sproc last time it was called? This works but I have a feeling there is a better way to do things.

Thanks.

sergeyb replied on Tuesday, July 15, 2008

I must be missing something.  I am not sure why you need a shared member to get two values back from SP.  You should be able to declare two output parameters and retrieve both values in DP_Exec and expose both as public properties.

 

 

 

Sergey Barskiy

Senior Consultant

office: 678.405.0687 | mobile: 404.388.1899

Magenic ®

Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: Warren [mailto:cslanet@lhotka.net]
Sent: Tuesday, July 15, 2008 5:21 PM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] Command Object Implementation

 

Hi Rocky,

Due to time constraints, I will not review WWF. I have managed to learn VB.NET, CSLA,  CSLAGen, OOP concepts, Microsoft Enterprise Application Blocks for
Exception Handling and Logging in this one project which I feel good about. I found a VB.Net open source Wizard control which provides a UI centric approach for orchestrating the workflow and allows the user to choose a course of action after each step. For others who may be interested the Wizard can be found at http://www.codeproject.com/KB/vb/GNWizard.aspx.

I think a "purer" OOP design would use 4 seperate command objects, each with a single behavior. On the otherhand, the behaviors are related and there is no use case for using anything but all four 'commands' together.

My lack of expertise in .NET coding and concepts brings up another question:

In order to retrieve both the return code and message from the sproc which is invoked by the command object factory method, I made the message field shared inside the Command Object like this:

Private Shared mReturnMessage As String
  
I get the return code like this:

Public Shared Function GetBalances() As Boolean
      Dim cmd As ImportBalanceCmd = New ImportBalanceCmd(WorkflowTypes.GetBalances)
      cmd = DataPortal.Execute(Of ImportBalanceCmd)(cmd)
      Return cmd.mReturnCode = 0
End Function


Is it considered bad form to use a public shared field which persists the message returned from the sproc last time it was called? This works but I have a feeling there is a better way to do things.

Thanks.



RockfordLhotka replied on Tuesday, July 15, 2008

Using a Shared field is problematic on a couple levels.

 

First, Shared fields aren’t serialized, so it won’t flow to/from an app server in a 3-tier scenario.

 

Second, Shared fields are not only shared across all object instances, but across the entire appdomain. So again, in a 3-tier scenario where you have a multi-threaded server you’ll get all sorts of odd results because different threads will all be overwriting the same field.

 

Rocky

 

Warren replied on Wednesday, July 16, 2008

Thanks for the advice regarding the use of shared fields and serialization.  I had a feeling it was a bad idea.  I changed the factory method to accept a parameter passed by reference.

Public Shared Function GetBalances(ByRef RetMsg As String) As Boolean
   Dim cmd As ImportBalanceCmd = New ImportBalanceCmd(WorkflowTypes.GetBalances)
   cmd = DataPortal.Execute(Of ImportBalanceCmd)(cmd)
   RetMsg = cmd.mReturnMessage
   Return cmd.mReturnCode = 0
End Function
 

 

 

sergeyb replied on Wednesday, July 16, 2008

Looks good to me. If you want to get rid of byref parameter, you can always wrap return value in a structure:

 

Public Struct BalanceValue

Message as String

Value a Boolean

End

 

Public Shared Function GetBalances() As BalanceValue

……

 

 

 

Sergey Barskiy

Senior Consultant

office: 678.405.0687 | mobile: 404.388.1899

cid:_2_0648EA840648E85C001BBCB886257279
Microsoft Worldwide Partner of the Year | Custom Development Solutions, Technical Innovation

 

From: Warren [mailto:cslanet@lhotka.net]
Sent: Wednesday, July 16, 2008 11:52 AM
To: Sergey Barskiy
Subject: Re: [CSLA .NET] RE: Command Object Implementation

 

Thanks for the advice regarding the use of shared fields and serialization.  I had a feeling it was a bad idea.  I changed the factory method to accept a parameter passed by reference.

Public Shared Function GetBalances(ByRef RetMsg As String) As Boolean
   Dim cmd As ImportBalanceCmd = New ImportBalanceCmd(WorkflowTypes.GetBalances)
   cmd = DataPortal.Execute(Of ImportBalanceCmd)(cmd)
   RetMsg = cmd.mReturnMessage
   Return cmd.mReturnCode = 0
End Function
 

 

 



Warren replied on Friday, July 18, 2008

Thanks for the idea re use of a structure Sergey,

I would appreciate any advice regarding the use of transaction processing in sprocs vs using  <Transactional(TransactionalTypes.TransactionScope)>  in my command object. So far I have chosen to use TP in the SQL Server sproc. It works nicely in my TRY/CATCH Block where I can issue a rollback if need be and pass back the error code back to the command object. The UI can then decide what to do with the error.

Are there drawbacks having to having TP in the stored procedure rather than the Command Object I should be aware of?

 

JoeFallon1 replied on Monday, July 21, 2008

Are there drawbacks having to having TP in the stored procedure rather than the Command Object I should be aware of?

Not really. As long as the command object is the only thing you are trying to do at that time.

When you have multiple BOs and you want them all to save to the DB as part of a transaction then the DB transaction model is a poor choice. In this case it is far better to start the tr in the BO layer and pass it around for all BOs to use.

So for consistency sake alone, I would remove the DB tr code - so you do not get confused about when to use which model in the future.

Joe

Warren replied on Monday, July 21, 2008

Thanks Joe.

This wizard function required command objects only. For consistency and since I need another wizard function that will use both command objects and regular business objects I should do TP in code instead of the sproc. 

The code method is much more "black boxed" however.  Can I assume that any SQL Errors that occur in the sproc will rollback the transaction allowing me to continue to return the cause of the error via my sproc so I can handle it in my UI?

 

JoeFallon1 replied on Tuesday, July 22, 2008

Can I assume that any SQL Errors that occur in the sproc will rollback the transaction allowing me to continue to return the cause of the error via my sproc so I can handle it in my UI?

When the tr is in the BO layer and you make a call in ADO.Net to the database, if there is a problem with the query then you will get an exception. You can trap this exception and then evaluate it and re-throw it if necessary.

For example, I get back "ambiguous column name" errors if a query has a JOIN and the same column name is in both tables but I fail to prefix it with the table name. Similarly, if a trigger fires and fails, I get back the error message in the trigger code that I wrote. So I am pretty sure you would get back whatever error you raised in your SP (but not 100% positive.)

Let us know after you try it <g>.

Joe

Copyright (c) Marimer LLC