Need help with design ReadOnlyListBase (Insert, Update, Delete from ReadOnlyListBase)

Need help with design ReadOnlyListBase (Insert, Update, Delete from ReadOnlyListBase)

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


VoodooSV posted on Wednesday, July 11, 2007

Hi!
At first, thanks a lot Rocky for great job with CSLA!
I'm novice and maybe ask a dummy question :-), but...

My task is:
For select client, I have a modal form (frmSelectClient) with grid. Datasourse for grid is root readonly list of Clients (ReadOnlyListBase). If user can't find client in the list, he would like to add new. I create and show a modal form (frmEditClient) with editable root (BusinessBase) for add a new Client to DB. This frmEditClient can reuse also to edit existing Client.

My question:
What is the best solution to update readonly clients list on frmSelectClient, that a user can see their newly inserted clients or updated clients info. Now I make "fullrefresh" data in root readonly list of Clients, but think that is a bad approach :-(

Tnaks a lot for help!
With best regards,
Sergey.

stanc replied on Thursday, August 02, 2007

Sergey,

I'm new to CSLA as well, but have recently looked at similar problem. I figured I'd share my approach with you and other can comment on it.

In my ROL, I filled it with editable BusinessBase objects. I use my ROL to populate a listbox. Users can then select an item in the listbox and I have other fields bound to the BusinessBase object selected. The two problems I had with this were 1.)Adding new BusinessBase objects to the ROL and 2.)Making sure my ROL was updated after a save of the BusinessBase object.

To overcome both of these I add public functions to my ROL object. This is where I would like some feedback from others who have attempted something like this before. Below are some code snippets to help explain what I did.

UI

myCommodity = _commodityEditableList.SaveCommodity(_commodityEditableList.IndexOf(myCommodity))

 

If rebindCtrls Then

   Me.CommodityEditableListBindingSource.DataSource = Nothing

   Me.CommodityEditableListBindingSource.DataSource = _commodityEditableList

End If

_commodityEditableList is my ROL object (I know bad name it really is ReadOnly)

ROL

    Public Function SaveCommodity(ByVal Index As Int32) As Commodity

 

        Dim o As Commodity = Me(Index)

 

        IsReadOnly = False

        Dim tmp As Commodity = o.Clone

        o = tmp.Save()

        Me(Index) = o

        IsReadOnly = True

 

        Return o

 

    End Function

 

In this way the ROL is able to call the save of the child BusinessBase, and update its own collection. This means my listbox is refreshed with all the changes and I only have one binding source for the screen. Any feedback on this approach s welcomed.

 

Stan

RockfordLhotka replied on Thursday, August 02, 2007

The general answer is to use the Observer design pattern.

Set up an Observer to watch for new items being added. The new item is, by definition, an editable root, or was saved through an editable root, which means that a Saved event is raised when the object is saved.

The Observer handles that event, and relays the information to anyone who cares - such as your ROL.

The ROL can then just add that one new item.

The easiest way to do this is to use .NET events as the "Observer".

In your editable root:

    public static event EventHandler ProjectSaved;

    protected static void OnProjectSaved(Project project)
    {
      if (ProjectSaved != null)
        ProjectSaved(project, EventArgs.Empty);
    }

    public override Project Save()
    {
      Project result = base.Save();
      OnProjectSaved(result);
      return result;
    }

The Save() override raises the static event any time any Project object is inserted or updated.

And in your ROL you handle that event:

    private ProjectList()
    {
      Project.ProjectSaved += new EventHandler(Project_ProjectSaved);
    }

    void Project_ProjectSaved(object sender, EventArgs e)
    {
      Project project = sender as Project;
      if (project != null)
      {
        IsReadOnly = false;
        try
        {
          for (int i = 0; i < this.Count -1; i++)
          {
            if (thisIdea [I].Id.Equals(project.Id))
            {
              // item exists - update with new data
              thisIdea [I] = new ProjectInfo(project.Id, project.Name);
              return;
            }
          }
          // insert item
          this.Add(new ProjectInfo(project.Id, project.Name));
        }
        finally
        {
          IsReadOnly = true;
        }
      }
    }

When the event is handled, sender is the new item. You can search the current list to see if that item already exists, and then replace it with an updated version. If it doesn't exist you simply add a new item.

VoodooSV replied on Monday, August 13, 2007

Hi guys!
I was leave and thanks a lot for your answer!!! I'll make approach, that proposed by Rocky.

With best regards,
Sergey.

Nicholas Trevatt replied on Sunday, February 10, 2008

I have implemented the observer pattern outlined here by Rocky and it half works for me.  If I edit an existing root object or delete a root object the changes are automatically reflected in the grid bound to the ROL without any rebinding/refreshing of the grid or bindingsource.  Pretty cool actually.  However, when I add a new root object it does not update the grid without forcing a rebind.  The root object does correctly notify the ROL through the event which in turn updates itself.  So, I have a ROL.Count of say 5 but a grid showing only 4 items.

Is this normal behaviour or am I missing something?

Cheers,
Nicholas

VoodooSV replied on Monday, February 11, 2008

Nicholas Trevatt:
I have implemented the observer pattern outlined here by Rocky and it half works for me.  If I edit an existing root object or delete a root object the changes are automatically reflected in the grid bound to the ROL without any rebinding/refreshing of the grid or bindingsource.  Pretty cool actually.  However, when I add a new root object it does not update the grid without forcing a rebind.  The root object does correctly notify the ROL through the event which in turn updates itself.  So, I have a ROL.Count of say 5 but a grid showing only 4 items.

Is this normal behaviour or am I missing something?

Cheers,
Nicholas


Hi!
Check a loop:
instead of
         for (int i = 0; i < this.Count -1; i++)
must be
         for (int i = 0; i < this.Count; i++)

With best regards,
Sergey.

mr_lasseter replied on Monday, February 11, 2008

@Nicholas,

Does the the object get added to the list when the grid is repainted (say if you minimize the app and then maximize it)? 

VoodooSV replied on Monday, February 11, 2008

I have similar problem, because new object add to the end of list (at position [this.Count-1]) and therefore it's not visible in grid if a boundary of loop is "<this.Count - 1". Simply, in code was a grammar mistake.

Nicholas Trevatt replied on Monday, February 11, 2008

Hi Sergey,

Thanks for the tip but I don't think this is the problem. 
I'm presuming you were thinking of the code inside the ROL which updates itself?  The ROL is a zero based collection which means a loop with this.Count -1 is actually correct.  Attempting the loop without -1 results in an index out of range error. 

As mentioned in my original post, the ROL is updating itself correctly.  Checking the ROL.Count from the GUI reference variable and inside the ROL itself show a correct count.  The grid is just not showing a newly added object unless I rebind the grid.

The fact that it does update itself when a child is updated or deleted makes me wonder if ReadOnlyListBase is not firing an event to notify the grid a new child has been added?  Is there a flag to turn this on/off perhaps?

Cheers,
Nicholas

Nicholas Trevatt replied on Monday, February 11, 2008

Found problem.

Had previously set BindingSource.RaiseListChangedEvents = False.

Doh!
Nicholas

Nicholas Trevatt replied on Tuesday, February 12, 2008

Another issue I am having with using this implementation of the observer pattern is ROL object references in the GUI are not being released from memory when the object reference is reassigned to  another ROL instance.  I did set it to Nothing before reassignment with no success.  The side effect of this behaviour is that when a BO raises a saved event it notify's multiple ROL's...even the ones no longer reference by my app.  Garbage collecting only seems to happen on these ROL objects when the app closes.

To work around this I have created a RemoveObserverHandlers in the ROL which the GUI calls before reassigning the object variable.  I would prefer it if the ROL could handle this itself when it's reference is released and would like help in how to do this.  I did code the ROL.Dispose event but this wasn't being called when the only variable the GUI held was released.  I'm presuming the problem lies in the nature of events holding on to my ROL?

Cheers,
Nicholas

Jerrycurl replied on Tuesday, April 01, 2008

I've run into this problem as well, and have implemented a RemoveObserverHandlers method like you mentioned.  Did you ever find out a better way?

Joffies replied on Thursday, April 02, 2009

Hi everyone.

I have a similar scenario where I have hooked up a ROL to the save event of BB object after the mybase.save method is called. (Exactly like Rocky's example above)

Public Shared Event RoleSaved(ByVal sender As Object, ByVal e As Csla.Core.SavedEventArgs)

Protected Shared Sub OnRoleSaved(ByVal sender As Role, ByVal e As Csla.Core.SavedEventArgs)
RaiseEvent RoleSaved(sender, e)
End Sub

Public Overrides Function Save() As Role

Dim result As Role

result = MyBase.Save
OnRoleSaved(Me, New Csla.Core.SavedEventArgs(result))
Return result
'Return MyBase.Save

End Function

Readonly List Code

Private Sub Role_Saved(ByVal sender As Object, ByVal e As Csla.Core.SavedEventArgs)

Dim old As Role = CType(sender, Role)

If old.IsNew Then
IsReadOnly = False
Add(RoleInfo.LoadInfo(CType(e.NewObject, Role)))
Else
Dim child As RoleInfo
For Each child In Me
If child.RoleID = old.RoleID Then
child.UpdateValues(CType(e.NewObject, Role))
End If
Next
End If

But how can I notify my ROL list to remove any deleted items?

Thanks

John

Copyright (c) Marimer LLC