N-Level Undo Issue / Edit Level Mismatch in AcceptChanges

N-Level Undo Issue / Edit Level Mismatch in AcceptChanges

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


JohnB posted on Monday, October 08, 2007

I've got issues with 3.0.2 regarding saving/edit levels etc. First let me say that it was working with 3.0.1. Here is what I have:
 
Notes:
-- myBusObject (of type BusinessBase)
-- UnbindBindingSource code is exactly the same as Rocky's in 3.0.1/2
-- BindUI is exactly the same as Rocky's in 3.0.1/2
 
Private Sub RebindUI(ByVal saveObject As Boolean, ByVal rebindCtrls As Boolean)
     '-- Disable events
    Me.bsParentObject.RaiseListChangedEvents = False
    Me.bsChildObject1.RaiseListChangedEvents = False
    Me.bsChildObject2.RaiseListChangedEvents = False
 
    Try
        '-- Unbind the UI
        UnbindBindingSource(Me.bsChildObject1, saveObject, False)
        UnbindBindingSource(Me.bsChildObject2, saveObject, False)
        UnbindBindingSource(Me.bsParentObject, saveObject, True)
 
        '-- Save or cancel changes
        If saveObject Then
            myBusObject.ApplyEdit()
 
            Try
                Dim tempBO As BO = myBusObject.Clone
                myBusObject = tempBO.Save
 
            Catch ex As Csla.DataPortalException
              ...
            Catch ex As Exception
              ...             
            End Try
        Else
            myBusObject.CancelEdit()
        End If
 
        '-- Rebind the UI if requested
        If rebindCtrls Then
            BindUI
        End If
 
    Finally
        '-- Restore events
        Me.bsParentObject.RaiseListChangedEvents = True
        Me.bsChildObject1.RaiseListChangedEvents = True
        Me.bsChildObject2.RaiseListChangedEvents = True
 
        If rebindCtrls Then
            '-- Refresh the UI if rebinding
            Me.bsParentObject.ResetBindings(False)
            Me.bsChildObject1.ResetBindings(False)
            Me.bsChildObject2.ResetBindings(False)
        End If
    End Try
 
End Sub
 

I get a errors when I cancel or save.
 
Cancel
According to Rocky in several post, "Never interact with the business object directly while it is data bound. Instead, always interact with the binding source object." Well I know that by the time I call CancelEdit, my BO is no longer data bound but it still throws the exception if I call myBusObject.CancelEdit(). If I change it to Me.bsParentObject.CancelEdit(), it works.
 
Save
I am not making any changes to any of the children, just data on the parent. I consistently blow up on the myBusObject.ApplyEdit(). The code breaks in 'BusinessListBase.vb -> AcceptChanges method'.

The error message says 'Edit Level Mismatch in AcceptChanges', the message and stack trace says:
"The generic type 'Csla.BusinessListBase`2' was used with the wrong number of generic arguments in assembly 'Csla, Version=3.0.2.0, Culture=neutral, PublicKeyToken=93be5fdc093e4c30'."
 
Any ideas? Again, the code you see above worked with 3.0.1. I have a grid which is filled with a BLB of BB objects. The user can click a row and choose Edit. On edit I do the following:
myBO = selectedItem (this is a BB object)
BindUI()
 
Does the fact that I am using a BLB instead of a ROLB matter?

ajj3085 replied on Tuesday, October 09, 2007

Search the forum, this has been discussed before.  The short answer is that you need to look at teh PTracker sample to see how to unbind and accept changes before saving.  You MUST follow that pattern exactly.

It doesn't matter that you're code worked in 3.0.1; there was a bug in that version regarding undo.

JohnB replied on Tuesday, October 09, 2007

ajj3085,

I agree but if you read my post you will see that I am doing exactly as Rocky suggest. The only difference between my code and the PTWin code is resetting the binding source for any child objects. My issue was also with Saving not just Cancel.

After reworking some stuff this AM I am now wondering if I am using the BLB correctly. Are there any best practices for working with BB objects in a BLB?

Thanks,
John

ajj3085 replied on Tuesday, October 09, 2007

Ahh, sorry.

I think this though is your problem:
The generic type 'Csla.BusinessListBase`2' was used with the wrong number of generic arguments

I'm not quite sure what would lead to that problem, but I'm on 3.0.2 now and am not getting this problem.

JoeFallon1 replied on Tuesday, October 09, 2007

ajj3085:
Ahh, sorry.

I think this though is your problem:
The generic type 'Csla.BusinessListBase`2' was used with the wrong number of generic arguments

I'm not quite sure what would lead to that problem, but I'm on 3.0.2 now and am not getting this problem.

I have seen the argument error in a couple of places in my code and also in the Watch window.

Here is what I have learned about it.

A lot of code assumes a collection has a single Item property with a single parameter of index As Integer. For example in a reflection helper class I saw this code:

Member = Type.GetMember(propertyNoIndex, MemberAccess)(0)

GetMember returns an array of MemberInfo objects and the (0) at the end says to use the first one.

If you overload the Item method in CSLA 1.x then it is probable that your code looks something like this:

Item(ByVal index As Integer)
Item(ByVal code As String)

In this case, the reflection code above sees index As Integer as the 0 element and code As String as the 1 element.

Moving to CSLA 2.x - Rocky moved the Item method into his Base class so you do not have to write it (or code gen ) it in your collection classes anymore.

BUT - this changed the way that reflection looks at the array of MemberInfo objects!

It seems that relfection code "sees" the Item method and its overloads in the order of the most concrete class and working up the inheritance chain to the base classes.

So for CSLA 2.x, the reflection code above sees index As Integer as the 1 element and code As String as the 0 element.

To make it even more confusing I added a 3rd overload in my base class which derives from CSLA2 and it pushed the index As Integer to the 2 element and my other overload became the 1 element.

e.g.
Item(ByVal code As String)                                                        0 element
Item(ByVal index As Integer, someParam As String)                   1 element
Item(ByVal index As Integer)                                                      2 element

I had a very similar issue with the ObjectListView. The same error was returned because the code used to identify the Item property did not anticipate overloads or moving the index As Integer method into a Base class.

I now use code like this to specifically identify WHICH overload of Item I want to use:

Dim itemProp As PropertyInfo = Parent.GetType().GetProperty("Item", New Type() {GetType(Integer)})

This code always grabs the correct version of Item that uses index As Integer.

Joe


 

JohnB replied on Tuesday, October 09, 2007

Joe,

Have you subclassed the framework? I did and was wondering if this was the problem although I just tried again going directly to the framework and received the same error.

FWIW, this is what I have in my class in order to subsclass BLB. I believe that this is correct.

Namespace MyNameHere

Public MustInherit Class BusinessListBase(Of T As BusinessListBase(Of T, C), C As {Csla.BusinessBase(Of C)})
    Inherits Csla.BusinessListBase(Of T, C)

End Namespace

Thanks,
John

JoeFallon1 replied on Tuesday, October 09, 2007

Yes. I sub-classed the framework. It is highly recommended and very valuable to have.

This is what I have:

Public MustInherit Class MyBusinessListBase(Of T As MyBusinessListBase(Of T, C), C As MyBusinessBase(Of C))
  Inherits BusinessListBase(Of T, C)

I am not sure why you have curly braces on your 2nd parameter.

Also, you did not re-name your sub classes - you used the same name as Rocky. I added "My" in front of my sample names. (I really used company initials though).

Joe

 

JohnB replied on Tuesday, October 09, 2007

The curly braces come from other examples I've seen of people subclassing the framework. Not being an expert on CSLA yet I wanted to make sure that I was doing what others were doing who had success.

As for the naming, I control access to my objects via namespaces. I tend to like it better this way. So far no issues.

John

JohnB replied on Wednesday, October 10, 2007

ok, so I solved my own problem. It appears that my issues were because I was using a BLB as a root for my BO's. I did not think this would be an issue and maybe it's not but if someone has any ideas on maybe what I may being doing incorrectly please let me know.

Original:
BLB filled with BB BO's. - Did not work at all in 3.0.2 but it did in 3.0.1

Solution:
ROLB filled with BB BO's - Works like a charm!

Again, any ideas on why my first solution would not work? I read through Rocky's book again to see if I missed anything but I've got nothing.....

John

ajj3085 replied on Wednesday, October 10, 2007

Your original solution would be fine, as long as your BO's contined in the list were calling MarkAsChild in their constructor.  Then you would ONLY call save on the BLB, never on the BB objects.

It sounds like you used BLB to contain ROOT business objects, which is not supported.   ROLB should NOT contain BB objects either; if you NEED a list of root objects, you should use ERLB.

In this case, root vs. child indicates 1) the transaction scope; a root object is the root of the transaction, while its child object updtes WILL take place within the transaction and 2)  the child objects CANNOT be saved independantly; it only makes sense to save them at the same time as the root.

I would check out the book again if that's not clear.

HTH
Andy

JohnB replied on Wednesday, October 10, 2007

I actually had a typo. The solution is a ROLB of ROB objs. When the user selects an item I retrieve a BB obj. As for the BLB, your explanation would expain my problem.

Thanks for your help.

ajj3085 replied on Wednesday, October 10, 2007

Ahh, ok.  That makes sense then, although I'm suprised the compiler didn't point out the problem immedately.

JohnB replied on Wednesday, October 10, 2007

Not to prolong this any further but what would you expect the compiler to "say"?

Also I wanted to jump back to the subject of ROLB. I just copied this from Rocky's book:

ReadOnlyListBase Class
Like the ReadOnlyBase class, ReadOnlyListBase is quite simple to create. It is designed to make it
easy for a business developer to create a business collection that doesn’t allow items to be added
or removed. Presumably, it will be used to contain read-only child objects, but any type of child object is allowed.

So is it considered to be a best practice to NOT to stuff anything but a ROB obj in a ROLB?

ajj3085 replied on Wednesday, October 10, 2007

Oh, you're right.  I thought it was like BLB, where it required that objects contained in the list would inherit from ROB.  My mistake.

Copyright (c) Marimer LLC