Chapter 8. Object Status Management

The next topic I want to discuss in the implementation of CSLA .NET is how editable objects manage status information. Editable business objects maintain a set of consistent status information. Management of these values is mostly automated by BusinessBase, BusinessListBase, and the data portal.

Object Status Properties

All editable business objects should keep track of whether the object has just been created, whether its data has been changed, or whether it has been marked for deletion. Using the validation rules functionality, the object can also keep track of whether it's valid. Table 8-1 lists the object status properties in BusinessBase and BusinessListBase.

Table 8.1. Object Status Properties

Property

Description

IsNew

Indicates whether the object's primary identifying value in memory corresponds to a primary key in a database—if not, the object is new

IsSelfDirty

Indicates whether the object's data in memory is known to be different from data in the database—if different, the object is dirty

IsDirty

Indicates whether the object itself has been changed, or if any of its child objects have been changed

IsSelfValid

Indicates whether the object currently has any broken validation rules—if so, the object is not valid

IsValid

Indicates whether the object itself is valid, and whether all its child objects are also valid

IsSavable

Indicates whether the object can be saved by combining IsValid, IsDirty, authorization, and edit level, and whether there are any outstanding async validation rules running

IsDeleted

Indicates whether the object is marked for deletion

ITrackStatus Interface

In Chapter 6 I briefly discussed the ITrackStatus interface from the Csla.Core namespace. This interface is implemented by BusinessBase and BusinessListBase, allowing your code to gain access to the object status values without worrying about the specific object type:

public interface ITrackStatus
  {
    bool IsValid { get; }
    bool IsSelfValid { get; }
    bool IsDirty { get; }
    bool IsSelfDirty { get; }
    bool IsDeleted { get; }
    bool IsNew { get; }
    bool IsSavable { get; }
  }

This interface is used within CSLA .NET itself, but is also available to business object and UI framework authors.

I will now discuss the concepts behind an object being new, dirty, valid, and marked for deletion.

IsNew

When an object is "new," it means that the object exists in memory but not in the database or other persistent store. If the object's data resides in the database, the object is considered to be "old." I typically think of it this way: if the primary key value in the object corresponds to an existing primary key value in the database, the object is old; otherwise it is new.

The value behind the IsNew property is stored in an _isNew field. When an object is first created, this value defaults to the object being new:

private bool _isNew = true;

The IsNew property simply exposes this value:

[Browsable(false)]
  public bool IsNew
  {
    get { return _isNew; }
  }

The property is adorned with the Browsable attribute from the System.ComponentModel namespace. This attribute tells data binding not to automatically bind this property. Without this attribute, data binding would automatically display this property in grids and on forms, and typically, this property shouldn't be displayed. This attribute is used on other properties in BusinessBase as well.

MarkOld Method

If the object is then loaded with data from the database, the _isNew field is set to false, through a protected MarkOld() method:

protected virtual void MarkOld()
  {
    _isNew = false;
    MarkClean();
  }

Notice that this process also sets the object to a "clean" status—a concept discussed later in this chapter when we look at the IsDirty property. When an object's data has just been loaded from the database, it is safe to assume that the object's data matches the data in the database and has not been changed and thus is "clean."

MarkNew Method

There's also a corresponding MarkNew() method:

protected virtual void MarkNew()
  {
    _isNew = true;
    _isDeleted = false;
    MarkDirty();
  }

These methods are normally called automatically by the data portal through the IDataPortalTarget interface, but since they are protected they are available to the business object author as well. This allows the business object to change its own state to handle those rare cases where the default behavior is inappropriate.

Knowing whether an object is new or old allows for implementation of the data portal in Chapter 15. The IsNew property will control the choice of whether to insert or update data into the database.

Sometimes, the IsNew property can be useful to the UI developer as well. Some UI behaviors may be different for a new object than for an existing object. The ability to edit the object's primary key data is a good example; this is often editable only up to the point that the data has been stored in the database. When the object becomes "old," the primary key is fixed.

IsSelfDirty

An object is considered to be "dirty," or changed, when the values in the object's fields do not match the values in the database. If the values in the object's fields do match the values in the database, the object is not dirty. It is virtually impossible to always know whether the object's values match those in the database, so the implementation shown here acts on a "best guess." The implementation relies on the business developer to indicate when an object has been changed and thus has become dirty.

A similar but related concept is that an object is considered dirty if it or any of its child objects have been changed. That concept is reflected by the IsDirty property, which is different from IsSelfDirty that reflects the status of only this specific object (not counting its child objects).

The current status of the value is maintained in a field:

private bool _isDirty = true;

The value is then exposed as a property:

[Browsable(false)]
  public virtual bool IsSelfDirty
  {
    get { return _isDirty; }
  }

Notice that this property is marked as virtual. This is important because sometimes a business object isn't simply dirty because its data has changed. In this case, the business developer will need to override the IsSelfDirty property to provide a more sophisticated implementation.

Note

While changing the behavior of IsSelfDirty is a rare scenario, the feature was requested numerous times by the community and so the property was made virtual.

The IsSelfDirty property defaults to true because a new object's field values won't correspond to values in the database.

MarkClean Method

If the object's values are subsequently loaded from the database, the _isDirty value is changed to false when MarkOld() is called because MarkOld() calls a MarkClean() method:

[EditorBrowsable(EditorBrowsableState.Advanced)]
  protected void MarkClean()
  {
    _isDirty = false;
    if (_fieldManager != null)
      FieldManager.MarkClean();
    OnUnknownPropertyChanged();
  }

This method not only sets the _isDirty value to false but also calls MarkClean() on the FieldManager to mark all the fields it contains as being unchanged. Once the object and its fields are marked as unchanged, this method calls the OnUnknownPropertyChanged() method implemented in Csla.Core.BindableBase to raise the PropertyChanged event for all object properties. This notifies data binding that the object has changed, so WPF and Windows Forms can refresh the display for the user. I discuss the BindableBase class and data binding in Chapter 10.

MarkDirty Method

There's a corresponding MarkDirty() method as well. This method is called from various points in an object's lifetime, including any time a property value is changed or when the MarkNew() method is called. When a property value is changed, a specific PropertyChanged event is raised for that property.

If MarkDirty() is called at other times, when a specific property value isn't changed, the PropertyChanged event for all object properties should be raised. That way, data binding is notified of the change if any object property is bound to a UI control.

To be clear, the goal is to ensure that at least one PropertyChanged event is raised any time the object's state changes. If a specific property is changed, the PropertyChanged event should be raised for that property. But if there's no way to tell which properties are changed (like when the object is persisted to the database), there's no real option but to raise PropertyChanged for every property.

Implementing this requires a couple of overloads of the MarkDirty() method:

protected void MarkDirty()
  {
    MarkDirty(false);
  }

  [EditorBrowsable(EditorBrowsableState.Advanced)]
  protected void MarkDirty(bool suppressEvent)
  {
    _isDirty = true;
    if (!suppressEvent)
      OnUnknownPropertyChanged();
  }

The first overload can be called by a business developer if she wants to manually mark the object as changed. This is intended for use when unknown properties may have changed.

PropertyHasChanged Method

The second overload is called by the PropertyHasChanged() method:

protected virtual void PropertyHasChanged(string propertyName)
  {
    MarkDirty(true);
    var propertyNames = ValidationRules.CheckRules(propertyName);
    if (ApplicationContext.PropertyChangedMode ==
                      ApplicationContext.PropertyChangedModes.Windows)
      OnPropertyChanged(propertyName);
    else
      foreach (var name in propertyNames)
        OnPropertyChanged(name);
  }

The PropertyHasChanged() method is called by the SetProperty() methods discussed in Chapter 7 to indicate that a specific property has changed. Notice that in this case, any validation rules for the property are checked (the details on this are discussed in Chapter 11).

Tip

This method is virtual, allowing you to add extra steps to the process if needed. Additionally, this means you can override the behavior to implement field-level dirty tracking if desired.

Then the object is marked as being dirty by raising the PropertyChanged event for the specific property that is changed. This isn't as simple as I would like because the code needs to behave differently when used by WPF as opposed to any other UI technology such as Windows Forms or Web Forms. Since there's no way to automatically detect whether this object is being used by WPF, there's a configuration switch the business developer can set to indicate how this method should behave.

Configuring the PropertyChangedMode

The Csla.ApplicationContext.PropertyChangedMode setting can be configured through the application's config file or in code.

In code, the UI developer will typically set the value by running the following line of code exactly once as the application starts up:

Csla.ApplicationContext.PropertyChangedMode =
    Csla.ApplicationContext.PropertyChangedModes.Xaml;

This only needs to be done in a WPF application, as the default is Windows, which is the correct setting for any non-WPF application.

The value can also be set in the application's app.config file by adding the following element to the <appSettings> element:

<add key="CslaPropertyChangedMode" value="Xaml" />

Either way, the result is that the PropertyChanged events are raised as required by WPF rather than as needed for Windows Forms or Web Forms.

Raising Events for WPF

In WPF, when a PropertyChanged event is handled by data binding, only the control(s) bound to that specific property are refreshed in the UI. This makes a lot of sense but causes a bit of complexity.

When a property is changed it triggers the execution of the business and validation rules associated with that property (see Chapter 11 for details). It also triggers the execution of rules associated with dependent properties. This means that changing one property can execute rules for multiple properties.

When a validation rule fails, the UI will display something to the user indicating that the value is invalid. WPF data binding knows to change the display for a control because it handles the PropertyChanged event for the property to which the control is data bound.

If the only PropertyChanged event raised is for the property that is changed, any broken validation rules for dependent properties will be ignored by data binding and won't be visible to the user.

Notice that the CheckRules() method call returns an array containing the names of all the properties for which business or validation rules are executed. This allows the PropertyHasChanged() method to raise PropertyChanged events for all those property names, not just for the property that is actually changed. The result is that broken validation rules are reflected in the UI, even for properties other than the one that is actually changed.

Raising Events for Windows Forms

In Windows Forms, data binding works differently. When a PropertyChanged event is handled by Windows Forms data binding, all controls bound to this business object are refreshed in the UI. Any single PropertyChanged event refreshes all the controls in the UI. This means that the fewer PropertyChanged events raised the better because each one causes a refresh of the UI.

For other technologies, such as Web Forms, the PropertyChanged event isn't used by data binding at all. But it is still important to raise the event because custom UI code often listens for this event so it knows that the object has been changed. Since most of this custom UI code was written before WPF, it tends to expect the Windows Forms-friendly behavior rather than the WPF-friendly behavior.

To this end, the default behavior is to raise only one PropertyChanged event for the property that is actually changed. This is true even if multiple properties have their validation rules run as a result of the change.

The final result is that the property's validation rules are checked, the IsDirty property is set to true, and the appropriate PropertyChanged events are raised.

IsDirty

You've seen how each individual business object manages its own IsSelfDirty property. When an object is a parent, things are slightly more complex. This is because a parent is considered to be changed if its fields have changed or if any of its child objects have been changed.

This is important because the IsDirty property is used by the data portal to optimize which objects to update into the database. If an object hasn't been changed, there's no sense trying to update the database with the values it already has. But consider the case of something like a parent SalesOrder object that contains a list of child LineItem objects. Even if users do not change the SalesOrder itself, if they change the LineItem objects, the object graph as a whole must be considered to have changed. When the UI code calls _salesOrder.Save(), it is reasonable to expect that the changed LineItem object (a child of the SalesOrder object) will be saved.

The IsDirty property makes it easy for the data portal code to determine whether the object graph has been changed because it consolidates the object's IsSelfDirty value with the IsDirty values of all child objects:

[Browsable(false)]
  public virtual bool IsDirty
  {
    get { return IsSelfDirty || (_fieldManager != null && FieldManager.IsDirty()); }
  }

As discussed in Chapter 7, the FieldManager is used to determine whether any child objects contained by this object have been changed.

Note

If you do not use managed backing fields to store your child object references, you'll need to override this method to also check the IsDirty status of your child objects. I recommend always using managed backing fields for child references so you don't need to worry about this issue.

Where IsSelfDirty provides the status for one object, IsDirty is often more useful because it provides the status for the object and all its child objects.

IsSelfValid

An object is considered to be valid if it has no currently broken validation rules. The Csla.Validation namespace is covered in Chapter 11 and provides management of the business rules. The IsSelfValid property merely exposes a flag indicating whether the object currently has broken rules or not.

There's also an IsValid property that reflects whether the current object, or any of its child objects, are invalid.

Here's the IsSelfValid code, which returns a value for only this object, not counting its child objects:

[Browsable(false)]
  public virtual bool IsSelfValid
  {
    get { return ValidationRules.IsValid; }
  }

As with IsSelfDirty, this property is marked with the Browsable attribute so data binding defaults to ignoring the property.

There are no methods to directly control whether an object is valid. The validity of an object is managed by the business and validation rules subsystem discussed in Chapter 11. However, this method is virtual, so it is possible to replace or extend the way CSLA .NET manages the concept of validity if you so desire.

IsValid

While IsSelfValid indicates the validity of a specific object, the true validity of an object must also include the validity of any child objects. Even if an object itself is valid, if it contains invalid child objects, the object graph as a whole must be considered invalid.

The IsValid property combines the IsSelfValid result with the IsValid results of any child objects contained by this object:

[Browsable(false)]
  public virtual bool IsValid
  {
    get
    {
      return IsSelfValid && (_fieldManager == null || FieldManager.IsValid());
    }
  }

Again, the FieldManager is used to determine whether any child objects are invalid. The result is that a business object is considered valid only if it, and all its child objects, are valid.

IsSavable

An object should only be saved to the database if it is valid and its data has changed and there are no outstanding asynchronous validation rules executing and the current user is authorized to update the object.

The IsValid property indicates whether the object is valid, and the IsValidating property indicates whether there are any outstanding asynchronous validation rules executing. The IsDirty property indicates whether the object's data has changed. The authorization rules subsystem is discussed in Chapter 12.

The IsSavable property is a simple helper to combine these concepts into a single property:

[Browsable(false)]
  public virtual bool IsSavable
  {
    get
    {
      bool auth;
      if (IsDeleted)
        auth = Csla.Security.AuthorizationRules.CanDeleteObject(this.GetType());
      else if (IsNew)
        auth = Csla.Security.AuthorizationRules.CanCreateObject(this.GetType());
      else
        auth = Csla.Security.AuthorizationRules.CanEditObject(this.GetType());
      return (auth && IsDirty && IsValid && !ValidationRules.IsValidating);
    }
  }

The authorization code is interesting because it relies on the state of the object to decide which type of authorization is required. For example, if the IsDeleted property returns true, the object is marked for deletion and so the delete authorization is checked.

Assuming the user is authorized, the code makes sure the object has been changed and is valid and that there are no outstanding asynchronous validation rules executing.

The primary purpose for this property is to allow a UI developer to enable or disable a Save button (or similar UI element) such that the button is only enabled if the object can be saved. For example, it is used by the CslaDataProvider control to automatically enable and disable controls in WPF, as discussed in Chapter 19.

IsDeleted

The CSLA .NET framework provides for deferred or immediate deletion of an object. The immediate approach directly deletes an object's data from the database without first loading the object into memory. It requires prior knowledge of the object's primary key value(s) and is discussed in Chapters 4 and 5.

The deferred approach requires that the object is loaded into memory. The user can then view and manipulate the object's data and may decide to delete the object, in which case the object is marked for deletion. The object is not immediately deleted but rather it is deleted if and when the object is saved to the database. At that time, instead of inserting or updating the object's data, it is deleted from the database.

This approach is particularly useful for child objects in a collection. In such a case, the user may be adding and updating some child objects at the same time as deleting others. All the insert, update, and delete operations occur in a batch when the collection is saved to the database.

Whether an object is marked for deletion is tracked by the _isDeleted field and exposed through an IsDeleted property:

private bool _isDeleted;

  [Browsable(false)]
  public bool IsDeleted
  {
    get { return _isDeleted; }
  }

Like the other status properties, this one cannot be data bound.

MarkDeleted Method

As with IsSelfDirty, there's a protected method to allow the object to be marked for deletion when necessary:

protected void MarkDeleted()
  {
    _isDeleted = true;
    MarkDirty();
  }

Of course, marking the object as deleted is another way of changing its data, so the MarkDirty() method is called to indicate that the object's state has been changed.

Delete and DeleteChild Methods

The MarkDeleted() method is called from the Delete() and DeleteChild() methods. The Delete() method is used to mark a non-child object for deferred deletion, while DeleteChild() is called by a parent object (such as a collection) to mark the child object for deferred deletion:

public void Delete()
  {
    if (this.IsChild)
      throw new NotSupportedException(Resources.ChildDeleteException);

    MarkDeleted();
  }
internal void DeleteChild()
  {
    if (!this.IsChild)
      throw new NotSupportedException(Resources.NoDeleteRootException);

    MarkDeleted();
  }

Both methods do the same thing: call MarkDelete(). But Delete() is scoped as public and can only be called if the object is not a child object (a topic covered in the discussion about parent and child object behaviors in Chapter 9). Conversely, DeleteChild() can only be called if the object is a child. Since it is intended for use by BusinessListBase, it is scoped as internal.

At this point, you should have a good understanding of the various object status values managed by BusinessBase and BusinessListBase.

Conclusion

In this chapter, I continued discussing the implementation of the CSLA .NET framework. This chapter covered the object status values maintained by editable objects, allowing the rest of CSLA .NET, your business code, and your UI code to interact with your objects in a standardized manner.

All editable objects maintain the following status values:

  • IsSelfDirty

  • IsDirty

  • IsSelfValid

  • IsValid

  • IsNew

  • IsSavable

  • IsDeleted

Some of these values are important to parent-child relationships, as discussed in Chapter 9. Others rely on validation and authorization, as discussed in Chapters 11 and 12. And many of them are used by the data portal as discussed in Chapter 15.

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

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