EF Core Extension Methods
NCache provides synchronous and asynchronous extension methods for caching queries in EF Core. The sync APIs are extension methods for the IQueryable
interface, while the async APIs return a task instance for these methods.
Note
This feature is also available in NCache Professional.
To utilize the following APIs, include the following namespace in your application:
Alachisoft.NCache.EntityFrameworkCore
NCache’s Entity Framework Core Caching Provider contains the following APIs for caching queries:
Synchronous APIs | Asynchronous APIs |
---|---|
FromCache |
FromCacheAsync |
LoadIntoCache |
LoadIntoCacheAsync |
FromCacheOnly |
FromCacheOnlyAsync |
The FromCache
extension method is ideal to use when handling transactional data (with frequent read and write operations). For example, maintaining flight records, where each unique flight requires continuous database logging of new entries. Alternatively, for reference data (where reads are more frequent than writes), use LoadIntoCache
and FromCacheOnly
extension methods. Just like in a product catalog, where products are treated as reference data, as they are rarely modified and consistently read from the database. To use these reference data extension methods, you need to define the query indexes within your application. This can be done dynamically by adding the [QueryIndexable]
tag.
Storing Cache Data with EFCore
In FromCache
, LoadIntoCache
, FromCacheOnly
, and their Async counterparts, you can use the EF Core caching queries to store data in the cache for subsequent queries.
You can store the data in the cache in two ways: store the entire data set as a collection against a single key in the data store using Insert
call. Or, you can add each entity separately against multiple keys in a bulk. You can make this choice by using the CachingOption StoreAs
set to StoreAs.Collection
or StoreAs.SeperateEntities
. You can specify and enable bulkInsertChunkSize
when dealing with a considerable amount of entities, e.g., 100,000. This ensures that data is loaded into the cache chunk-by-chunk, regardless of the dataset's size, making the data storage process more efficient and manageable.
Note
The bulkInsertChunkSize
property divides the bulk of entities into smaller chunks and updates the cache chunk-by-chunk. As such, it allows the operation to workaround the connection timeout by caching a chunk of entities within the 90-second limit, preventing it from triggering the cancellation token. By default, the bulkInsertChunkSize
is 1000.
Important
Since the application only receives a response once the entire bulk is cached, it is best to use an Async extension method to avoid any significant delay when dealing with a large dataset.
FromCache
The FromCache
method caches the result set data generated against the LINQ query and returns it to the application. If the data doesn't exist in the cache, it will be fetched from the data source and stored in the cache. If the application makes the same query again, it will get the necessary data from the cache, avoiding an unnecessary trip to the data source.
However, since this method takes place on two levels (getting the result set data and updating the cache), there is an increased chance of failure, especially in the latter case. You may fail to update the cache due to a variety of reasons. For instance, database connection failure, network error, the cache/server being down, a failure to serialize the data, a state transfer scenario, etc.
Therefore, NCache provides users employing FromCache
with the errorEnabled
flag that allows them to determine whether they want to stop the application for any non-result-set-related issues. Generally, if caching and system performance are a priority, users will set this flag to True (as the query deals with the data source every time, otherwise). However, if their priority is preventing the application from stopping, users will set this flag to False.
Note
By default, the errorEnabled
flag is set to False.
Note
Even if the errorEnabled
flag is False, these updating cache-related exceptions will be logged in the Cache logs available at %NCHome%/log-files
.
Examples
- The following example fetches customer details based on a specified customer ID from the database and stores them as a separate entity in cache with specified caching options.
using (var context = new NorthwindContext())
{
var options = new CachingOptions
{
StoreAs = StoreAs.SeperateEntities
};
var resultSet = (from cust in context.Customers
where cust.CustomerId == someCustomerId
select cust).FromCache(options).ToList();
}
Note
You can also monitor the activity of your cache using the NCache Monitor.
- The following example returns the cache key generated internally against the query result set data stored as a collection in the cache. This key can be saved for future usage like removing the entity/result set from cache.
using (var context = new NorthwindContext())
{
var options = new CachingOptions
{
StoreAs = StoreAs.Collection
};
var resultSet = (from cust in context.Customers
where cust.CustomerId == someCustomerId
select cust).FromCache(out string cacheKey, options);
}
Note
By using Export-CacheKeys in the tool of your choice, you can view the cache keys of a particular cache.
Considerations for FromCache
FromCache
supports the following functions:
Group By
Order By Ascending
Order By Descending
Sum
Min
Max
Average
Count
Contains
Like
FirstOrDefault
The functions can be implemented while using LINQ with FromCache
, as follows:
- The
Like
operator in EF Core performs pattern matching within character strings. Usually, it is employed withWhere
inSELECT
statements to filter rows based on specific patterns or substrings within a column's value. Additionally, it allows the use of wildcards to match one or more characters in a string:
using (var context = new NorthwindContext())
{
var result = context.Customers
.Where(c => EF.Functions.Like(c.CompanyName, "Alfreds Futterkiste"))
.FromCache(options)
.ToList();
}
- The
Contains
operator can be used similarly to theLike
operator to employ pattern-based searches within a given criteria.
using (var context = new NorthwindContext())
{
var result = context.Customers
.Where(b => (b.CompanyName.Contains("Alfreds Futterkist")))
.FromCache(out cacheKey, options)
}
- The
FirstOrDefault
operator can be implemented as demonstrated below.
using (var context = new NorthwindContext())
{
var result = context.Products
.Where(b => b.UnitPrice > 1)
.FromCache(out cacheKey, options)
.FirstOrDefault();
}
- The
GroupBy
can be used with projection for FromCache as implemented below.
using (var context = new NorthwindContext())
{
var resultSet = context.Products
.Where(p => p.UnitPrice == 10)
.GroupBy(p => p.ProductName)
.Select(group => group.Key)
.FromCache(out cacheKey, options)
.ToList();
}
LoadIntoCache
The LoadIntoCache
API fetches the result set data from the source and caches it without returning it to the application. This API allows subsequent FromCache
calls to yield fresh data. The LoadIntoCache
method is particularly suitable for frequently updated data like customer orders or sensitive data like payment details. In such scenarios, using stale data can result in incorrect business transactions, hence, a fresh copy of the data must be present in the cache at all times. This extension method loads your complete working set of data into the cache. Then, this method queries the database, stores the result in the cache, and returns it to the application.
Examples
- The following example fetches the customer orders from the database and loads the result set data into the cache as a collection. It also returns the cache key generated internally which can be saved for future use.
using (var context = new NorthwindContext())
{
var options = new CachingOptions
{
StoreAs = StoreAs.Collection
};
var resultSet = (from custOrder in context.Orders
where custOrder.Customer.CustomerId == someCustomerId
select custOrder).LoadIntoCache(out string cacheKey, options);
}
- The following example loads the specific order details from the database into the cache as separate entities with caching options specified.
using (var context = new NorthwindContext())
{
var options = new CachingOptions
{
StoreAs = StoreAs.SeperateEntities
};
var resultSet = (from custOrder in context.Orders
where custOrder.Customer.CustomerId == someCustomerId
select custOrder).LoadIntoCache(options);
}
- The
GroupBy
can be used with projection forLoadIntoCache
as implemented below.
using (var context = new NorthwindContext())
{
var resultSet = context.Products
.Where(p => p.UnitPrice == 10)
.GroupBy(p => p.ProductName)
.Select(group => group.Key)
.LoadIntoCache(out cacheKey, options)
.ToList();
}
You can execute this method at regular intervals whenever you expect that data to change. For instance, if you foresee data modifications within a week, running it again after that period ensures the data in the cache is updated.
FromCacheOnly
For data that is less frequently updated like customer details, regularly fetching it from the data source is costly. FromCacheOnly
queries the entities present in the cache and does not approach the data source in any case. Essentially, if the entity does not exist in the cache, it will still not be fetched from the data source.
Note
This API only works when entities are stored separately, i.e., the caching option StoreAs
is set to SeperateEntities
.
Important
The entities must be indexed in the NCache Management Center before they are used with FromCacheOnly
.
- The following example fetches the customer information from the cache if the Customer entity exists. If not, the result set returned will be empty.
using (var context = new NorthwindContext())
{
var resultSet = (from cust in context.Customers
where cust.CustomerId == someCustomerId
select cust).FromCacheOnly();
}
Considerations for FromCacheOnly
FromCacheOnly
supports aggregate functions. However, these functions only cache the result and not the entities themselves. Hence, NCache provides the QueryDeferred
method that defers the query so the entities in the cache get the result in response to a query. For more details on deferred APIs, please refer to the chapter Query Deferred APIs. FromCacheOnly
supports the following functions:
Group By
Order By Ascending
Order By Descending
Sum
Min
Max
Average
Count
Contains
Like
Keep in mind the following considerations while using LINQ with FromCacheOnly
:
Except for
Count
, all aggregate functions must have integer values.Count
needs to be provided with an entity to count.A single LINQ expression for
FromCacheOnly
cannot use more than one aggregate function.Projections in LINQ expressions can only be performed if a
GroupBy
operator and an aggregate function are used forFromCacheOnly
.Multiple projections are not supported. Only one attribute can be projected in a LINQ expression (that is carried forward).
using (var context = new NorthwindContext())
{
var result = context.Employees
.Select(e => e.EmployeeId) // Multiple projections have not been performed
.GroupBy(eid => eid) // eId was projected so GroupBy had to be used
.DeferredMin() // MIN provided with a numeric attribute
.FromCacheOnly();
}
GroupBy
is primarily used for aggregating functions (grouping the result of queries using columns) and can only be used if the projection is complete beforehand and an aggregate function is used.GroupBy
has to be the last operator in the LINQ expression except ifOrderBy
is used. In such cases, theGroupBy
function can be shifted forward (afterOrderBy
) or backward (beforeOrderBy
).
using (var context = new NorthwindContext())
{
var result = context.Products
.Where(data => data.ProductName == "Tofu")
.GroupBy(data => new { data.ProductName})
.Select(group => new
{
_String = group.Key.ProductName,
Count = group.Count()
})
.FromCacheOnly();
}
OrderBy
(both ascending and descending) can only be used in a LINQ expression forFromCacheOnly
ifGroupBy
is used.More than one
GroupBy
andOrderBy
operator cannot be used in a single LINQ expression forFromCacheOnly
.Joins are not supported so the
Include()
method will not work.For LINQ expressions containing the
DateTime
instances, all nodes in a cache cluster must have the sameDateTime
format set up. Otherwise, it will result in a format exception. This problem arises when theDateTime.Now
andDateTime.Parse()
methods are used in a multi-node cache cluster. To avoid it, synchronize theDateTime
format on both machines. Moreover, useDateTime.ParseExact()
overDateTime.Parse()
, as theParseExact()
API restricts the user from specifying a format forDateTime
.The
Like
operator in EF Core performs pattern matching within character strings. Usually, it is employed withWhere
inSELECT
statements to filter rows based on specific patterns or substrings within a column's value. Additionally, it allows the use of wildcards to match one or more characters in a string:
using (var context = new NorthwindContext())
{
var result = context.Customers
.Where(c => EF.Functions.Like(c.CompanyName, "Alfreds Futterkiste"))
.FromCacheOnly()
.ToList();
}
- The
Contains
operator can be used similarly to theLike
operator to employ pattern-based searches within a given criteria.
using (var context = new NorthwindContext())
{
var result = context.Customers
.Where(b => (b.CompanyName.Contains("Alfreds Futterkist")))
.FromCacheOnly()
}
- The
Contains
operator can also be used to search through a dictionary.
using (var context = new NorthwindContext())
{
var customerList = new List<string> { "CustomerId", "Alfreds Futterkiste" };
var result = context.Customers
.Where(b => customerList.Contains(b.CompanyName))
.FromCacheOnly();
}
Asynchronous LINQ APIs
The asynchronous APIs have the same functionality as their synchronous counterparts, but with asynchronous behavior. The parameters passed to these methods are also the same.
Important
Due to its asynchronous nature, cacheKey
is not returned in any of the asynchronous calls like FromCacheAsync
and LoadIntoCacheAsync
- as out
parameters are not allowed with methods with async signature.
FromCacheAsync
Data is directly fetched from the database if cache connection establishment fails. However, after every 60 seconds of the request being made, the cache connection is retried. The subsequent requests will try to reinitialize the cache after 60 seconds of the last request. Whenever a connection is successfully established, the data is fetched from the cache.
The following example fetches customer details based on a specified customer ID from the database asynchronously and stores them as a separate entity in the cache with specified caching options.
using (var context = new NorthwindContext())
{
var options = new CachingOptions
{
StoreAs = StoreAs.SeperateEntities
};
var task = (from cust in context.Customers
where cust.CustomerId == someCustomerId
select cust).FromCacheAsync(options);
task.Wait();
var resultSet = task.Result.ToList();
}
LoadIntoCacheAsync
The following example loads specific order details from the database into the cache as separate entities with the caching options specified.
using (var context = new NorthwindContext())
{
var options = new CachingOptions
{
StoreAs = StoreAs.SeperateEntities
};
var task = (from custOrder in context.Orders
where custOrder.Customer.CustomerId == someCustomerId
select custOrder).LoadIntoCacheAsync(options);
task.Wait();
var resultSet = task.Result.ToList();
}
FromCacheOnlyAsync
The following example fetches the customer information only from the cache if the Customer entity exists. If not, the result set returned will be empty, as the database is ignored.
using (var context = new NorthwindContext())
{
var options = new CachingOptions
{
StoreAs = StoreAs.SeperateEntities
};
var task = (from cust in context.Customers
where cust.CustomerId == someCustomerId
select cust).FromCacheOnlyAsync();
task.Wait();
var resultSet = task.Result.ToList();
}
Sample Code
NCache provides a sample application for EF Core on GitHub.
See Also
.NET: Alachisoft.NCache.EntityFrameworkCore namespace.
.NET: Alachisoft.NCache.Runtime.Caching namespace.