One way to speed up our applications is by adding a caching layer next to our database. Often we write helper methods to read data from our database, serialize it, and store it in our cache. NCache integrates with Entity Framework Core to cache our entities with a few lines of code. Let’s cover how to implement a cache-aside strategy with NCache and Entity Framework Core.
Entity Framework Core doesn’t have a built-in caching mechanism. We have to roll our own. But, NCache has a set of convenient extension methods to cache our query results easily.
With this integration, we take advantage of all scalability and replication features of NCache in our Entity Framework applications.
Let’s create a sample ASP.NET Core 6.0 application to cache an Entity Framework Core query to show the 10 best-rated movies in a catalog.
How to register NCache with Entity Framework Core
Before starting, let’s make sure to have NCache installed. We need either an Enterprise or Professional edition.
After creating an ASP.NET Core Web API application, let’s install the EntityFrameworkCore.NCache NuGet package. If we’re using a Professional edition, we install the EntityFrameworkCore.NCache.Professional NuGet package instead.
To register NCache with Entity Framework, in the Program.cs file of our application, let’s write,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
using Alachisoft.NCache.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; var builder = WebApplication.CreateBuilder(args); builder.Services.AddDbContext(options => { var config = builder.Configuration; var connectionString = config.GetConnectionString("Default"); // Configure NCache with Entity Framework // We need a cacheId and database type NCacheConfiguration.Configure(cacheId: "demoCache", DependencyType.SqlServer); NCacheConfiguration.ConfigureLogger(); options.UseSqlServer(connectionString); }); builder.Services.AddControllers(); var app = builder.Build(); app.MapControllers(); app.Run(); |
That looks like a normal ASP.NET Core application that uses Entity Framework Core, except for two methods: Configure() and ConfigureLogger() from NCacheConfiguration.
With the Configure() method, we specify the cacheId and database type. We’re using demoCache, the default cache instance, and DependencyType.SqlServer. Of course, we should read the cacheId from a configuration file.
In this example, we’re only using a cacheId and database type, but we can also pass extra configuration options like retries and timeouts.
And ConfigureLogger() setups the underlying logging abstraction from ASP.NET Core.
How to insert and cache Entity Framework Core results in NCache
After registering NCache into Entity Framework Core, we’re reading to start caching our movies.
Caching Entity Framework Core results
Let’s create a MoviesController class to read the 10 best movies and cache them in NCache. Like this,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
using Alachisoft.NCache.EntityFrameworkCore; using Microsoft.AspNetCore.Mvc; namespace Movies.Controllers; [ApiController] [Route("[controller]")] public class MoviesController : ControllerBase { private readonly DatabaseContext _database; public MoviesController(DatabaseContext database) { _database = database; } [HttpGet] public async Task<IEnumerable> Get() { return await _database .Movies .OrderByDescending(m => m.Rating) .ThenBy(m => m.ReleaseYear) .Take(10) .FromCacheAsync(new CachingOptions { StoreAs = StoreAs.Collection }); } } |
NCache brings new extension methods on top of the existing Entity Framework Core methods.
After a LINQ query to retrieve 10 movies sorted by rating and release year, we add a new method: FromCacheAsync().
FromCacheAsync()
caches the result of our LINQ query and returns it. If our cache doesn’t have this result, it goes to the database and then caches it. If we can’t connect to our cache server, NCache will return the result from our database. Then it will keep retrying to populate the cache with our result.
When using FromCacheAsync(), we pass a CachingOptions parameter, telling NCache to cache the result of our query as a whole instead of separate entries. There are more options to specify expirations and priorities.
FromCacheAsyn() has a synchronous alternative FromCache()
. Unlike its async version, FromCache() returns the cache key of the new item as an output parameter.
LoadIntoCache and FromCacheOnly
Apart from FromCache() and FromCacheAsync(), NCache has another two methods: LoadIntoCache() and FromCacheOnly().
LoadIntoCache() overwrites the cached results on every call. It works like FromCache() with a cache miss on every call. This method is best suited for frequently changed data and scenarios where we always need a fresh copy of our data.
FromCacheOnly() doesn’t call the underlying database. It always goes to the cache server. FromCacheOnly() supports aggregation functions like Sum, Min, and Max. But, it also has some limitations. For example, FromCacheOnly() doesn’t support multiple projections or joins in the LINQ expression.
Of course, these last two methods have async alternatives too.
Inserting items
After caching the results of our LINQ queries, let’s see how to insert items.
In the MoviesController
class, let’s write a new method to add a movie. Like this,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
// In the same MoviesController.cs file [HttpPost] public async Task Post([FromBody] AddMovie request) { var newMovie = new Movie( name: request.Name, releaseYear: request.ReleaseYear, rating: request.Rating); _database.Movies.Add(newMovie); await _database.SaveChangesAsync(); var options = new CachingOptions { StoreAs = StoreAs.SeperateEntities }; Cache cache = _database.GetCache(); cache.Insert(newMovie, out _, options); } |
After inserting a movie as we normally do with SaveChangesAsync(), we get a reference to our cache with GetCache(). Then, using the same object reference, we insert a new movie with Insert() passing the CachingOptions. We can also pass a priority, query identifier, and expiration times.
When we use Insert() if the cache already contains an item, it gets updated instead.
Notice we inserted the new item into our cache right after we called SaveChangesAsync(), without going again to the database to retrieve it and then cache it. That saves one roundtrip to our database per each object we want to insert.
Conclusion
That’s how we can use NCache in our Entity Framework Core applications. We can start caching our entities with a few changes to our existing LINQ queries. We only need to extend our Entity Framework configuration
and use FromCache() and its alternatives. In this post, we cached and inserted items, but we can also remove items from our cache by a key name, query identifier, or an object reference.
Let’s remember, for complex LINQ queries, let’s use FromCache(). For frequently updated data, let’s use LoadIntoCache(). And for read-only data, FromCacheOnly().
Apart from the Cache-aside strategy we followed in this post, NCache supports other caching strategies and patterns like Read-Through and Write-Through. And it integrates with the ASP.NET Core framework to support Session State Caching.
To follow along with the code we wrote in this post, check my NCache Demo Repository on GitHub.