Description:
Unlike the XPO Best Practices Knowledge Base article, this one describes approaches that should be avoided as they always cause undesirable side effects. This article demonstrates known mistakes, and describes which approach should be used instead.
Answer:
1. Set the DeleteObjectOnRemove property to true for the XPCollection used as a value of the collection property participating in the association.
When XPCOllection is bound to a grid, deleting a row within the grid removes a persistent object from the collection, but does not delete it from the database, by default. To enable deletion, it is necessary to set the XPBaseCollection.DeleteObjectOnRemove property to true. However, what if it is a detail collection retrieved by RelatedCurrencyManager from an object currently selected in a parent collection? This occurs when the DataMember property of the grid is used to bind a detail view to a detail collection. In this situation there is no way to modify XPCollection properties.
A common mistake is to create a base class for all persistent classes in an application, and override its CreateCollection method to set the DeleteObjectOnRemove property to true.
C#protected override XPCollection<T> CreateCollection<T>(DevExpress.Xpo.Metadata.XPMemberInfo property) {
XPCollection<T> result = base.CreateCollection<T>(property);
result.DeleteObjectOnRemove = true;
return result;
}
Protected Overrides Function CreateCollection(Of T)(ByVal prop As DevExpress.Xpo.Metadata.XPMemberInfo) As XPCollection(Of T)
Dim result As XPCollection(Of T) = MyBase.CreateCollection(Of T)(prop)
result.DeleteObjectOnRemove = True
Return result
End Function
This approach is incorrect, because XPO internally removes objects from an associated collection, when modifying a reference property from the opposite side of the association. Therefore, an instance previously referenced by the owner of the collection will be deleted from the database when it is not supposed to occur. The following code illustrates the problem:
C#Customer customerA = new Customer(session);
Customer customerB = new Customer(session);
Order order = new Order(session) { Customer = customerA };
order.Customer = customerB;
Visual BasicDim customerA As Customer = New Customer(session)
Dim customerB As New Customer(session)
Dim order As New Order(session) With {.Customer = customerA}
order.Customer = customerB
At this point, it is not expected that the order is deleted from the database. The expected behavior is that the order will be added to the Orders collection of the CustomerB. Actually when the order is removed from the Orders collection of the customerA, it will be deleted from the database, since the DeleteObjectOnRemove property of the collection is set to true.
A correct solution when you need to delete an object from the database is to handle the deleting operation at the UI level, and use the Session.Delete method to delete an object. In Why are objects not deleted when I delete them in the XtraGrid? Knowledge Base article there is an approach to using DataNavigator for this purpose.
2. Implement the persistent members as fields, or apply a value within a persistent property setter without firing the PropertyChanged event.
A common mistake is to define persistent members as fields:
C#public string Name;
Visual BasicPublic Name As String
Or directly assign value to the value holder, without using the SetPropertyValue method:
C#private string fName;
public string Name {
get { return fName; }
set { fName = value; }
}
Visual BasicDim fName As Stirng
Public Property Name() As String
Get
Return fName
End Get
Set
fName = value
End Set
End Property
In C#, it is also possible to define auto implemented properties:
C#public string Name { get; set; }
As a result, persistent object loses the INotifyPropertyChanged implementation, implemented within the XPBaseObject class. In WinForms applications, this interface is used by data aware controls to automatically refresh display values once the property value has been modified.
In addition, the UnitOfWork functionality will be lost. The UnitOfWork tracks the PropertyChanged event, and saves only modified objects to the database, when the CommitChanges method is being executed. Therefore, you can run into a situation when modifications applied to persistent objects are lost, unless you explicitly call the Save method, in order to force the UnitOfWork to save the object to the database, when the CommitChanges method is executed.
Please look at the code below. This code is not too complicated to neglect the benefits it gives.
C#private string fName;
public string Name {
get { return fName; }
set { SetPropertyValue<string>("Name", ref fName, value); }
}
Visual BasicDim fName As Stirng
Public Property Name() As String
Get
Return fName
End Get
Set
SetPropertyValue(Of String)("Name", fName, value)
End Set
End Property
3. Use the Session.Connection or Session.ConnectionString property in your code.
Sometimes, it is necessary to access an IDbConnection instance used by XPO to connect to the database. For example, to execute an arbitrary SQL query (in version 10.2, XPO provides native methods for this purpose). A common mistake is to use the Session.Connection or Session.ConnectionString property for this purpose.
As described in our documentation, the value of these properties depends on many factors, and can be empty. The correct approach is to use corresponding methods of the IDataLayer instance:
C#IDbConnection connection = session.DataLayer.Connection
Visual BasicDim connection As IDbConnection = session.DataLayer.Connection
4. Use the parameterless constructors of the XPBaseObject or XPCollection.
XPBaseObject descendants cannot live without a Session. This means that if a Session instance is not provided in the constructor, XpoDefault.Session will be used. This is a dangerous practice, especially when a programmer is not aware of the above specifics. This can lead to mixing objects created with the default Session with ones created with another Session instance. XPO does not allow this, and will throw an exception.
A more dangerous situation can occur if the XpoDefault.DataLayer is not initialized, and all Sessions are created using a custom Data Layer. In this situation, the default Session is targeting the default Access database, which does not have any data. If persistent objects are accidentally loaded or created with a default Session, this leads to subtle errors.
The most reliable way to prevent such problems is to remove parameterless constructors from persistent classes. In this situation, if these constructors are accidentally used somewhere in the application, an exception will be thrown, and the problem will be found at early stages of development.
5. Make a calculated property persistent.
Sometimes it is necessary to update the value of a persistent property based on changes made in other properties. In simple cases this approach will work, but it is difficult to synchronize updates and keep the property value up to date. This property should be calculated both on the client and on the database side. This way, its value will always be up to date.
To implement a calculated property, apply a PersistentAliasAttriute attribute to it. An expression to calculate the property value on the database side can be provided via the AliasExpression property of this attribute.
See also: XPO Best Practices
Thank you for taking the time to write this up!
Just a friendly reminder that this article hasn't been updated in a while. :)
"The most reliable way to prevent such problems is to remove parameterless constructors from persistent classes. "
Question : is this really enough or is ist needed to create a parameterless private constructor?
Hello Wolfgang,
Normally, you do not need to use parameterless constructors. Hence, this is enough to remove them. The idea of this trick is to find all places where persistent objects are created incorrectly. When you remove parameterless constructors, you may receive compilation errors and runtime exceptions. These errors and exceptions will point you to problematic places in code. This is the easiest way to find these places and fix them.