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.