All docs
V21.2
21.2
21.1
20.2
The page you are viewing does not exist in version 20.2. This link will take you to the root page.
20.1
The page you are viewing does not exist in version 20.1. This link will take you to the root page.
19.2
The page you are viewing does not exist in version 19.2. This link will take you to the root page.
19.1
The page you are viewing does not exist in version 19.1. This link will take you to the root page.
18.2
The page you are viewing does not exist in version 18.2. This link will take you to the root page.
18.1
The page you are viewing does not exist in version 18.1. This link will take you to the root page.
17.2
The page you are viewing does not exist in version 17.2. This link will take you to the root page.

ORM Layer Performance

  • 8 minutes to read

This article explains how to fix the most frequent SQL-related performance issues caused by ORM data model design, business logic or UI settings.

Use the diagnostic information from the Database Performance article to analyze the code of ORM persistent classes as described in this article.

ORM Best Practices

Follow the best practices for your ORM to achieve the best performance results.

If your code already follows these best practices, review SQL queries with the highest call count and/or execution time. Note that if SQL query execution time takes the most time in end-user operations, SQL query optimization is required.

Troubleshooting

Check this list of the most frequent causes of performance issues and make sure you apply all the recommendations from this list.

  • A ListView loads data slowly if the corresponding database table contains more than 100K records (or more than 10K records with complex structure).

    Solution 1: If you do not need to show all records simultaneously, add a server-side filter to your ListView.

    Solution 2: For Grid List Editors, set List View Data Access Modes to values other than Client. For Pivot Grid List Editors, see Ways to improve Pivot Grid List Editor or PivotChart performance with large amounts of data.

    For more information, refer to the following video: Data Access in DevExpress XAF & ORM-related Performance Considerations - Data Access Modes in ListView - DEMO.

  • The main SELECT query takes a long time to execute, although not many rows are returned.

    This may occur if each persistent object contains many fields with images, long text, references to complex persistent objects, etc.

    Solution 1: For List Editors, set the ListView’s DataAccessMode to DataView, ServerView or InstantFeedbackView to load only required properties (by default, all object properties are loaded except for collections). For reports, use ViewDataSource.

    Solution 2: Enable Delayed Loading for BLOB and complex reference properties. Note that delayed properties should not be displayed in the ListView.

  • XPO sends additional queries to load associated (referenced) objects.

    This behavior is expected for XPO - it loads related objects IDs in the main query, and then loads all these objects in the second query by these IDs. In most cases, this behavior should not cause performance issues. If it does, there are two ways to change it:

    Solution 1: Include referenced objects in the main SELECT query by applying the ExplicitLoading attribute to the corresponding properties.

    Solution 2: If related objects are not used in list views, you can use the Delayed Loading for them.

    In most cases, all referenced objects of the same type are loaded through a single additional query for all records. If additional queries are performed for each record, see the next case.

  • XPO or Entity Framework executes a separate query or multiple queries for each record.

    For more information, see What is the “N+1 selects problem” in ORM (Object-Relational Mapping)? (StackOverFlow) and Fixing an N+1 performance problem in XAF/XPO with totally undocumented APIs (by Manuel Grundner, a DevExpress MVP).

    To resolve such issues, see what additional queries are executed and analyze your business class to understand what code causes this. Below are the most common cases.

    • Additional queries load a property of the current business class that is not loaded in the main SELECT query.

      Solution: This property is likely delayed, and there is a ListView column that displays it. In this case, either do not use delayed loading for this property, or remove the ListView column (see Change Field Layout and Visibility in a List View). The same issue occurs if the delayed property is accessed in code while the view loads objects. Such code may be located in another property’s getter or in the OnLoaded method.

    • Additional queries select data from other tables according to a PersistentAlias expression.

      This may happen if the expression uses collection properties (such as Orders.Sum(Amount)) or join operands (such as [<Task>][AssignedTo.Oid = ^.Oid].Single()). The EvaluateAlias method evaluates a PersistentAlias attribute expression on the client side. If the expression contains a collection property, ORM loads the collection when the getter is called.

      Solution 1: For List Editors, set the ListView’s DataAccessMode to DataView, ServerView or InstantFeedbackView. For reports, use ViewDataSource. In this case, when ORM constructs the main SELECT query, it includes all queries that can run on the server side (PersistentAlias expressions). The application will not use any client-side code that loads data.

      Solution 2: Pre-fetch associated collections using the Session.PreFetch and XPObjectSpace.SetPrefetchPropertyNames methods. In this case, all associated objects will be loaded in a single query. You can find an example in the following ticket: How do I prefetch related details data to increase performance for calculated fields.

      Solution 3: Use the Session.Evaluate method instead of the EvaluateAlias method in getters of such properties. The Session.Evaluate method evaluates the specified expression on the database side and returns a single value. XPO still sends a separate query for each row, but these queries require less time and memory.

      Solution 4: Recalculate property values only when related objects are changed - see Store Calculated Property Values in the Database.

      Solution 5: Implement a calculated column in the database and map it to an XPO property using the FetchOnly attribute (v18.2+).

    • Additional queries are executed for an unknown reason.

      Solution: To determine why an object of a certain type is loaded, set a breakpoint in its constructor. Run the app and open the corresponding ListView. When the debugger stops at the specified breakpoint, open the callstack window and review the methods lead to object initialization. This may be business logic from property getters or an OnLoaded method call.

      If your class loads objects in the OnLoaded method, try to move this logic to a calculated property getter. This calculated property should be hidden from the ListView. To show calculated values in the ListView, implement a PersistentAlias property and optimize it as described in the previous case.

  • The applications frequently perform the same queries that return the same database records.

    Solution: If these records are changed rarely, it makes sense to enable caching at the Data Layer level to prevent the repetitive requests.

  • An XPO-based WebForms app creates a separate database connection for each user.

    Solution: Ensure that the code from Initialization of one XPO data layer per one global HttpApplication is added to Global.asax.cs for XAF apps created before v15.2.4.

  • An XPO-based WinForms app repeatedly creates and closes database connections on performing asynchronous operations in Instant Feedback mode.

    Solution: Make sure to add the following code to WinApplication.cs (for XAF apps created before v16.2.5). This code registers XPObjectSpaceProvider or SecuredObjectSpaceProvider with enabled caching and connection pooling in the data store provider.

    protected override void CreateDefaultObjectSpaceProvider(CreateCustomObjectSpaceProviderEventArgs args) {
        args.ObjectSpaceProviders.Add(new SecuredObjectSpaceProvider((SecurityStrategyComplex)Security, XPObjectSpaceProvider.GetDataStoreProvider(args.ConnectionString, args.Connection, true), false));
        args.ObjectSpaceProviders.Add(new NonPersistentObjectSpaceProvider(TypesInfo, null));
    }
    
  • Validation may take significant time when an object to be saved exposes a large collection of aggregated objects.

    Solution: Aggregated objects are integral to a master object and should be validated together. The PersistenceValidationController loads the entire aggregated collection in this case. Refer to the following article for more information and possible solutions: Validation performance - PersistenceValidationController and the Aggregated attribute cause selecting child records one by one on saving the master object.

  • Your persistent classes have many reference and collection properties (or the entire object graph is too complex) and the profile confirms excessive JOIN/additional queries.

    Consider if you can denormalize your database table or mapping your persistent class to a database view with only required columns from your database table. In the UI, you may lose the capability to navigate to certain reference sub-properties, but this may be unimportant for users (so the changes are justified).


This article lists only the most common performance issues. You can also encounter issues related to a certain database provider or legacy database, scenario-specific issues, and so on. We recommend that you find out how the query or the database table can be modified to improve performance, and then try to modify your persistent objects accordingly.

Next Steps

If you still experience performance issues after you followed the advice in this topic, review the next article in our optimization guide: Application Performance.

See Also