xpo-2109-create-a-data-model-add-persistence-to-an-existing-hierarchy-change-the-base-inheritance.md
Changing the base inheritance of classes requires the IXPObject and IComparable interfaces to be implemented. Compared to the other option of adding persistence to an existing class hierarchy which is discussed in the Add Persistence to an Existing Hierarchy: Session-less Persistent Objects topic, changing the base inheritance of classes represents a more flexible technique for manipulating persistence objects (such as handling the object’s initialization, loading, saving or deletion) and extending their functionality (such as adding collection properties).
Below is a step-by-step guide as to what you need to do.
public class PersistentObject: SuperObject, IXPObject, IComparable {
Public Class PersistentObject
Inherits SuperObject
Implements IXPObject
Implements IComparable
Session-specific constructor declarations are required. The session and classInfo variables are initialized here for future use by the corresponding properties (see IXPSimpleObject implementation below). Additional construction logic can be implemented in the PersistentBase.AfterConstruction method.
public PersistentObject(Session session) {
this.session = session;
this.classInfo = session.Dictionary.GetClassInfo(this.GetType());
if(!session.IsObjectsLoading)
AfterConstruction();
}
// Implement additional construction logic here
protected void AfterConstruction() {
if (Session is UnitOfWork && ClassInfo.IsPersistent) {
Session.Save(this);
}
}
Public Sub New(ByVal session As Session)
Me._session = session
Me._classInfo = session.Dictionary.GetClassInfo(Me.GetType())
If Not Me._session.IsObjectsLoading Then
AfterConstruction()
End If
End Sub
' Implement additional construction logic here
Protected Sub AfterConstruction()
If TypeOf Session Is UnitOfWork AndAlso ClassInfo.IsPersistent Then
Session.Save(Me)
End If
End Sub
To distinguish between the states of persistent objects stored in a database, an object identity value should be used. It’s used to address a specific object’s state. In the following code example one possible implementation of identity values is shown. In a database it will be represented by an auto-generated key field of the integer type.
int persistentID;
[Key(AutoGenerate = true)]
public int Oid {
get { return persistentID; }
set {
persistentID = value;
OnChanged(nameof(Oid));
}
}
Dim persistentID As Integer
<Key(AutoGenerate:=True)> _
Public Property Oid() As Integer
Get
Return persistentID
End Get
Set(ByVal Value As Integer)
persistentID = Value
OnChanged(NameOf(Oid))
End Set
End Property
In order to manipulate persistent objects, the IXPSimpleObject and DevExpress.Xpo.IXPObjectWithChangedEvent events should be implemented. Whenever changes to the object are made, the IXPObjectWithChangedEvent.Changed event should be fired. The type of changes are specified by the ObjectChangeEventArgs instance as shown below.
Session session;
XPClassInfo classInfo;
public Session Session { get { return this.session; } }
public XPClassInfo ClassInfo { get { return this.classInfo; } }
bool changed = false;
public IDataLayer DataLayer { get { return session.DataLayer;} }
public XPDictionary Dictionary { get { return session.Dictionary; } }
protected void RaiseChangeEvent(ObjectChangeEventArgs args) {
if(Changed != null)
Changed(this, args);
}
public void OnChanged() {
changed = true;
if(!IsLoading)
RaiseChangeEvent(new ObjectChangeEventArgs(Session, this, ObjectChangeReason.Reset));
}
protected void OnChanged(string propertyName) {
changed = true;
if(!IsLoading)
RaiseChangeEvent(new ObjectChangeEventArgs(Session, this, ObjectChangeReason.PropertyChanged, propertyName, oldValue, newValue));
}
public event ObjectChangeEventHandler Changed;
// The following three methods implement general persistent operations
public void Save() {
Session.Save(this);
}
public void Delete() {
changed = true;
Session.Delete(this);
RaiseChangeEvent(new ObjectChangeEventArgs(Session, this, ObjectChangeReason.Delete));
}
public void Reload() {
Session.Reload(this);
collections.Clear(); // Required for collection properties (see implementation below)
RaiseChangeEvent(new ObjectChangeEventArgs(Session, this, ObjectChangeReason.Reset));
}
Dim _session As Session
Dim _classInfo As XPClassInfo
Public ReadOnly Property Session() As Session Implements IXPSimpleObject.Session
Get
Return Me._session
End Get
End Property
Public ReadOnly Property ClassInfo() As XPClassInfo Implements IXPSimpleObject.ClassInfo
Get
Return Me._classInfo
End Get
End Property
Dim _changed As Boolean = False
Public ReadOnly Property DataLayer() As IDataLayer
Get
Return Me._session.DataLayer
End Get
End Property
Public ReadOnly Property Dictionary() As XPDictionary
Get
Return Me._session.Dictionary
End Get
End Property
Protected Sub RaiseChangeEvent(ByVal args As ObjectChangeEventArgs)
RaiseEvent Changed(Me, args)
End Sub
Public Sub OnChanged()
_changed = True
If Not IsLoading Then
RaiseChangeEvent(New ObjectChangeEventArgs(Session, Me, ObjectChangeReason.Reset))
End If
End Sub
Protected Sub OnChanged(ByVal propertyName As String)
_changed = True
If Not IsLoading Then
RaiseChangeEvent(New ObjectChangeEventArgs(Session, Me, ObjectChangeReason.PropertyChanged, propertyName, oldValue, newValue))
End If
End Sub
Public Event Changed As ObjectChangeEventHandler Implements IXPSimpleObject.Changed
' The following three methods implement general persistent operations
Public Sub Save()
Session.Save(Me)
End Sub
Public Sub Delete()
_changed = True
Session.Delete(Me)
RaiseChangeEvent(New ObjectChangeEventArgs(Session, Me, ObjectChangeReason.Delete))
End Sub
Public Sub Reload()
Session.Reload(Me)
_collections.Clear() ' Required for collection properties (see implementation below)
RaiseChangeEvent(New ObjectChangeEventArgs(Session, Me, ObjectChangeReason.Reset))
End Sub
To enable object comparisons, the IComparable interface should be implemented, this is required to implement sorting. The following code example shows how to do this.
int IComparable.CompareTo(object value) {
return Comparer.Default.Compare(Session.GetKeyValue(this),
Session.GetKeyValue(value as IXPSimpleObject));
}
Public Overridable Overloads Function CompareTo(ByVal value As Object) As Integer _
Implements IComparable.CompareTo
Return Comparer.Default.Compare(Session.GetKeyValue(Me),
Session.GetKeyValue(CType(value, IXPSimpleObject)))
End Function
To perform additional housekeeping after initializing, saving or deleting an object, an IXPObject interface should be implemented. The IsLoading property indicates whether the object is completely loaded.
bool isLoading;
[NonPersistent]
public bool IsLoading { get { return isLoading; } }
// Implement additional logic after an object has been deleted
void IXPObject.OnDeleted() { }
// Implement additional logic when an object is about to be deleted
void IXPObject.OnDeleting() { }
// Implement additional logic after an object has been loaded
void IXPObject.OnLoaded() {
isLoading = false;
}
// Implement additional logic before loading an object
void IXPObject.OnLoading() {
isLoading = true;
}
// Implement additional logic after an object has been saved
void IXPObject.OnSaved() { }
// Implement additional logic before saving an object
void IXPObject.OnSaving() { }
Dim _isLoading As Boolean
<NonPersistent()> _
Public ReadOnly Property IsLoading() As Boolean
Get
Return _isLoading
End Get
End Property
' Implement additional logic after an object has been deleted
Sub OnDeleted() Implements IXPObject.OnDeleted
End Sub
' Implement additional logic when an object is about to be deleted
Sub OnDeleting() Implements IXPObject.OnDeleting
End Sub
' Implement additional logic after an object has been loaded
Sub OnLoaded() Implements IXPObject.OnLoaded
_isLoading = False
End Sub
' Implement additional logic before loading an object
Sub OnLoading() Implements IXPObject.OnLoading
_isLoading = True
End Sub
' Implement additional logic after an object has been saved
Sub OnSaved() Implements IXPObject.OnSaved
End Sub
' Implement additional logic before saving an object
Sub OnSaving() Implements IXPObject.OnSaving
End Sub
To provide support for lookup editors, add This property implementation as shown in the following code example.
public object This { get { return this; } }
Public ReadOnly Property This() As Object
Get
Return Me
End Get
End Property
To implement collection properties, the following code can be used. Note that the Reload method clears the collection’s contents (see implementation above).
IDictionary collections;
IDictionary Collections {
get {
if(collections == null)
collections = new System.Collections.Specialized.HybridDictionary();
return collections;
}
}
XPMemberInfo GetCollectionProperty(string name) {
XPMemberInfo p = ClassInfo.GetMember(name);
if (p != null)
return p;
throw new PropertyMissingException(ClassInfo.FullName, name);
}
// Retrieves a collection by the name of the property which exposes the collection
protected XPCollection GetCollection(string propertyName) {
XPCollection result = Collections[propertyName] as XPCollection;
if(result == null) {
result = new XPCollection(this.Session, this, GetCollectionProperty(propertyName));
Collections.Add(propertyName, result);
}
return result;
}
}
Dim _collections As IDictionary
ReadOnly Property Collections() As IDictionary
Get
If _collections Is Nothing Then
_collections = New System.Collections.Specialized.HybridDictionary()
End If
Return _collections
End Get
End Property
Function GetCollectionProperty(ByVal name As String) As XPMemberInfo
Dim p As XPMemberInfo = ClassInfo.GetMember(name)
If Not p Is Nothing Then
Return p
End If
Throw New PropertyMissingException(ClassInfo.FullName, name)
End Function
' Retrieves a collection by the name of the property which exposes the collection
Protected Function GetCollection(ByVal propertyName As String) As XPCollection
Dim result As XPCollection = CType(Collections.Item(propertyName), XPCollection)
If result Is Nothing Then
result = New XPCollection(Me.Session, Me, GetCollectionProperty(propertyName))
Collections.Add(propertyName, result)
End If
Return result
End Function
End Class
The following example demonstrates how to provide access to an Object Access Layer which the current Session uses to retrieve and update object data in a data store. This property is required for internal purposes.
IObjectLayer IObjectLayerProvider.ObjectLayer {
get { return Session.ObjectLayer; }
}
Private ReadOnly Property ObjectLayer() As IObjectLayer Implements IObjectLayerProvider.ObjectLayer
Get
Return Session.ObjectLayer
End Get
End Property
See Also
Add Persistence to an Existing Hierarchy: Session-less Persistent Objects