Vibrant discussion about CSLA .NET and using the framework to build great business applications.
I'm new to CSLA, but like it very much so far. I'm using the 3.8.3 version.
We've just recently ran into a scenario where I can't figure out how to utilize CSLA to get the desired result.
The issue is we have a database table where the potential exists for a user to attempt to enter a duplicate value. We have added a constraint to the table on the SQL side to prevent this, but the attempt will also create a DataPortalException error, which is expected. The problem is that this will occur inside a loop where there will be many items being saved to the DB, and if any single item encounters this attmpted-duplication error, we want to catch the error, but continue the save operation for the rest of the items in the collection for which they are not duplicates.
However, what I'm seeing is that if one item in the collection produces the duplication error, then none of the others will save either. I am trying to catch the error in our BO classes in various places (on the Form where the initial button click calls the BO Update method, inside the root BO where the Save is called, and inside the actual BO where the DB call is made), however in all these cases, if one item cannot be saved, none of them are.
So, is there a way that CSLA will support catching an error (from a looped-collection save) but continue the save operation for all valid objects.
It sounds like this loop is running on the client? That's horribly inefficient, and makes solving your issue difficult as well.
You are probably better off creating a unit of work object that saves all the objects in a single data portal call. This way the loop is in the unit of work object's DataPortal_Execute method on the server where you can use a simple try..catch block to catch the database exception and yet continue to save the rest of the items in the collection.
Thanks for the response. I'm off work now and won't be back till Tuesday, but I needed to reply to this at least.
The loop is not running on a client, but in BO and is called on the DataPortal_Update method.
To be more specific, this is a rather complex BO that is 4 levels deep (don't ask, that's just how is has to be). Because of some limitations to how the DB was set up, the Parent object undergoes some filtering of the grandchildren to determine which ones need to be updated (this is passed from the client via data-bound filter controls, etc.). What happens is that once a Save is executed from the client, calling the Parent DP_Update method, this is where the filtered collection is attempted to loop through.
From talking with a co-worker, and from your initial response, we are thinking now that part of the issue may be precisely that we are calling a single data portal call and that if any of the objects fail in their update attempt, then the ALL the associated grandchildren objects are rolled back as well.
I do have Try-Catch blocks implemented. This is one area of confusion. If I attempt to catch the error at the Client level, or in the Root object's DP_Update call, or even at the level of each individual grandchild object's DP_Update, then I can catch the error, and does proceed through the loop, however, NONE of the other items, that do not have errors, get saved. Is there a preferred place to use the Try-Catch blocks (Client, Parent's Update method, or in the Grandchild's update - but if we place one here, we've found that the CLSA Data Portal throws it's own unhandled exception - we seem to have to do it at least the Parent object's level).
I'm confused by your use of "DataPortal_Execute" method - we are using the Update, Insert, Fetch methods, but I haven't seen an "Execute" one. Should I be using this one?
As I said, I won't be able to pick this up until next Tuesday, but feel free to add any additional suggestions and I'll try to get back next week.
You can do this but you do not get much builtin help from CSLA
IE: You should not use the builtin transactional support - you are on your own here. CSLA builtin transactional support will rollback if an unhandled exception occured (that is an Exception reached the DataPortal).
So - you are on your own and should [Transaction(TransactionalTypes.Manual)] attribute on the "root" save methods.
I would however argue that you should not allow an exception back to the user as the save operation did not fail. I would rather consider this as a "Success with message" to the user and rather implement this in the actual "root" object.
You should add your own exception handling in the data access code and could set a "Message" in the ApplicationContext and move that content into a property on your "root" object. The message could be generic or you should have this on a per-object type so that it could be visualized to the user after the save operation.
Jonny Bekkum, Norway
Or you can keep using automatic [Transaction] support, but then you must not allow the exception to flow back to the client.
That is what causes the rollback - if your DataPortal_XYZ method throws an exception (or allows an exception to flow up) then the data portal rolls back the transaction.
So Jonny is right - you can do manual transactions and allow the exception to flow up. Or you can continue to use automatic transactions, but then you can't allow the exception to flow up.
Thanks, your solution is what I'd prefer, where is the best place to catch the error - i.e., which DataPortal_XYZ method - the parent's, the ultimate Grandchild performing the safe, or somewhere deeper (inside the CSLA, etc.)? We do have extension methods that lie behind the DataPortal_ calls, and these do have exception handling, but a rollback still occurs.
Really you have to catch the exception in the loop so you can finish the loop right?
In terms of returning the errors, you need to have your own way to do that. I recommend doing this save in a unit or work object based on CommandBase, so you can easily return a collection of errors along with the root business object.
I do need to return something to the client as there are interactions required to be dealt with, so if there is an error, I can reset things or tell the user.
I guess I'll have to export the CommandBase idea or the Manual method.
Is there not a way to break up the "root" Execute command into separate commands for the actual grandchild object I'm trying to save? All this seems rather laborious just to save a collection, when one item in the collection will cancel all the others.
I'm sorry, it may be a few weeks before I get back to a resolve this, but I won't forget and will update it and pick an answer, etc.
Thanks for your help and patience.