Chapter 9. Parent-Child Relationships

In Chapter 6, I started walking through the framework implementation. Chapter 7 covers how to declare properties, including properties that reference child objects, and Chapter 8 focuses on how object status values are tracked, including some interaction with child objects.

The idea of child objects is introduced in Chapter 3. A child object is contained by a parent object. Conversely, a parent object is an object that contains one or more child objects. The top-most parent of a group of objects is the root object, and that is the object that can be directly retrieved or updated into the database. Any child objects contained by that root object are retrieved or updated at the same time, a topic I cover in Chapter 15.

While many of the details of parent and child objects are covered in other chapters, this chapter recaps the concepts, consolidating them into one location for easy reference. First I talk about child properties of an editable object, then I talk about how editable collections work because they contain an entire list of child objects.

Parent Editable Object

One common scenario is to have an editable object be a parent of other objects. In many cases, the root object is also an editable object, as shown in Figure 9-1.

Parent with single child

Figure 9.1. Parent with single child

A parent object may also contain a child collection, as shown in Figure 9-2.

In Figure 9-2 there are actually two parent objects. The Root object is the parent of ChildList, which is the parent of several Child objects. I discuss parent collections later in the chapter in the "Parent Editable Collection" section. For now let's focus on the editable object as a parent.

Parent with single collection

Figure 9.2. Parent with single collection

Parent-Child Interaction

In Chapter 7, I discuss the various options for declaring properties, including the use of private and managed backing fields. When declaring a property that contains a reference to a child object I strongly recommend always using a managed backing field, even if you are using private backing fields for your other properties. The reason for this is that using a managed backing field allows CSLA .NET to help you properly handle the child reference.

Table 9-1 lists the details managed for you by CSLA .NET.

Table 9.1. Parent-Child Details Managed by CSLA .NET

Method

Description

IsValid

A parent object is only valid if it and all its child objects are also valid.

IsDirty

A parent object is dirty (changed) if it or any of its child objects are dirty.

IsBusy

A parent object is busy (has an asynchronous operation running) if it or any of its child objects are busy.

Changed events

When a child object is changed, its changed event (PropertyChanged or ListChanged) results in the parent raising a ChildChanged event, which cascades up through the parent-child chain and is raised by every object, including the root object.

Parent reference

All child objects have a reference to their parent, which must be established when the child is attached to the parent and when the object graph is deserialized.

N-level undo

The edit levels of the parent and child interact with each other as discussed in Chapter 11.

If you don't use a managed backing field, you assume responsibility for handling all these details.

Note

You can handle all these details manually. Prior to CSLA .NET 3.5, it was necessary to do this in all parent objects. Forgetting to do one of these steps (or doing one incorrectly) was one of the primary causes of bugs in pre-3.5 objects.

Each item in Table 9-1 deserves a little discussion.

IsValid and IsDirty Properties

In Chapter 8, I discussed object status values. One important concept is that a parent object is considered valid or changed based on a combination of its state and the state of its child object(s).

Each editable object has an IsSelfValid property that gets a value, indicating whether that particular object is valid. The concept of validity is discussed further in Chapter 11.

However, each editable object also has an IsValid property that gets a value indicating whether the object and its child objects are valid. For IsValid to return true, the object and all its child objects must be valid. This is handled through the implementation of IsValid and IsSelfValid in BusinessBase, combined with the FieldDataManager (discussed in Chapter 7), which interrogates all managed backing fields to find out if they are all valid.

Here's the IsValid implementation in FieldDataManager:

public bool IsValid()
  {
    foreach (var item in _fieldData)
      if (item != null && !item.IsValid)
        return false;
    return true;
  }

The validity of simple fields, such as a string or int value, is managed by the business object's validation rules, not the specific IsValid value for the field itself. The FieldData object simply returns true for IsValid at all times. But if the field is a reference to a child object, FieldData delegates the IsValid call to the actual child object, and so the child object's validity is what matters.

Here's the IsValid implementation in FieldData:

protected virtual bool IsValid
  {
    get
    {
      ITrackStatus child = _data as ITrackStatus;
      if (child != null)
      {
        return child.IsValid;

      }
      else
      {
        return true;
      }
    }
  }

Notice how the ITrackStatus interface is used to detect whether the field's value is an object capable of representing its own status values. If the field value can be cast to ITrackStatus, the child object's IsValid value is returned; otherwise, true is returned.

The IsDirty property of BusinessBase works exactly the same way, but the rule is slightly different. An object is considered to be changed, or dirty, if its data has changed or if any one of its child objects has been changed.

As with IsValid, the IsDirty property in BusinessBase works with FieldDataManager to determine whether any child objects have been changed. I won't walk through that code because it is essentially the same as the IsValid code shown here. You can look in the code from the download to see how IsDirty is implemented.

ChildChanged Event

When a property of an object is changed, a PropertyChanged event is raised. I discuss the PropertyChanged event and how it supports data binding in Chapter 10. For now it is enough to know that changing a property value in an object results in this event being raised.

If the child object is a collection or a list, inheriting from a BusinessListBase, a ListChanged event is raised when the list changes. This includes changes to properties of child objects contained in the list. In other words, when an object in the list changes, it raises a PropertyChanged event, which is handled by the list and results in a ListChanged event being raised. Again, I discuss ListChanged and data binding more in Chapter 10.

There are cases where the parent of an object needs to know that its child has been changed. For example, sometimes you'll have business or validation rules that must run in the parent when a child is changed.

To address this, BusinessBase includes code to handle the PropertyChanged or ListChanged events raised by any child objects. When such an event is handled, the result is a call to OnChildChanged(), which raises a ChildChanged event from the parent.

This means that the author of a parent business object can either override OnChildChanged() or handle the parent object's ChildChanged event. Either technique will result in notification that a child has changed.

The key is that BusinessBase includes code in the LoadPropertyValue() method (discussed in Chapter 7) to hook the PropertyChanged or ListChanged event of any child object when a reference to the child object is loaded into a managed backing field.

Also, when an object is serialized and deserialized (which occurs when it is cloned or flows through the data portal) all event hookups are lost. They must be reestablished when the object is deserialized, and that occurs in BusinessBase in the FieldDataDeserialized() method, which is called from the OnDeserializedHandler() method.

In both cases, the events are handled by these two methods in BusinessBase:

private void Child_PropertyChanged(
    object sender, PropertyChangedEventArgs e)
  {
    OnChildChanged(sender, e, null);
  }

  private void Child_ListChanged(object sender, ListChangedEventArgs e)
  {
    OnChildChanged(sender, null, e);
  }

As you can see, they both call the OnChildChanged() method, which is protected and virtual:

[EditorBrowsable(EditorBrowsableState.Advanced)]
  protected virtual void OnChildChanged(
    object source,
    PropertyChangedEventArgs propertyArgs,
    ListChangedEventArgs listArgs)
  {
    Csla.Core.ChildChangedEventArgs args =
      new Csla.Core.ChildChangedEventArgs(source, propertyArgs, listArgs);
    if (_childChangedHandlers != null)
      _childChangedHandlers.Invoke(this, args);
  }

This method uses the provided parameter values to create an instance of ChildChangedEventArgs, which is then used to raise the ChildChanged event.

The event itself is declared using the custom event declaration form:

[NonSerialized]
  [NotUndoable]
  private EventHandler<Csla.Core.ChildChangedEventArgs>
    _childChangedHandlers;

  public event EventHandler<Csla.Core.ChildChangedEventArgs> ChildChanged
  {
    add
    {
      _childChangedHandlers = (EventHandler<Csla.Core.ChildChangedEventArgs>)
        System.Delegate.Combine(_childChangedHandlers, value);
    }
    remove
    {
      _childChangedHandlers = (EventHandler<Csla.Core.ChildChangedEventArgs>)
        System.Delegate.Remove(_childChangedHandlers, value);
    }
  }

This is substantially more code than a simple event declaration such as this:

public event EventHandler<Csla.Core.ChildChangedEventArgs> ChildChanged;

The reason for all the extra code is that the backing field for the event needs to be marked as NonSerialized and NotUndoable. All events on a Serializable object must be marked with NonSerialized. Failure to do this will result in serialization errors if the event is being handled by a nonserializable object such as a WPF Form or a Windows Forms Form or a Web Forms Page object. Similarly, any event declared in an undoable object (one that inherits from UndoableBase or implements IUndoableObject as discussed in Chapter 13) must mark the backing field as NotUndoable. Failure to do this will result in undo errors during data binding.

This long form for declaring events allows explicit declaration of the backing field (which is otherwise autogenerated by the compiler):

[NonSerialized]
    [NotUndoable]
    private EventHandler<Csla.Core.ChildChangedEventArgs>
      _childChangedHandlers;

This declares a delegate field to manage the event handler references and is normally done by the compiler. However, since this code is explicit, the declaration can be decorated with the NonSerialized and NotUndoable attributes. The result is that the ChildChanged event won't cause problems during serialization, n-level undo, or data binding.

You should now understand how BusinessBase handles all child PropertyChanged and ListChanged events and raises a ChildChanged event in response.

Parent Reference

All child objects maintain a reference to their immediate parent object. This reference is declared in BusinessBase as NonSerialized and NotUndoable:

[NotUndoable()]
  [NonSerialized()]
    private Core.IParent _parent;

The BinaryFormatter and NetDataContractSerializer can handle circular references in an object graph, so I could get away without the NonSerialized attribute. However, it has been observed that circular references in an object graph cause a substantial increase in the size of the byte stream that contains the serialized data. By using the NonSerialized attribute, I am reducing the size of the serialized data that is often transferred over the network in client/server scenarios.

The NotUndoable attribute is absolutely required. As you'll see in Chapter 13, the n-level undo support in CSLA .NET doesn't handle circular references, so if this attribute is missing, n-level undo would go into an infinite loop, resulting in a stack overflow exception.

Notice that the field type is IParent from the Csla.Core namespace. All parent objects are required to implement the IParent interface to enable interaction between the child and the parent. I discuss IParent later in this chapter.

The Parent property in BusinessBase is of this type as well:

[EditorBrowsable(EditorBrowsableState.Advanced)]
  protected Core.IParent Parent
  {
    get { return _parent; }
  }

There's also a SetParent() method, which is invoked by the parent object to set the reference:

internal void SetParent(Core.IParent parent)
  {
    _parent = parent;
  }

This method is invoked by LoadPropertyValue() when the child object is set into a managed backing field. And it is invoked when the parent object is deserialized. Remember that the _parent field is NonSerialized, so when the child is deserialized the value is null. It must be restored to a meaningful value once deserialization is complete, and that occurs in the parent object's FieldDataDeserialized() method, which is invoked by the OnDeserializedHandler() method.

The result is that a child object can always get a reference to its immediate parent through its Parent property. This reference is used automatically in some cases, as I discuss later in the "Parent Editable Collection" section.

N-Level Undo

The n-level undo functionality of CSLA .NET is discussed in detail in Chapter 13. It is also discussed to some degree in Chapter 10 because n-level undo is used to provide important functionality for data binding.

There are several points in n-level undo and data binding, where parent and child objects must interact with each other. I won't discuss the details here; you can refer to Chapters 10 and 13.

IParent Interface

The IParent interface, declared in Csla.Core, allows a child object to interact with its parent:

public interface IParent
{
  void RemoveChild(Core.IEditableBusinessObject child);
  void ApplyEditChild(Core.IEditableBusinessObject child);
}

The RemoveChild() method is used by the data binding functionality discussed in Chapter 10. There are scenarios where data binding informs a child object that it has been removed or deleted and the child then must ask its parent to remove its reference to the child object.

The ApplyEditChild() method is used for interaction with the EditableRootListBase class, again triggered by data binding, so the parent collection can be notified that a child object's changes should be committed.

Perhaps more important than these two methods is the fact that the IParent interface provides a consistent type that is used by any child object when maintaining a reference to its immediate parent object.

At this point, you should have a high-level understanding of the features that CSLA .NET automatically provides when a child object reference is contained in a managed backing field. If you choose to use a private backing field to maintain a child object reference, you should make sure you replicate these behaviors as part of your implementation. Again, I recommend using managed backing fields to avoid having to do all that work.

Declaring Child Properties

There are several ways to use a managed backing field to reference a child object, depending on the specific features you desire. The variations center on how and when the child object is to be created.

You can create the child object as the parent object is created or retrieved from the database. Alternately you can create the child object on demand, meaning it is created the first time the UI (or other calling code) accesses the property that exposes the child object. Finally, you can use lazy loading to retrieve an existing child object in an on-demand manner.

Next I'll walk through each variation.

Create Child in DataPortal_XYZ Methods

The simplest property declaration is as follows:

private static PropertyInfo<ChildType> ChildProperty =
    RegisterProperty(new PropertyInfo<ChildType>("Child"));
  public ChildType Child
  {
    get { return GetProperty(ChildProperty); }
  }

This stores the child reference in a managed backing field, which allows the field manager to automatically take care of all the housekeeping details involved with a child reference.

You should be wondering how the child object is actually created. This code simply returns a value, but that value must be initialized somewhere.

In Chapter 15 I discuss the data portal and object persistence. One feature of the data portal is that a root object is created or retrieved in methods you write, named DataPortal_Create() and DataPortal_Fetch().

The DataPortal_Create() method is invoked by the data portal when your object should initialize itself with values appropriate for a new object. In many cases, this is where you'd also create a new, empty instance of your child object:

protected override void DataPortal_Create()
  {
  LoadProperty(ChildProperty, Child.NewChild());
    base.DataPortal_Create();
  }

The call to ChildType.NewChild() is invoking the child object's factory method, which is a concept I discuss in Chapters 4 and 5. It returns a new instance of the child object, which is then loaded into the property's managed backing field using the LoadProperty() method.

Similarly, the DataPortal_Fetch() method is invoked by the data portal when your object should load itself with data from the database. In most cases, this is where you'd also load any child objects with data from the database as well:

protected void DataPortal_Fetch(SingleCriteria<RootType, int> criteria)
  {
    using (var ctx = ContextManager<DataContext>.GetManager("MyDatabase"))
    {
      // load root object with data here
    // get data for child here
    LoadProperty(ChildProperty, Child.GetChild(childData));
    }
  }

There's a lot going on here, much of which is covered in Chapter 15. The important thing to realize is that the Child.GetChild() method is calling a factory method that creates an instance of the child object and loads that child object with data from the database. In many cases the data for the child object(s) is already loaded from the database by the code here in the parent, but that's not required.

Again, a LoadProperty() method is used to put the resulting child object into the managed backing field for the property.

The result is that when your root object is created or retrieved, DataPortal_Create() or DataPortal_Fetch() are invoked, and they both load a child object into the property using the LoadProperty() method. That value is then available to other business objects and the UI through the public property.

Additionally, any calls to the parent object's IsValid and IsDirty properties automatically take into account the IsValid and IsDirty status of your child object. And any change to the child object that results in a PropertyChanged or ListChanged will cause a ChildChanged event to be raised by the parent.

Create Child on Demand

It is very common to create a child object in the DataPortal_Fetch() method, so it can be efficiently loaded with data from the database as the parent object's data is also loaded. And that's a great approach. However, it seems unfortunate that you have to create a DataPortal_Create() method just to create an empty instance of a new child object. Many objects don't need to initialize values as they are created, and so you wouldn't need to implement DataPortal_Create() at all in that case (because there's a default implementation of DataPortal_Create() already provided by BusinessBase).

For the purpose of creating a new child object, you might choose to just create the child object on demand. In this case, you would not call LoadProperty() in the DataPortal_Create() method, as shown in the previous section. Instead, you'd enhance the property declaration itself like this:

private static PropertyInfo<ChildType> ChildProperty =
    RegisterProperty(new PropertyInfo<ChildType>("Child"));
  public ChildType Child
  {
    get
    {
    if (!FieldManager.FieldExists(ChildProperty))
      LoadProperty(ChildProperty, ChildType.NewChild());
      return GetProperty(ChildProperty);
    }
  }

The FieldExists() method returns a value indicating whether this particular managed backing field exists yet. If it does not yet exist, the LoadProperty() method is called to load the backing field with a new child, created by the NewChild() factory method. On the other hand, if the managed backing field does already exist, the existing value is simply returned by the property.

The result of this approach is that a new child object is only created if and when this property is called the first time. It is created on demand.

This approach is also totally compatible with the idea of loading the child object in DataPortal_Fetch(). If the child object is created, initialized, and loaded in DataPortal_Fetch(), it will already exist by the time the property is invoked, and so that existing value is simply returned.

Lazy Load Child

There's one more variation you can use, which is on-demand retrieval of a child object. This is often called lazy loading of the child object. The idea here is that you would not create or load the child object in either DataPortal_Create() or DataPortal_Fetch(). Instead, you create or fetch the child object on demand in the property itself. There are wider implications here, especially in terms of how the child class is coded.

Before getting into the child class though, here's the property declaration in the parent:

private static PropertyInfo<ChildType> ChildProperty =
    RegisterProperty(new PropertyInfo<ChildType>("Child"));
  public ChildType Child
  {
    get
    {
      if (!FieldManager.FieldExists(ChildProperty))
      if (this.IsNew)
        LoadProperty(ChildProperty, ChildType.NewChild());
      else
        LoadProperty(ChildProperty, ChildType.GetChild(this));
      return GetProperty(ChildProperty);
    }
  }

The basic concept is the same, in that the FieldExists() method is used to detect whether the child object already exists. If it does not already exist, the child object is created and loaded. But that process is a little different.

Notice that the parent object's IsNew property is used to determine whether the parent is a new object. If the parent is a new object, the child must be new as well, so the NewChild() factory method is invoked.

However, if the parent object is not new, it was loaded from the database, so it is reasonable to assume that there is child data in the database as well. So in this case the GetChild() factory method is called and the parent object is provided as a parameter to that factory.

Warning

Remember that the child object is created the first time the property is accessed. It is easy to accidentally access a property, for example, by data binding the property to a UI. So if you want to use lazy loading, it is critical that you be very careful about how and when this property is accessed to avoid prematurely triggering lazy load process.

I won't fully discuss object persistence, factory methods, and the data portal until Chapter 15, so you may want to skip ahead and review those concepts, then return to the following discussion.

Enabling Lazy Loading in the Child Class

When using lazy loading, the code in a child class is different from normal. In fact, the child object will implement the same kind of factory and DataPortal_Fetch() as a root object. And you'll need to call MarkAsChild() manually because the data portal won't know to call it automatically on your behalf.

The child factory method will look like this:

internal ChildType GetChild(ParentType parent)
  {
    return DataPortal.Fetch<ChildType>(
      new SingleCriteria<ChildType, int>(parent.Id));
  }

The data portal is invoked to retrieve the child object, using properties from the parent object as criteria. In many cases a child object is loaded based on the parent object's unique ID value, which can be used as a foreign key, but the actual criteria values you use will depend on your specific object and data models.

Tip

Notice that a reference to the parent object is provided as a parameter rather than specific parent properties. This provides good decoupling between the parent and child because this way the parent has no idea what data is required by the child's factory method.

As you'll see in Chapter 15, the data portal ultimately creates an instance of the object and invokes the DataPortal_Fetch() method. Normally, a child object wouldn't implement this method at all, but when using lazy loading you do implement this method. The method is responsible for loading the child object with data from the database:

private void DataPortal_Fetch(SingleCriteria<ChildType, int> criteria)
  {
    MarkAsChild();
    // load child with data from database
  }

The important thing to remember in this case is that you must call MarkAsChild() to indicate that this is a child object. You must do this manually because the data portal is being used to load the object here as though it were a root object. The data portal doesn't know to mark the object as a child automatically, so you must do it explicitly.

The end result is that the child object is created or retrieved on demand, using lazy loading.

At this point you should understand how an editable object can act as a parent or a child and how a parent object manages the references to its child objects. In the next section, I discuss how this works when the parent object is an editable collection.

Parent Editable Collection

Editable collections are created by inheriting from BusinessListBase, as discussed in Chapters 4 and 5. By definition, a collection is a parent because it contains the items in the collection.

To a large degree, the interactions between parent and child objects discussed already in this chapter also apply when the parent is a collection. For example, the BusinessListBase class implements the IParent interface. And BusinessListBase calls SetParent() as each child object is added to the collection or when the collection is deserialized. The same benefits and features I've already discussed apply to collections as well as editable objects.

However, editable collections provide some different behaviors as well, most notably around how items are deleted from the collection and how child events are cascaded up as ListChanged events from the collection (especially after deserialization of the collection).

Parent-Child Interaction

The BusinessListBase class provides the same set of behaviors in Table 9-1, earlier in the chapter. The primary difference in the implementation is that a collection has no intrinsic state of its own. Instead, its state comes from its child objects. In other words, the collection's IsValid and IsDirty properties simply reflect the underlying state of the child objects. For example, a collection is valid only if all the child objects it contains are valid.

The one big difference between an editable object and an editable collection is in terms of how ChildChanged events are handled.

ListChanged Event

Whereas an editable object handles any PropertyChanged or ListChanged events from its child objects and raises a ChildChanged event, an editable collection works a little differently.

An editable collection can only contain editable child objects so only needs to worry about those objects raising PropertyChanged and ChildChanged events. Any time a child object raises PropertyChanged, the collection raises a ListChanged event. This is automatic behavior provided by the BindingList<T> class from the System.ComponentModel namespace. Any time a child object raises a ChildChanged event, the collection raises its own ChildChanged event, effectively cascading the ChildChanged event up to each parent until it is raised by the editable root object.

Unfortunately, BindingList<T> doesn't automatically handle the case where the collection is serialized and deserialized, which happens when the object is cloned or transferred over the network in a client/server scenario. When a collection is deserialized, the ListChanged event is no longer automatically raised in response to a child object's PropertyChanged event.

To overcome this issue, BusinessListBase includes code to hook the PropertyChanged events from its child objects on deserialization and to raise the ListChanged event just like its base class did before serialization.

When the collection is deserialized, the formatter invokes OnDeserializedHandler(), which is implemented in ExtendedBindingList and includes code to hook the PropertyChanged events from all child objects in the list:

foreach (T item in this)
        OnAddEventHooksInternal(item);

The OnAddEventHooksInternal() method includes code to hook a number of child object events, most notably PropertyChanged:

INotifyPropertyChanged c = item as INotifyPropertyChanged;
    if (c != null)
        c.PropertyChanged += Child_PropertyChanged;

The OnChildChangedInternal() method in BusinessListBase handles each child PropertyChanged event and raises a corresponding ListChanged event:

protected internal override void OnChildChangedInternal(
      object sender, ChildChangedEventArgs e)
    {
      if (RaiseListChangedEvents && e.PropertyChangedArgs != null)
      {
        DeferredLoadIndexIfNotLoaded();
        if (_indexSet.HasIndexFor(e.PropertyChangedArgs.PropertyName))
          ReIndexItem((C)sender, e.PropertyChangedArgs.PropertyName);

      int index = IndexOf((C)sender);
      if (index >= 0)
      {
        PropertyDescriptor descriptor =
          GetPropertyDescriptor(e.PropertyChangedArgs.PropertyName);
        if (descriptor != null)
          OnListChanged(new ListChangedEventArgs(
            ListChangedType.ItemChanged,
            index, GetPropertyDescriptor(e.PropertyChangedArgs.PropertyName)));
        else
          OnListChanged(new ListChangedEventArgs(
            ListChangedType.ItemChanged, index));
        return;
      }
    }
  }

There's code in here for LINQ to CSLA as well, which I discuss in Chapter 14. This method is invoked by the ExtendedBindingList base class, which contains the code to hook and unhook child object events as necessary.

I've highlighted the code relevant to the event discussion. You might expect that raising a ListChanged event would be easy, but it turns out to be quite complex. The reason is that the ListChanged event needs to provide both the index of the changed item and a PropertyDescriptor object for the child property that is changed.

Once the index value has been found, a GetPropertyDescriptor() method is called to find the PropertyDescriptor for the changed child property. Here's that method:

private static PropertyDescriptorCollection _propertyDescriptors;

  private PropertyDescriptor GetPropertyDescriptor(string propertyName)
  {
    if (_propertyDescriptors == null)
      _propertyDescriptors = TypeDescriptor.GetProperties(typeof(C));
    PropertyDescriptor result = null;
    foreach (PropertyDescriptor desc in _propertyDescriptors)
      if (desc.Name == propertyName)
      {
        result = desc;
        break;
      }
    return result;
  }

The PropertyDescriptor concept comes from System.ComponentModel and is used extensively by Windows Forms data binding. This part of .NET is related to reflection but is a separate type system from reflection itself. Like reflection, however, the type descriptor functionality has a pretty high performance cost. To minimize the impact, the PropertyDescriptorCollection for the child object type is cached in a static field, so it is only retrieved once per AppDomain (typically once each time the application is run).

The PropertyChanged event only provides the name of the changed property, so it is necessary to loop through all the items in the PropertyDescriptorCollection to find the matching property name, at which point the resulting PropertyDescriptor can be returned. If no match is found, a null is returned.

While all this behavior is automatically handled by the BindingList<T> base class, this code is necessary because BindingList<T> doesn't handle the case where the collection has been serialized and deserialized.

Removing Child Objects from the Collection

It is possible to remove an item from the collection. The basic process is handled automatically by the BindingList<T> base class. However, there are some complications that must be handled by BusinessListBase. Specifically, any LINQ to CSLA index must be updated and there's interaction with the n-level undo behaviors, discussed in Chapter 13.

This last point about n-level undo is the most complex. If changes to a collection can be rolled back, any removed items must be restored and any newly added items removed.

I'll leave detailed discussions of LINQ to CSLA and n-level undo to their respective chapters. For now it is enough to know that the RemoveItem() method, which is a protected method provided by BindingList<T>, is invoked when a child item is to be removed. This method contains important code necessary for both LINQ to CSLA and n-level undo.

You should now have an understanding about how an editable collection provides not only the same parent functionality as an editable object but how it handles the child PropertyChanged events and must interact with the LINQ to CSLA and n-level undo functionality.

Conclusion

In this chapter I discussed how CSLA .NET supports important parent-child interactions, including the following:

  • IsValid

  • IsDirty

  • Change events

  • Parent reference

  • N-level undo

Chapters 10 through 16 continue the coverage of the implementation of CSLA .NET. Then, from Chapter 17 on, the focus is on building the simple business application designed in Chapter 3 to illustrate how the classes in the framework can be used to build applications based on business objects.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset