Blogs

Gary's Blog

XPO Cookbook #4 – Persisting an Existing Hierarchy by Implementing Interfaces

     

Problem

You want to use XPO as an ORM tool. You have an existing hierarchy of business objects that you wish to persist, but you want a more flexible solution than that provided by using attributes.

Solution

Assume we have the very simplistic business object below:-

public class Car {

    private string _Make;
    public string Make {
        get { return _Make; }
        set {
            _Make = value;
        }
    }

    private string _Model;
    public string Model {
        get { return _Model; }
        set {
            _Model = value;
        }
    }
    
    public Car() {
        
    }
}

After the required modifications, and the addition of some code to test it, the example now looks like this:-

using System;
using DevExpress.Xpo;
using DevExpress.Xpo.Metadata;
using System.Collections;
using DevExpress.Data.Filtering;


class Program {
    static void Main(string[] args) {
        //GS - Test our hierarchy is now persistent

        //GS - Create and save a Car
        using (UnitOfWork uow = new UnitOfWork()) {
            Car car = new Car(uow) {
                Make = "Ford",
                Model = "Fiesta"
            };
            car.Save();
            uow.CommitChanges();
        }

        //GS - Read back the car
        using (UnitOfWork uow = new UnitOfWork()) {
            Car car = FindCarByMake("Ford", uow);
            Console.WriteLine(String.Format(
                "Make = {0}, Model = {1}", 
                car.Make, 
                car.Model));
        }

        //GS - Delete the Car
        using (UnitOfWork uow = new UnitOfWork()) {
            Car car = FindCarByMake("Ford",uow);
            car.Delete();
            uow.CommitChanges();
        }

        //GS - Prove the car has been deleted
        using (UnitOfWork uow = new UnitOfWork())
        {
            Car car = FindCarByMake("Ford",uow);
            if(car == null){
                Console.WriteLine("Car has been deleted!");
            }

            else{
                Console.WriteLine("Something went wrong!");
            }
        }
    }

    private static Car FindCarByMake(string make, UnitOfWork uow) {
        BinaryOperator bo = new BinaryOperator("Make", make);
        return uow.FindObject<Car>(bo);
    }
}

//GS - Create an existing hierarchy
public class Car : IXPObject, IComparable {

    private string _Make;
    public string Make {
        get { return _Make; }
        set {
            _Make = value;
            OnChanged("Make");
        }
    }

    private string _Model;
    public string Model {
        get { return _Model; }
        set {
            _Model = value;
            OnChanged("Model");
        }
    }
    
    public Car() {
        
    }

    //GS - Provide an Id for our object
    private int _Id;
    [Key(true)]
    public int Id {
        get { return _Id; }
        set {
            _Id = value;
            OnChanged("Id");
        }
    } 

    //GS - Provide a backing store for Session and ClassInfo
    private Session _Session;
    private XPClassInfo _ClassInfo;

    //GS - Provide a constructor for our object that takes a Session object
    public Car(Session session) {
        _Session = session;
        _ClassInfo = session.Dictionary.GetClassInfo(this.GetType());
        if (!IsLoading)
            AfterConstruction();
    }       

    //GS - Write any post construction code here
    private void AfterConstruction() { }

    //GS - Create a backing store and a property for the IsLoading bool
    bool _IsLoading;
    [NonPersistent]
    public bool IsLoading { get { return _IsLoading; } }
    
    //GS - Define the Changed event
    public event ObjectChangeEventHandler Changed;

    //GS - Raise the Change event
    protected void RaiseChangeEvent(ObjectChangeEventArgs args) {
        if (Changed != null)
            Changed(this, args);
    }

    public void OnChanged() {
        if (!IsLoading)
            RaiseChangeEvent(new ObjectChangeEventArgs(ObjectChangeReason.Reset));
    }

    protected void OnChanged(string propertyName) {
        if (!IsLoading)
            RaiseChangeEvent(
                new ObjectChangeEventArgs(ObjectChangeReason.PropertyChanged,
                    propertyName));
    }

    //GS - Provide general persistence methods
    public void Save() {
        Session.Save(this);
    }

    public void Delete() {
        Session.Delete(this);
        RaiseChangeEvent(new ObjectChangeEventArgs(ObjectChangeReason.Delete));

    }

    public void Reload() {
        Session.Reload(this);
        RaiseChangeEvent(new ObjectChangeEventArgs(ObjectChangeReason.Reset));
    }    

    #region IXPObject Members

    public void OnDeleted() {}

    public void OnDeleting() {}

    public void OnLoaded() {
        _IsLoading = false;
    }

    public void OnLoading() {
        _IsLoading = true;
    }

    public void OnSaved() {}

    public void OnSaving() {}

    #endregion

    #region IXPClassInfoProvider Members

    public XPClassInfo ClassInfo {
        get { return _ClassInfo; }
    }

    #endregion

    #region IXPDictionaryProvider Members

    public XPDictionary Dictionary { get { return Session.Dictionary; } }

    #endregion

    #region ISessionProvider Members

    public Session Session {
        get { return _Session; }
    }

    #endregion

    #region IDataLayerProvider Members

    public IDataLayer DataLayer {get {return Session.DataLayer;}}

    #endregion

    #region IComparable Members

    public int CompareTo(object obj) {
        return Comparer.Default.Compare(Session.GetKeyValue(this),
          Session.GetKeyValue(obj as IXPSimpleObject));
    }

    #endregion
}

Discussion

To make an existing hierarchy persistent, using interfaces, the first thing we do is to modify the the class definition so that it implements the IXPObject and IComparable interfaces. Then we add a property to hold the Id of the object (note the use of the key attribute to specify that the key should be auto generated), and implement change notification in that property and the other properties already defined in our class.

Following that we create a backing store for the Session and ClassInfo objects and implement a constructor, for our class, that sets those fields. We also, at this point, provide a hook for developers to place any intialisation code that must be run post construction.

The next thing we do is to code the change notification that we called from our properties when we made those changes above, before coding the general persistence methods of Save(), Reload() and Delete().

This completes the work we have to do on our own class, and all that remains for us to do is to implement the methods required by the interfaces.

Technorati tags:
Digg This
Published Aug 27 2008, 01:50 PM by Gary Short (DevExpress)
Filed under:
Technorati tags: XPO Cookbook
Bookmark and Share

Comments

 

Brandon Maness said:

I think the FindCarByMake method BinaryOperator declaration should be changed from this:

BinaryOperator bo = new BinaryOperator("Make", "Ford");

to this:

BinaryOperator bo = new BinaryOperator("Make", make);

FYI: You guys are doing a great job of providing documentation and training (For free no less!). Keep up the good work.

August 27, 2008 10:52 AM
 

Gary Short (Developer Express) said:

@Brandon, yes it should, good catch. A nice cut and paste error that my tests didn't catch. Thanks!

Glad you like the posts. We're always trying to make our customer's lives easier.

August 27, 2008 11:10 AM
 

Alex Hoffman said:

I think the problem this is trying to solve is more correctly stated as:-

... you have an existing hierarchy of business objects that you wish to persist, but you do not wish to (or cannot) inherit your business objects from an XPO base class.

-- Alex Hoffman

expressapp.blogspot.com

August 27, 2008 6:47 PM
 

Gary Short (Developer Express) said:

@Alex, yes stating the problem that way is more accurate, however, that also accurately states the problem in #3 as well. As #3 and #4 are two ways of solving the same underlying problem, I had to state the problem in terms that differentiated them.

August 28, 2008 4:38 AM
 

Hernan Chamvi said:

thats new,delete, find but

update in the database?

July 7, 2009 1:29 AM
More from DevExpress
Live Chat
Have a pre-sales question?
Need assistance with your evaluation?
We are here to help.
Chat is one of the many ways you can contact members of the DevExpress Team. We are available Monday-Friday between 8:30am and 5:00pm Pacific Time.
If you need additional product information, require pre-sales assistance, or want help with your order, write to us at info@devexpress.com or call us at
+1 (818) 844-3383.