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.   mendicant — July 22, 2009 @ 3:12 pm    Reply

    Typical MS tightly coupled approach. You’re going to be writing a TON of data access for each object. Use NHibernate and you can have state AND persistence solved out of the box.

    And if the ActiveRecord pattern is really what you want, there’s persistence frameworks for that too.

    Even in the .Net world, we are so far beyond ever having to write all this code manually. This is solving a problem that has already been solved.

  2.   DeborahK — July 22, 2009 @ 4:06 pm    Reply

    Hi mendicant –

    Thanks for coming by the blog. The purpose of putting this into a base class is so you never have to write it again.

    And I have additional methods (not shown here) that automatically populate my business objects and update the database based on the business object properties. So I don’t write any data access for any objects.

    I only need the stored procedures, and I have written a code generator to do that for me. 🙂

  3.   Mark — July 22, 2009 @ 4:18 pm    Reply

    … and with Spring.Net, AOP, etc.

    I used to try to this in VB. The overhead was just not worth the effort. At most, implement the property change listeners and use a service oriented architecture.

  4.   mendicant — July 22, 2009 @ 5:10 pm    Reply

    Hi Deborah,

    Your base class provides:

    public abstract Boolean SaveItem();

    Which means every item needs to override it. Now I’ve got to write code in every single class that implements it. Do your automatic methods exist on each item?

    As for the stored procs, I’m not ready to even begin down that road again.

    Thanks for taking my first comment so nicely, I wasn’t very rational when I wrote it.

  5.   Mark — July 22, 2009 @ 7:34 pm    Reply

    Deborah, You will still need “Data access” code even with Stored Procs. Do you mean that is common code?

    I am with mendicant on SPs. I’ll try not to get started on them too. 🙂

  6.   Harry — July 22, 2009 @ 10:06 pm    Reply

    +1 to mendicant for acknowledging the harsh tone in the comment
    +1 to Deborah for trying so hard to come up with this even people say the problem has been solved. So what, next time when you actually need to use NHibernate you are that much wiser than those who didn’t try
    -1 to me for not providing any value in this comment ….

  7.   DeborahK — July 23, 2009 @ 12:54 pm    Reply

    Hi mendicant –

    In the simple sample code I have posted on my Web site, I do indeed have the SaveItem code manually written into each business object. (But in my sample code, there is only one business object so not a big deal.)

    In my *real* applications, I have some code in my base class (not shown here) that automatically populates the stored procedure parameters. So the SaveItem method in each business object only needs to define the appropriate stored procedure and reset the entity state.

    I have also worked with some very basic Linq to SQL with POCO. I plan to do a post on that soon.

  8.   Julian — July 24, 2009 @ 2:08 am    Reply

    Hi Deborah

    Your real application version base class sounds interesting could you show an example.

    Julian

  9.   Eric Smith — August 8, 2009 @ 8:03 am    Reply

    This all sounds pretty close to an old incarnation of CSLA.NET (We’re talking .NET 1.1 days)—I’m wondering if Rocky has moved beyond defining a base class, and then having to hand-code state management into property setters of derived classes?

    We considered this approach in the project that we are working on but have moved onto a more flexible (but considerably more advanced) means of managing all that plumbing code that you’ve baked into your base class, viz., using AOP.

    At the end of the day, developers should be concentrating on solving business problems, not writing plumbing and although your base class does take alot of that away, it does mean that you’re not stuck deriving from it. Also, you still need to hand-code that state management and notification stuff into your derived classes—why not just use an AOP framework to bolt that on at runtime?

    It does amaze me that after so many years of object orientation and applying MVC goodness, we still haven’t got to a place where this is all automagically dealt with by the underlying framework.

  10.   Yuri — August 8, 2009 @ 2:55 pm    Reply

    I guess POCO based approach using NHIbernate is way better than implementing any business logic in your BO’s even if it is only SaveItem…

    If you have a big project with hundreds of entities you will quickly noticed how unmaintainable it gets

RSS feed for comments on this post. TrackBack URI

Leave a comment

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