Extending the DataPortal

Extending the DataPortal

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


pgroenenstein posted on Thursday, September 03, 2009

HI Everybody

I'm finally moving old software based on VB6 CSLA over to the new .Net.  But i need a quick pointer in the right direction (time is against me to figure it out).

What I need is as follows:

I have an invoice object with a collection of children representing items on the invoice.
As the user works with the invoice he has the ability to save as he carries on putting items on the invoice (i.e. Saving as he feels like it. Sometimes it might take days to actually get the invoice ready for processing, which means he would need the ability to save it, reload or edit it) Now of course there is no problem here at all the framework works EXCELLENT with all the validation and the DataPortal "FETCH", "INSERT" and "UPDATE" functionality.

We keep the "Temp" invoices in different tables and once the "Finalize" button is hit we need to save it to the permanent tables in the DB. Most of the time a invoice would not even have been saved to the DB, but would go to "finalize" state without ever been saved in the "Temp" tables.

However i have ONE problem if you can point me in the right direction. Once the invoice is ready to be processed the user hits the button and we call a "Finalize" method on the invoice object to "book" the invoice object which also must of course "book" the items too. Much like the "Insert" option in the Dataportal. But all the items MUST be booked REGARDLESS of  the "Isnew", or "IsEdited" flags, the only prerequisite being that it should be valid for booking purposes.

As the "Insert" and "Update" methods is already used for normal saving and retrieving we can't "misuse" them for this purpose since we need that functionality along with the "Finalize" functionality.
So we need an EXTRA function from the DataPortal to be able to do this. I tried to use "Execute" but I'm having a hard time to figure out how to use it for that, it would seem that is is only suited for limited data functionality and not really the way to go if I need  to "execute" on the child objects too.

Is it possible to add to the DataPortal that functionality without changing the framework, i.e. from the buss obj's side?

What I need is a "DataPortal_Finalize" option that would work pretty much like the "SAVE" option with SOME changes to how it works e.g. Ignoring "Edit" and "Dirty" states and committing all objects to the DB.

Not sure if I have expressed myself clear enough on my need

rsbaker0 replied on Thursday, September 03, 2009

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.

pgroenenstein replied on Thursday, September 03, 2009

Thanks for the reply

I'm on a crash-course to learn CSLA.Net in a couple of days and don't really have all the concepts of CSLA.NET figured yet.

Can you please explain a bit more in detail as to what you mean. How would you caal the "Childs" Finalize subs then?

JoeFallon1 replied on Thursday, September 03, 2009

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 replied on Thursday, September 03, 2009

Joe

Thanks for your response. It seems I would be taking the path you suggested.

You also said:
"Notice that you "bypassed" calling the DataPortal methods of the Invoice and its child objects."

Is that good, bad or not a issue at all?
(sorry for asking dumb question but I'm trying to get up to speed with CSLA.NET like in 5 days.

Phillip

pgroenenstein replied on Thursday, September 03, 2009

Joe

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

rsbaker0 replied on Thursday, September 03, 2009

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 replied on Thursday, September 03, 2009

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

rsbaker0 replied on Thursday, September 03, 2009

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)

pgroenenstein replied on Sunday, September 06, 2009

Joe

I quote:
"Since it contains the Invoice object it has access to the header rows and the line rows."

How exactly do you get to the Parent  object (Invoice),  Collection object and the child objects from within the "Command Object (which is a nested class in the invoice object)"? 

I am not sure how to reference these object from within the command object.





JoeFallon1 replied on Sunday, September 06, 2009

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

 

pgroenenstein replied on Sunday, September 06, 2009

Joe

I was thinking of wrapping it all in Controller Object, but was not sure if that would be a good design when working with CSLA Now I understand your posting better, and it seems to me the "better" way to go. Thanks for helping a newbie like me out.

Thanks

rsbaker0 replied on Sunday, September 06, 2009

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

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

RockfordLhotka replied on Thursday, September 03, 2009

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.

pgroenenstein replied on Thursday, September 03, 2009

Rocky

It is all one use case, I did considered using a "Finalize" flag. But using the "Execute" method somehow appeals to me to.

What would your suggestion be in choosing between the two? Given Joe's answer would that not be better as it seems you would have more control over the code.


ajj3085 replied on Friday, September 04, 2009

I deal with invoices as well, but my requirements are different. In my case, invoices are always stored in the same table, and whether they are "finalized" or not is part of the state of the invoice, so its also a database column (in my case they Close invoices, which also triggers them to be exported to our accounting software). But even with two tables, it seems like this is the simplier approach, and the BO can just check the flag to save to the proper spot.

A command object is more appropriate if you have a requirement like I do.. where you also have quotes and orders, and a quote can be converted to an order which can be converted to an invoice. The command "knows" how to convert from one kind of document to another; the document isn't at all responsible for that (although you can ask it to get a Converter instance to use).

I think the button location is a bit misleading, since my convert button is also on a toolbar, but I'm not sure converting is the same use case as the normal editing of an invoice.

robert_m replied on Thursday, September 03, 2009

Seems to me that all you need is a "final" flag in your Invoice object so the DataPortal_Insert and DataPortal_Update methods can decide what to do with it, ie. in which set of tables to save it.
So if the invoice is not yet finalized it gets saved into temp tables, and if it's finalized it gets saved into permanent tables.
As for the IsNew and IsDirty properties, it should be no problem either. You can always force either one of them to become true via MarkNew() & MarkDirty() methods if you have the need to do so...

pgroenenstein replied on Thursday, September 03, 2009

Robert

Thanks for your reply.

My question is this if one of the lines on the invoice was loaded after it was save and no editing happened on that line wouldn't the status of the flags (i.e. Isnew = False, IsDirty = False) result in one or more lines not being "booked" once the finalized flag is set?

I could force the flags to a state that would "Force" updating to the DB, but "Forcing" a flag cannot possible be a good thing. Consider after such a "force" the DB (I suse ADO.Net with Stored Procedures) might for some or other reason reject the update (e.g. not enough stock quantities on hand to sell) and then, if I'm right, your buss obj would be in a "Confused" state.


Copyright (c) Marimer LLC