Implementing Multi-Operation Functions

Implementing Multi-Operation Functions

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


dtabako posted on Tuesday, July 29, 2008

I believe I may have brought up this topic previously but I don't think I got a response I could come to grips with. What I'm looking for is a way to implement a "command" (for lack of a better term) that performs multiple operations over a list of data items. For example, I have a sales order post function that needs to do something like the following:

For each order in the table:

1. Post the order total to the customer's balance

2. Post total sales and cost figures to a (optional) subaccount table

3. Post unit, sales, and cost figures back to each individual inventory item on the various order detail lines

4. Create a record in an inventory ledger table for each individual order detail line

5. Move the order and all detail lines into a history table (or possibly change the status of the order within the table to denote it has been posted and is now part of history)

There are more things that need to be done for each order, but you get the gist. Logically, I can envision having a ReadOnlyList of, say, OrdersToPost objects which would each have a child collection of OrderLinesToPost. The OrdersToPost objects would have a series of private embedded CommandObjects that relate to the various stages of the post process and one public "PostMe" CommandObject to kick the process off for that object. There would then be a master CommandObject for the post that would be responsible for retrieving the list of OrdersToPost objects and cycling through that to call "PostMe" on each. Is that the "right" or "best practices" way to go? I envision a lot of database hits this way and a possible performance nightmare. There can certainly be thousands of orders in the table and many could be quite large in terms of number of detail lines.

Another idea I had is that this type of scenario seems perfect for a WorkFlow. But I know very little about WF - I would basically have to learn it. And I would still have the same performance concerns. Is it worth learning WF to solve this issue? Is ther another way that I am not considering?

Thanks, Dennis

RockfordLhotka replied on Tuesday, July 29, 2008

Does any of this need to be done on the client? It doesn't sound like it. It sounds pure server-side.

My first recommendation is to try and do the whole thing in a stored procedure. You are describing a series of set operations, and that's what SQL is good at. And you'd avoid moving the data out of the database process at all, which is a good thing.

But if you can't stomach doing it in SQL (and I don't blame you) then I'd try to do it all on the app server. No sense bringing anything back to the client except a success/fail response.

To do that, I'd create a Command object so the client can start the process - by executing the command. This Command object would pop over to the app server, and would probably start the transaction (Transactional attriubte on DataPortal_Execute()).

Inside DataPortal_Execute() you'd do your work. If you want to load up a ROLB and loop through the items, or use a DataSet, or use a raw DataReader - that's really implementation details that are up to you. The point is that all the work is done on the app server, and in one batch operation.

SonOfPirate replied on Tuesday, July 29, 2008

This is the exact same situation I am in (see my post http://forums.lhotka.net/forums/thread/25054.aspx).  Obviously, my goal was to go with WF to handle orchestrating the multiple steps.  I too am new to WF and am looking for insights into implementing with CSLA.

In my case, I need flexibility to change the process as the customer's needs and deployment environment change.  If your business logic is fixed, then I would say that WF is an unecessary overhead UNLESS it is a long-running process that you can run in the background.

I wouldn't go with the separate Command object approach you described.  But putting all of the logic in a stored procedure sets you up for the age ol' debate about where your logic belongs.  However, with fixed logic and all data stored in a single database, that seems to be the simplest solution.

On the other hand, is the "posting" the same a "saving" the order?  If that is the case, then most of the framework to support what you want to do is present in the business objects and you just need to put some extra logic in your stored procedureS to store the values in the right places.  Your root OrderToPost object would initiate the process and store its data then iterate through each of the line items telling them to persist themselves.  This is baked into CSLA.

If posting is a separate operation from saving (i.e. perhaps you can save a draft version of the order), then you can simply duplicate the same logic that is used to save the objects when the Post() method is called on your root object.  You can have a PostItem() method on your OrderLineToPost child objects that handles the individual line items.

I'll keep watching in case you choose to go the WF route since that is the approach I need because of flexibility.  Let us know how you fare...

HTH!

 

JoeFallon1 replied on Wednesday, July 30, 2008

I have a large web app that deals with Orders.

But I also create small WinForms apps that handle a specific "task" like connect to the DB and loop over the message table and send out an email per row. There is only a single form and it is actually invisible so there is no UI to speak of. The client schedules each task according to their needs.

e.g. Run email task every 10 minutes. Run export task once a day. etc.

So you could create a task to do your posting. And it looks like it doesn't process "each order in the table" - it only has to process new orders of a certain status that have not been posted yet.

So you would have a root BO that fetched the list of Orders that need to be posted and then call Save (or Execute) on it. Once it is in the DataPortal you would use ADO.Net commands to process each order using the steps you outlined. You could wrap a transaction around each order (not the whole list) and then trap for any excpetions as each order is processed, if one fails, you can log it and send an email to an Admin and then continue processing.

If the task is scheduled hourly then the list of orders to process should be relatively small for each run (once the initial run has been accomplished.)

Joe

 

 

dtabako replied on Wednesday, July 30, 2008

To try to respond to everything at once ;^)   :

Rocky: You're right. This is definitely something that can and should be done entirely on the server (aside from the user entering some initial data and kicking off the process which is something I've been able to do quite nicely with a root object criteria class/command base executable class combo for simpler functions already). I considered the SP route, but as Joe mentions there's a bit of discomfort for me putting so much business logic into the SP. However, I see from a performance standpoint how that would certainly be the best option. I assume by using the Transactional attribute, then the entire process would succeed or fail as a complete unit? That works OK, but what if I go one level deeper with the transaction? Say each of the objects in my ROLB has its own "PostMe" command that would do all the work of posting one order within its command. Then I could put the transactional attribute at that level and even if the overall process ends up failing somewhere down the line, at least the orders up to that point would get posted. What I would definitely want to avoid is having any one order end up partially posted.

HTH and Joe: Saving an order and posting an order are different operations. Saving an order comes with its own set of tasks: e.g. updating committed and on-hand quantities in inventory. But the basic procedure goes like this: the user enters, saves, and verifies orders all day. At the end of the day, a batch "posting" is run to update figures in customer accounts, G/L accounts, etc. and move all appropriate orders into the history table (or status).

What about using Linq to SQL in my DataPortal_Execute? I'm also kind of a novice with Linq but I need to learn it at some point anyway. Now is as good a time as any. Could I use that against my ROLB as a compromise between putting business logic in an SP vs. manually cycling through a potentially long list and creating thousands of database hits? Of course, then I would need to put the transactional attribute at the main command level again, too.

Thanks for your help, Dennis

Copyright (c) Marimer LLC