There is no need to modify the data portal to do this (and several possible solutions).
I'd suggest that you just implement a CommandBase derived class (FinalizeInvoiceCommand) to do the finalizing.
One of class' members would be the invoice. Its DataPortal_Execute method can perform an arbitrary data transformation on your invoice on the server side of the data portal.
I use this technique in several places to implement alternative methods of "saving" a BO that already has a primary DataPortal_Update implementation.
When you use a Command object you call DataPortal Execute and once the BO is "on the server" you can do whatever you want to with it.
Since it contains the Invoice object it has access to the header rows and the line rows.
You can start a transaction and then directly call ExecuteNonQuery to perform the Insert of all the header data. The loop over the child lines and directly Insert each of them too. (Obviously, the data in each BO are the input parameters to your SP or SQL).
Then call transaction.commit and you are done.
Notice that you "bypassed" calling the DataPortal methods of the Invoice and its child objects.
Joe
pgroenenstein:
Can you just give me a quick pointer on how best to use the "Command object". Would that be a private class in the the "invoice object" and if so what is the preferred way to access the invoice "parent" object from there then?
Thanks
Phillip
Yes, a nested class inside your Invoice class would work fine.
You can extend your Invoice with an additional Finalize() method that internally constructs your nested FinalizeCommand() -- perhaps using itself as an argument to the constructor so the command will have access to it, and then invokes the Execute method on the command.
pgroenenstein:
Is it possible to post some sample of what this "Nested" commandbase class should look like. It would go a great deal towards understanding what is to be done.
Phillip
Here is a generic class I have written for this purpose. It has some things you don't need (the database key), but basically it can have generic argument of any type (e.g. your Invoice), and then it calls a virtual Execute() method -- typically all the derived class need implement -- when executed via the Data Portal. (We do our own transaction management, which is what the TransactionContext is, so the point of this class is to centralize the ability to pass an argument to a command that gets executed inside a database transaction)
using System;
namespace Csla.WORMapper
{
///
/// Execute a command with a generic argument in a database transaction, automatically enlisting in
/// current one if active
///
/// Usually, derived classes need only override the Execute(TransactionContext context) method
///
/// A class that inherits from .
/// An argument of any type.
[Serializable()]
public class TransactionalCommand<T, V> : CommandBase where T : TransactionalCommand<T, V> {
protected V _arg;
protected string _databaseKey;
protected TransactionalCommand()
: this("Base")
{
}
protected TransactionalCommand(string databaseKey)
{
_databaseKey = databaseKey;
}
public static V Execute(V arg )
{
return Execute(arg, "Base");
}
bool _autoBeginTrans = true;
public virtual bool AutoBeginTrans
{
get { return _autoBeginTrans; }
set { _autoBeginTrans = value; }
}
public V Argument
{
get { return _arg; }
}
public static V Execute(V arg, string databaseKey)
{
// Must retrieve via data portal
TransactionalCommand command = Activator.CreateInstance();
command._arg = arg;
command._databaseKey = databaseKey;
return DataPortal.Execute>(command)._arg;
}
protected override void DataPortal_Execute()
{
TransactionContext.ExecuteInTransaction(_databaseKey, Execute, AutoBeginTrans);
}
protected virtual void Execute(TransactionContext context)
{
throw new System.NotImplementedException("Method Execute() must be implemented in derived class");
}
}
}
Here is a typical instance of the command (it takes a PO and just increments the print count, possibly changing the status). You'll see why I went to the trouble to implement the generic class -- now I can implement Commands that basically are just the single Execute method that does all the work...
[Serializable]class
POIncrementCommand : Csla.WORMapper.TransactionalCommand<POIncrementCommand, PO> { protected override void Execute(Csla.WORMapper.TransactionContext context) { _arg.PrintCount = (short)(_arg.PrintCount.GetValueOrDefault(0) + 1); // Move status from APPROVED to ORDERED when printed if (_arg.IsPOStatus(POStatuses.Approved) && GlobalSettingsCache.Get("Purchasing", "SetPOToOrderedWhenPrinted", true)) // Took out the check for first print 4/9/03 for Version 6.2 (Jim) // APo.m_PrintCount == 1 && { SetStatus(context, _arg, POStatuses.Ordered, 0); } _arg.Update(context); } }
Invocation is trivial. Here is a static method that takes a PO and invokes the command...
static public void IncrementPrintCount(PO aPO)
{
POIncrementCommand.Execute(aPO);
}
(Sorry about the cut/paste -- I'm not sure how to best format code for the forum -- it was omitting the generic parameters for some reason)
You have gotten a lot of advice in this thread and you have mixed some of it together.
I do not have a Command Object as a nested class inside my invoice object.
I have a regular Root BO which I call a Use Case Controller (or Unit Of Work). This root BO contains other BOs (including the Invoice BO which has its collection of lines.)
So when I am inside the UOW I can just do:
Me.mInvoice.mLines.Item(0).Price
Joe
I don't have my source code in front of me, but if memory serves both BusinessBase and BusinessListBase implement IChild, which can be used to walk up the ancestor tree from a CSLA object. If the command object contains a reference to a BO (BusinessBase or BusinessListBase), you should be be able to walk up the object heirarchy.
Here is a thread on this...
It seems to me like there are a couple primary options to consider.
If this is all one use case, so the user is on a form and at the bottom of the form are two buttons: "Save draft" and "Finalize", that's one thing.
If this is two use cases: edit invoice data and finalize invoice, that's another thing.
Assuming a single use case, I'd consider just putting a Finalize() method on the root class:
public Invoice Finalize()
{
IsFinalized = true;
return this.Save();
}
Your data access code can use IsFinalized to decide whether to write to the temp tables or the real tables. And if IsNew is false, then it knows to blank the temp table data.
If this is two use cases then the second use case (finalize) is probably best done with a command object - but it sounds like this is probably not the case.
Copyright (c) Marimer LLC