Click here to Skip to main content
Click here to Skip to main content

Discard changes in business objects

By , 15 Nov 2006
 

Sample Image - IEditableObject.png

Prerequisites

In order to run the samples and follow the article, you must have Visual Studio 2005 and C# installed.

Introduction

The most convenient and advanced approach in working with data is to use business objects that encapsulate business logic. Then these objects make calls to the Data Access Layer to persist information. Business objects can be created by hand or generated by the ORM tool.

Once we have the classes ready and hydrated (data is extracted from the persistence layer), we show objects in some grids and give the user the ability to edit objects in forms. So far so good. Thanks to Microsoft for the Binding class and the ability to set bindings once and leave the .NET Framework to manage data exchange between the Form's controls and business objects.

The Problem

As a typical case, the user selects a single object from the grid and opens the form for editing. Modal form is shown and the business object's properties are bound to the form's controls. By using data binding, we are able to commit the changed data from controls to the object according to the DataSourceUpdateMode enumeration:

  • Never - Data source is never updated and values entered into the control are not parsed, validated or re-formatted.
  • OnPropertyChanged - (default) Data source is updated whenever the value of the control property changes.
  • OnValidation - Data source is updated when the control property is validated.

This gives us some control "when" the data source is updated. What if we want to discard changes?

Resolutions

At this point we have two solutions:

  1. Validate() Method

    The first one is not very elegant but is more obvious - we could use DataSourceUpdateMode.OnValidation mode to update the data source and to call Form.Validate() method only if the user wants to confirm data. If data has to be discarded Validate() method is not called. This introduces a consistency problem: what if some of controls are validated (and source is updated) but the rest are not? We, of course, ask the user to fix the information entered. But if at this point, the user decides to cancel object editing, we are in trouble because some fields are already updated.

  2. IEditableObject interface

    Fortunately there is a way to achieve this with .NET tools. This is the IEditableObject interface. This interface declares three public methods:

    • BeginEdit() - Begins an edit on an object.
    • CancelEdit() - Discards changes since the last BeginEdit call.
    • EndEdit() - Pushes changes since the last BeginEdit or IBindingList.AddNew call into the underlying object.

    There is another good news - Microsoft controls support and works well with this interface. All we have to do is to implement this interface in our business objects. This requires some work from us but gives us enough flexibility. My personal preference is to create a base class of all business objects which implements common logic including IEditableObject methods.

Generic IEditableObject implementation

Provided here is a generic implementation which could easily be added to the base class and included in existing projects. This implementation preserves values for all public properties do not keep an eye on all fields, private properties, public properties without set ancestor

First we need some key/value pair collection which will hold the original values for us.

Store original values

This is the flag that the object is in edit mode too.

Hashtable props = null;

BeginEdit()

This method stores the current values for future restoration using reflection.

public void BeginEdit()
{
    //exit if in Edit mode
    //uncomment if  CancelEdit discards changes since the 
    //LAST BeginEdit call is desired action
    //otherwise CancelEdit discards changes since the 
    //FIRST BeginEdit call is desired action
    //if (null != props) return;

    //enumerate properties
    PropertyInfo[] properties = (this.GetType()).GetProperties
                (BindingFlags.Public | BindingFlags.Instance);

    props = new Hashtable(properties.Length - 1);

    for (int i = 0; i < properties.Length; i++)
    {
        //check if there is set accessor
        if (null != properties[i].GetSetMethod())
        {
            object value = properties[i].GetValue(this, null);
            props.Add(properties[i].Name, value);
        }
    }
}

CancelEdit()

This method restores old values.

public void CancelEdit()
{
    //check for inappropriate call sequence
    if (null == props) return;

    //restore old values
    PropertyInfo[] properties =(this.GetType()).GetProperties
        (BindingFlags.Public | BindingFlags.Instance);
    for (int i = 0; i < properties.Length; i++)
    {
        //check if there is set accessor
        if (null != properties[i].GetSetMethod())
        {
            object value = props[properties[i].Name];
            properties[i].SetValue(this, value, null);
        }
    }

    //delete current values
    props = null;
}

EndEdit()

This method just deletes stored values (and our flag).

public void EndEdit()
{
    //delete current values
    props = null;
}

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

Galin Iliev [Galcho]
Software Developer (Senior)
Bulgaria Bulgaria
Member
Galin has entered in area of software development in end of 20th centutry with Pascal and assembler. His career went through C++, Visual Basic, ASP, InstallShiled, Wise and etc...
For last four years he has been involved in developing all kind of software in .NET.
He holds MCSD, MCAD, MCSD.NET, MCPD and MCT certificates.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionAlternate approach?memberYumashin Alex6 May '10 - 1:08 
What about simply reloading the same object (from DAL) and rebinding it to the form? this will also discard all changes, and this is much easier than all that reflection stuff.
GeneralBeginEdit EndEditmemberjansen842128 Dec '09 - 22:50 
Hi,
 
What a great a example!
I've a question about it.
 
I've placed a messagebox in BeginEdit and EndEdit.
 
When I click in the mainform on a row every time it invokes BeginEdit and EditEdit.
 
In the example everything is readonly.
 
How can I disable these this.
 
I only want to edit in the customform.
 
Kind regards,
Jan
GeneralRe: BeginEdit EndEditmemberjansen842110 Jan '10 - 4:03 
Hi,
 
Why does everybody want's to post an item but don't update it!?
What a disadvantage of codeproject!
 
Regards,
Jan
QuestionObject with a listmemberRui Figueiredo14 May '09 - 0:39 
Hi
I like the solution that you found to discard changes but I've found a litle issue.
If you put a List inside your object it doesn't discard the changes in the list.
 
Regards, Rui Figueiredo
GeneralAlso Some IdeasmemberIzzet Kerem Kusmezer8 May '08 - 8:00 
properties[i].GetSetMethod is checked in the BeginEdit Method, but there can be properties which have only a setter and not a getter method , for example WriteOnly Indicated Properties in VB.Net.
Instead of this maybe u can use properties[i].CanRead and properties[i].CanWrite property values to check for it.
 
For example i have a simple method which called copyobjecttoobject like following:
 

Public Shared Function CopyObjectToObject(source as T,target as T) as Boolean
Dim wholeProperties as PropertyInfo() = GetType(T).GetProperties()
For Each tempProp as PropertyInfo in wholeProperties
if (tempProp.CanRead AndAlso tempProp.CanWrite) then
tempProp.SetValue(target,tempProp.GetValue(source,Nothing),Nothing)
end if
Next
Return True
End Function

GeneralSimple And Super Quick ImplementationmemberIzzet Kerem Kusmezer8 May '08 - 7:35 
I liked it , i loved it, i admire it Smile | :)
 
Quick and nice solution, 5 points from me Smile | :)
GeneralTwo feature suggestionsmemberGoljan24 Mar '08 - 10:52 
Great idea - I'm using variation in my own code. The cost is what's a concern for me (and should probably be ignored until profiled), but I have considered using the following to speed things up:
 
1. The first time a class of type A is processed via BeginEdit, the information should somehow be cached, as future calls are redundantly invoking expensive reflection calls. Similar to the way the .net framework validation framework on CodePlex works.
 
2. Attributes to control Edit behavior, e.g. ability to specify on a class what should be saved (properties, field or both, public, private, etc.), ability to ignore a specific field/property, the ability to specify a specific field's value should be cached using Clone or perhaps a copy constructor (to handle deep copy semantics for references).
 
I'm attemping to do the above and will let you know how it goes.
GeneralBeginEdit also for items selected in DataGridViewmemberRoberto Ferraris4 Sep '07 - 22:18 
First of all thanks for the idea about using reflection to backup data.
 
I've noticed that when data are selected in the DataGridView the BeginEdit is called, even if ReadOnly property is set to True for the DataGrid.
This is little strange, and causes that when the BeginEdit is invoked from the edit form then the props variabile is already initialized.
I think that this behaviour can cause some trouble.
Do you have any additional information about this, or do you know a way to bypass it?
 

 
Roberto
Generalcloning business objectmemberPrimadi2 Sep '07 - 17:23 
what about cloning the business object, so you can query old value and new value in the business object validation. you can also rollback business object with this technique.
GeneralNice approach ..memberfreefighter20 Aug '07 - 5:14 
these interfaces are useful ..
the idea of having a generic prototype by reflection is good one, although the cost of computing Wink | ;)
sure will use it Wink | ;)

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web01 | 2.6.130523.1 | Last Updated 16 Nov 2006
Article Copyright 2006 by Galin Iliev [Galcho]
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid