Deborah's Developer MindScape






         Tips and Techniques for Web and .NET developers.

July 21, 2009

Building a Business Object Base Class

Filed under: C#,Data Binding,OOP,VB.NET @ 1:49 pm

If you are building applications in .NET to manage data for a business, you are most likely creating business object classes. Depending on the business, these classes could include Customer, Product, Order, Invoice, PurchaseOrder, Employee, TimeCard and so on.

A simple sample Customer class is shown here.

There are some features that all business objects need to support. For example:

  • Entity State: The business object needs to track whether it is new, updated, or deleted so it can make the appropriate change to the database.
  • Validation: Is the current data defined for the business object valid?
  • Save: Saves the changes to the database.

Notice that data retrieval functionality is not included in this list. That is because in most cases, you may never retrieve a single business object. Rather, you would retrieve a set of them. For example, all active orders or all customers with overdue invoices. So the retrieve functionality is not included in the class that manages a single object. (Most on this in a later post.)

Instead of repeating this common functionality in each business object, it makes sense to build a base class. Each business object can then inherit from this base class to share this common functionality.

So let’s get started.

In C#:

using System;
using System.ComponentModel;

public abstract class BoBase :
        IDataErrorInfo,
        INotifyPropertyChanged
{

}

In VB:

Imports System.ComponentModel

Public MustInherit Class BOBase
    Implements IDataErrorInfo
    Implements INotifyPropertyChanged

End Class

The class is abstract (MustInherit in VB) to indicate that it is meant to be a base class and not to be used on its own. An abstract class cannot be instantiated directly, so no other code can create an instance of the class.

The class then implements two interfaces:

  • IDataErrorInfo: This interface provides error information that the user interface can use to report validation errors to the user. It works well with the ErrorProvider control provided in WinForms and supports ASP.NET and WPF features.
  • INotifyPropertyChanged: This interface ensures that the user interface is notified when a property value changes, keeping your business object and user interface in sync.

We’ll look at the implementation of these interfaces shortly.

First, define the valid set of entity states.

In C#:

protected internal enum EntityStateType
{
    Unchanged,
    Added,
    Deleted,
    Modified
}

In VB:

Protected Friend Enum EntityStateType
    Unchanged
    Added
    Deleted
    Modified
End Enum

The Enum is declared Protected because the entity state should only be accessible from the business object itself. However, Internal (Friend in VB) was added so that related objects (such as Order and OrderLineItem) could reference the related object state.

The business object state is retained using an EntityState property.

In C#:

protected EntityStateType EntityState { get; private set; }

In VB:

Private _EntityState As EntityStateType
Protected Property EntityState() As EntityStateType
    Get
        Return _EntityState
    End Get
    Private Set(ByVal value As EntityStateType)
        _EntityState = value
    End Set
End Property

This property is protected, but the setter is private. So the business objects that inherit from this class can read the entity state, but only the base class can set the value.

Additional properties provide a way to get the entity’s state in an easier fashion. These properties are not required, but they make the base class a little easier to use.

In C#:

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsDirty
{
    get { return this.EntityState != EntityStateType.Unchanged; }
}

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsNew
{
    get { return this.EntityState == EntityStateType.Added; }
}

In VB:

<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsDirty() As Boolean
    Get
        Return Me.EntityState <> EntityStateType.Unchanged
    End Get
End Property

<BindableAttribute(False)> _
<BrowsableAttribute(False)> _
Public ReadOnly Property IsNew() As Boolean
    Get
        Return Me.EntityState = EntityStateType.Added
    End Get
End Property

The IsDirty and IsNew properties are public, so they can be accessed from anywhere. They are marked with two attributes:

  • Bindable: Defines whether the property should be used for binding. In this case the value is false because the user interface should not be able to bind to this property.
  • Browsable: Defines whether the property should be displayed in the Properties window. Again, the value is false.

Two other properties handle the validation.

In C#:

[BindableAttribute(false)]
[BrowsableAttribute(false)]
public bool IsValid
{
    get { return (ValidationInstance.Count == 0); }
}

protected Validation ValidationInstance { get; set; }

In VB:

<Bindable(False)> _   
<BrowsableAttribute(False)> _
Public ReadOnly Property IsValid() As Boolean
    Get
        Return (ValidationInstance.Count = 0)
    End Get
End Property

Private _ValidationInstance As Validation
Protected Property ValidationInstance() As Validation
    Get
        Return _ValidationInstance
    End Get
    Private Set(ByVal value As Validation)
        _ValidationInstance = value
    End Set
End Property

Again, the public property is marked with the Browsable and Bindable attributes. The ValidationInstance property retains an instance of the Validation class for the business object. The code for the Validation class is here.

The constructor creates an instance of the Validation class.

In C#:

protected BoBase()

   ValidationInstance = new Validation();
}

In VB:

Protected Sub New()
    ValidationInstance = New Validation
End Sub

The following is the implementation of IDataErrorInfo.

In C#:

#region IDataErrorInfo Members

[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string Error
{
    get { return ValidationInstance.ToString(); }
}

[BrowsableAttribute(false)]
[BindableAttribute(false)]
public string this[string columnName]
{
    get { return ValidationInstance[columnName]; }
}

#endregion

In VB:

#Region " Properties required by the IDataErrorInfo"

    <Bindable(False)> _   
    <BrowsableAttribute(False)> _
    Public ReadOnly Property [Error]() As String _
           Implements IDataErrorInfo.Error
        Get
            Return ValidationInstance.ToString
        End Get
    End Property

    <BrowsableAttribute(False)> _
    <Bindable(False)> _
    Default Protected ReadOnly Property Item(ByVal columnName _ 
           As String) As String _ 
           Implements IDataErrorInfo.Item
        Get
            Return ValidationInstance.Item(columnName)
        End Get
    End Property

#End Region

The Error property uses the overridden ToString method of the validation class to return the full list of validation errors.

The Item property provides access to the validation errors given a property name. This property is implemented as the class indexer in C#.

The following in the implementation of INotifyPropertyChanged.

In C#:

#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
#endregion

In VB:

#Region " Events required by INotifyPropertyChanged"
    Public Event PropertyChanged(ByVal sender As Object, _
        ByVal e As System.ComponentModel.PropertyChangedEventArgs) _
        Implements INotifyPropertyChanged.PropertyChanged
#End Region

This interface only defines a single event. This event should be raised whenever the data is changed.

Since every business object will have unique requirements for the save operation, the SaveItem method is not implemented. Rather it is defined as abstract.

In C#:

public abstract Boolean SaveItem();

In VB:

Public MustOverride Function SaveItem() As Boolean

Defining an abstract (MustOverride in VB) SaveItem method ensures that every business object has a SaveItem method.

Finally, since the EntityState property setter is private, the base class needs a method to set the entity state.

In C#:

protected internal void SetEntityState(EntityStateType newEntityState)
{
    switch (newEntityState)
    {
        case EntityStateType.Deleted:
        case EntityStateType.Unchanged:
        case EntityStateType.Added:

            this.EntityState = newEntityState;
            break;

    default:
            if (this.EntityState == EntityStateType.Unchanged)
                this.EntityState = newEntityState;
            break;
    }
}

protected internal void SetEntityState(EntityStateType newEntityState, string propertyName)
{
    SetEntityState(newEntityState);
    if (PropertyChanged != null)
        PropertyChanged(this,
              
new PropertyChangedEventArgs(propertyName));
}

In VB:

Protected Friend Sub SetEntityState(ByVal dataState As EntityStateType)
    SetEntityState(dataState, Nothing)
End Sub

Protected Friend Sub SetEntityState( _
ByVal newEntityState As EntityStateType, ByVal propertyName As String)
    Select Case newEntityState
        Case EntityStateType.Deleted, _
             EntityStateType.Unchanged, _
             EntityStateType.Added

            Me.EntityState = newEntityState

        Case Else
            If Me.EntityState = EntityStateType.Unchanged Then
                Me.EntityState = newEntityState
            End If
    End Select

    If Not String.IsNullOrEmpty(propertyName) Then
        Dim e As New PropertyChangedEventArgs(propertyName)
        RaiseEvent PropertyChanged(Me, e)
    End If
End Sub

The SetEntityState method has two overloads. The first is used when changing the entity state in general and the second is used when changing the entity state because a specific property is changed.

For example, when setting an object as Unchanged, Added, or Deleted, it does not matter which property was changed. But when a particular property is changed, the code must also raise the PropertyChanged event.

In this case, the C# and VB code was implemented differently. In the C# code, the code to set the entity state is in the first overload. The second overload then calls the first and then raises the event.

In the VB code, the first overload simply calls the second overload. The second overload then sets the entity state and then raises the event as appropriate. You can use either technique in either language.

That’s it! You now have a base class that can be used with any business object class. If you have any other common functionality, you can add it to this base class.

Here is an example of how you use this base class.

In C#:

public class Customer : BoBase
{

    private string _LastName;
    public string LastName
    {
        get { return _LastName; }
        set
        {
            if (_LastName == null || _LastName != value)
            {
              string propertyName = "LastName";
              _LastName = value;

              // Validate the last name
              ValidationInstance.ValidateClear(propertyName);
              ValidationInstance.ValidateRequired(propertyName, value);

              SetEntityState(EntityStateType.Modified, propertyName);
            }
        }
    }

    public override Boolean SaveItem()
    {
        // TODO: Add code here
    }

}

In VB:

Public Class Customer
    Inherits BoBase

    Private _LastName As String
    Public Property LastName() As String
        Get
            Return _LastName
        End Get
        Set(ByVal value As String)
            If _LastName Is Nothing OrElse _LastName <> value Then
              Dim propertyName As String = "LastName"
              _LastName = value

              ‘ Validate the last name
              ValidationInstance.ValidateClear(propertyName)
              ValidationInstance.ValidateRequired(propertyName, value)

              SetEntityState(EntityStateType.Modified, propertyName)
            End If
        End Set
    End Property

    Public Overrides Function SaveItem() As Boolean
        ‘ TODO: Add code here
    End Function
End Class

Notice how the class statement includes the syntax to inherit from BoBase. The LastName property uses the ValidationInstance defined in the base class to validate the value. It also sets the entity state when the last name is changed.

Enjoy!

13 Comments

  1.   Steave — October 22, 2009 @ 2:38 am    Reply

    Nice post as for me. It would be great to read something more about this topic.

  2.   vb-dev — April 17, 2015 @ 12:00 pm    Reply

    Deborah,

    The SetEntityState method doesn’t handle a state of Modified.

  3.   vb-dev — April 17, 2015 @ 12:57 pm    Reply

    …Especially when you forget to set state to Unchanged in your Fill or Create method on your business object…never mind!

RSS feed for comments on this post. TrackBack URI

Leave a comment

© 2019 Deborah's Developer MindScape   Provided by WPMU DEV -The WordPress Experts   Hosted by Microsoft MVPs