KB Article K18356
Visible to All Users

How to use XPO data layer caching in XAF

Session Layer Caching

By default, XPO caches records at the Session level. Since most XAF root Views have their own Session, each root View has its own cache. This cache is reset when a View is closed or its Object Space is reloaded. Besides, most database systems provide an advanced caching mechanism and perform the subsequent calls of the same SQL queries faster. In most situations, this functionality is enough to achieve good performance. In case of a significant latency of a database server connection and a lot of repetitive queries, it may be helpful to implement caching at the data store level.

Data Layer Caching

To enable data store-level caching, connect an application to a Cached Data Store. In this case, the application caches database queries and their results. If an application executes the same query for a second time, the cached data will be loaded from memory. The DataCacheNode and DataCacheNodeLocal objects perform caching. Connect these objects to a shared DataCacheRoot object. DataCacheRoot synchronizes changes made in different cache nodes. Thus, you can connect only one root to the database at one time. You need to pass all data modifications through it. To reset or clear any cached information on data store tables (such as table update information and cached query results), use the NotifyDirtyTables or Reset() methods (see code examples at Questions about resetting data store cache for certain types only).

For more information on built-in XPO caching strategies and their differences, see Session Management and Caching.

Ways to Share DataCacheRoot to Implement Data Layer Caching

Depending on your XAF application configuration, consider the following options:

ASP.NET Core Blazor | Web API Service

1. In the SolutionName.Blazor.Server project (SolutionName.WebApi project), add the CachedDataStoreProvider.cs file with the following code:

C#
using DevExpress.ExpressApp.Xpo; using DevExpress.Xpo.DB; using System.Data; public class CachedDataStoreProvider : IXpoDataStoreProvider { private object syncRoot = new object(); private static IDisposable[] rootDisposableObjects; private static DataCacheRoot root; private static DataCacheNode node; private static IXpoDataStoreProvider xpoDataStoreProvider; public static void ResetDataCacheRoot() { root = null; if (rootDisposableObjects != null) { foreach (IDisposable disposableObject in rootDisposableObjects) { disposableObject.Dispose(); } rootDisposableObjects = null; } } public string ConnectionString { get { return xpoDataStoreProvider.ConnectionString; } } public CachedDataStoreProvider(string connectionString, IDbConnection connection, bool enablePoolingInConnectionString) { if (xpoDataStoreProvider == null) { xpoDataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(connectionString, connection, enablePoolingInConnectionString); } } public IDataStore CreateWorkingStore(out IDisposable[] disposableObjects) { if (root == null) { lock (syncRoot) { if (root == null) { IDataStore baseDataStore = xpoDataStoreProvider.CreateWorkingStore(out rootDisposableObjects); root = new DataCacheRoot(baseDataStore); node = new DataCacheNode(root); //node.MaxCacheLatency = TimeSpan.FromSeconds(3600); } } } disposableObjects = new IDisposable[0]; return node; } public IDataStore CreateUpdatingStore(bool allowUpdateSchema, out IDisposable[] disposableObjects) { return xpoDataStoreProvider.CreateUpdatingStore(allowUpdateSchema, out disposableObjects); } public IDataStore CreateSchemaCheckingStore(out IDisposable[] disposableObjects) { return xpoDataStoreProvider.CreateSchemaCheckingStore(out disposableObjects); } }

For more information on IXpoDataStoreProvider, study How to customize the underlying database provider options and data access behavior in XAF.

2. In the SolutionName.Blazor.Server/Startup.cs file (SolutionName.WebApi/Startup.cs file), modify the ConfigureServices method as follows:

C#
public void ConfigureServices(IServiceCollection services) { // ... services.AddSingleton<IXpoDataStoreProvider>((serviceProvider) => { string connectionString = null; if(Configuration.GetConnectionString("ConnectionString") != null) { connectionString = Configuration.GetConnectionString("ConnectionString"); } #if EASYTEST if(Configuration.GetConnectionString("EasyTestConnectionString") != null) { connectionString = Configuration.GetConnectionString("EasyTestConnectionString"); } #endif ArgumentNullException.ThrowIfNull(connectionString); return new CachedDataStoreProvider(connectionString, connection: null, enablePoolingInConnectionString: true); }); // ...

3. Modify the AddSecuredXpo method as follows:

C#
builder.ObjectSpaceProviders .AddSecuredXpo((serviceProvider, options) => { options.ThreadSafe = true; var cachedDataStoreProvider = serviceProvider.GetRequiredService<IXpoDataStoreProvider>(); ArgumentNullException.ThrowIfNull(cachedDataStoreProvider); options.UseCustomDataStoreProvider(cachedDataStoreProvider); }) // ...

Note that reading the connection string is done in point #2 instead of AddSecuredXpo in the default Solution Wizard configuration.

Attached is a complete sample project for your reference (you may need to update it using the Project Converter tool).

WinForms with Middle Tier Security (.NET Framework)

In this configuration, DataCacheRoot is hosted on a Middle Tier server application. Each client application's instance is connected to it. To host DataCacheRoot on your Middle Tier server application, update the following code in the YourSolutionName.ApplicationServer project's Program.cs  (Program.vb) or ApplicationServerService.cs  (ApplicationServerService.vb) file:

C#
var mainDataStore = XpoDefault.GetConnectionProvider(connectionString, AutoCreateOption.SchemaAlreadyExists); var root = new DataCacheRoot(mainDataStore); var node = new DataCacheNode(root); Func<IDataLayer> dataLayerProvider = () => new ThreadSafeDataLayer(XpoTypesInfoHelper.GetXpoTypeInfoSource().XPDictionary, node); WcfXafServiceHost serviceHost = new WcfXafServiceHost(dataLayerProvider, dataServerSecurityProvider, false);

Changes to the WinForms client code are not needed.

ASP.NET WebForms (.NET Framework)

1. In the SolutionName.Web project, create the CachedDataStoreProvider.cs  (CachedDataStoreProvider.vb) file:

C#
using System; using System.Data; using DevExpress.ExpressApp.Xpo; using DevExpress.Xpo.DB; namespace SolutionName.Web { public class CachedDataStoreProvider : IXpoDataStoreProvider { private static IDisposable[] rootDisposableObjects; private static DataCacheRoot root; private static IXpoDataStoreProvider xpoDataStoreProvider; public static void ResetDataCacheRoot() { root = null; if (rootDisposableObjects != null) { foreach (IDisposable disposableObject in rootDisposableObjects) { disposableObject.Dispose(); } rootDisposableObjects = null; } } public string ConnectionString { get { return xpoDataStoreProvider.ConnectionString; } } public CachedDataStoreProvider(string connectionString, IDbConnection connection, bool enablePoolingInConnectionString) { if (xpoDataStoreProvider == null) { xpoDataStoreProvider = XPObjectSpaceProvider.GetDataStoreProvider(connectionString, connection, enablePoolingInConnectionString); } } public IDataStore CreateWorkingStore(out IDisposable[] disposableObjects) { if (root == null) { IDataStore baseDataStore = xpoDataStoreProvider.CreateWorkingStore(out rootDisposableObjects); root = new DataCacheRoot(baseDataStore); } disposableObjects = new IDisposable[0]; return new DataCacheNode(root); } public IDataStore CreateUpdatingStore(bool allowUpdateSchema, out IDisposable[] disposableObjects) { return xpoDataStoreProvider.CreateUpdatingStore(allowUpdateSchema, out disposableObjects); } public IDataStore CreateSchemaCheckingStore(out IDisposable[] disposableObjects) { return xpoDataStoreProvider.CreateSchemaCheckingStore(out disposableObjects); } } }

2. In the SolutionName.Web/WebApplication.cs  (WebApplication.vb) file, update the following code:

C#
using System; using DevExpress.ExpressApp; using DevExpress.ExpressApp.Web; using DevExpress.ExpressApp.Xpo; using DevExpress.ExpressApp.Security; using DevExpress.ExpressApp.Security.ClientServer; namespace SolutionName.Web { public partial class SolutionNameAspNetApplication : WebApplication { //... private IXpoDataStoreProvider GetDataStoreProvider(string connectionString, System.Data.IDbConnection connection) { System.Web.HttpApplicationState application = (System.Web.HttpContext.Current != null) ? System.Web.HttpContext.Current.Application : null; IXpoDataStoreProvider dataStoreProvider = null; if (application != null && application["DataStoreProvider"] != null) { dataStoreProvider = application["DataStoreProvider"] as IXpoDataStoreProvider; } else { dataStoreProvider = new CachedDataStoreProvider(connectionString, connection, true); if (application != null) { application["DataStoreProvider"] = dataStoreProvider; } } return dataStoreProvider; } //... } }

3. In the SolutionName.Web/Global.asax.cs  (Global.asax.vb) file, add the following code in the Application_End method:

C#
using System; using System.Configuration; using DevExpress.ExpressApp; using DevExpress.Persistent.Base; using DevExpress.ExpressApp.Security; using DevExpress.ExpressApp.Web; using DevExpress.Web; namespace SolutionName.Web { public class Global : System.Web.HttpApplication { //... protected void Application_End(Object sender, EventArgs e) { CachedDataStoreProvider.ResetDataCacheRoot(); } //... } }

See Also

How to measure and improve the application's performance - check other ways of improving the application's performance before using these solutions. Note that they may increase the memory used by your applications.

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.