Ticket K18428
Visible to All Users

XPO Worst Practices

created 14 years ago

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 Basic
Dim 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 Basic
Public 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 Basic
Dim 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 Basic
Dim 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 Basic
Dim 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

Comments (3)

    Thank you for taking the time to write this up!
    Just a friendly reminder that this article hasn't been updated in a while. :)

    WS WS
    Wolfgang Schmidt 6 9 years ago

      "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?

      DevExpress Support Team 9 years ago

        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.

        Disclaimer: The information provided on DevExpress.com and affiliated web properties (including the DevExpress Support Center) is provided "as is" without warranty of any kind. Developer Express Inc disclaims all warranties, either express or implied, including the warranties of merchantability and fitness for a particular purpose. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.

        Confidential Information: Developer Express Inc does not wish to receive, will not act to procure, nor will it solicit, confidential or proprietary materials and information from you through the DevExpress Support Center or its web properties. Any and all materials or information divulged during chats, email communications, online discussions, Support Center tickets, or made available to Developer Express Inc in any manner will be deemed NOT to be confidential by Developer Express Inc. Please refer to the DevExpress.com Website Terms of Use for more information in this regard.