It will be explained in this article how to integrate Entity Framework Core with a caching engine using NCache. The article will give a practical example of how we could set up our Entity Framework Core in a Console application and how to make use of NCache to make faster requests to the database with its native in-memory distributed cache.

What is Entity Framework Core?

Entity Framework Core is Microsoft's most recent ORM - Object Relational Mapper, that helps software applications map, connect, and manage entities to a wide range of databases. Entity Framework Core is open source and cross-platform, being the top 1 ORM used by software using Microsoft technologies.

At the moment of writing this article, Entity Framework Core offers two ways to connect your entities to the database:

Code First, writing your project's entities first and then reflecting those objects in the database;
Database First, have your database created first and then generate your project's entities.

What is NCache?
NCache is also open-source and cross-platform software. Its cache server offers a scalable in-memory distributed cache for .NET, Java, Scala, Python, and Node.js. As this article will be focusing on .NET technologies, we can use NCache to take advantage of the following usages:

  • ASP.NET session state storage;
  • ASP.NET view state caching;
  • ASP.NET output cache;
  • Entity Framework cache;
  • NHibernate second-level cache.

NCache with Entity Framework Core
We can add a layer of cache between the Entity Framework Core and our application with NCache, this would improve our queries response time and reduce the necessity of round trips to the database as far as we would get data from NCache cached entities.

Caching Options

NCache gives the possibility to have a different set of options to be sent from each request, meaning that we can use the cache differently based on the result set that we are working with in order to be more efficient.

As we are going to see in the practical samples, we must provide the cache options on each request to NCache and those options are the following:
AbsoluteExpirationTime, sets the absolute time when the cached item will expire;
    Data type: Datetime
CreateDbDependency, creates or not a database dependency from the result set;
    Data type: boolean
ExpirationType, sets the expiration type:
IsSyncEnabled, sets if the expired items must be re-synced with the database.
    Data type: boolean
Priority, sets the relative priority of items stored in the cache.
QueryIdentifier, result set identifier.
    Data type: string.
ReadThruProvider, sets the read thru provider for cache sync
    Data type: string
SlidingExpirationTime, sets the sliding expiration time
    Data type: TimeSpan
StoreAs, sets how the items are to be stored.

Deferred Calls
NCache has its own extension methods for us to work with Entity Framework Core deferred calls and they are in 3 different groups:

Aggregate Operators, making operations against collections. Can be used with both FromCache and FromCacheOnly methods.
        Products.Select(o => o.UnitPrice).DeferredAverage()
        Customers.Select(c => c.Country).GroupBy(c => c).DeferredCount()
        Orders.Where(o => o.CustomerId == "VINET").Select(o => o.RequiredDate).DeferredMin()
        Orders.Select(o => o.RequiredDate).DeferredMax()
        OrderDetails.Select(o => o.UnitPrice).DeferredSum()
Element Operators, making operations for single elements. Can be used only with the FromCache method.
        Customers.DeferredElementAtOrDefault(c => c.City == "London")
        Customers.DeferredFirst(c => c.ContactTitle == "Sales Representative")
        Customers.DeferredFirstOrDefault(c => c.ContactTitle == "Sales Representative")
        Customers.DeferredLast(c => c.City == "London")
        Customers.DeferredLastOrDefault(c => c.City == "London")
        Customers.DeferredSingle(c => c.CustomerId == "ALFKI")
        Customers.DeferredSingleOrDefault(c => c.CustomerId == "ANATR")
Others. Can be used only with the FromCache method.
        Products.DeferredContains(new Products { ProductId = 1 })

Caching Methods

NCache's methods to manipulate cached objects:

Insert a single object in the cache with its own options. Returns the cache key
var customerEntity = new Customers
   CustomerId = "HANIH",
   ContactName = "Hanih Moos",
   ContactTitle = "Sales Representative ",
   CompanyName = "Blauer See Delikatessen"

//Add customer entity to database

//Caching options for cache
var options = new CachingOptions
   QueryIdentifier = "CustomerEntity",
   Priority = Runtime.CacheItemPriority.Default,

//Add customer entity to cache
Cache cache = database.GetCache();

cache.Insert(customerEntity, out string cacheKey, options);

Remove (object Entity)
Remove a single object from the cache.
var cust = new Customers
  CustomerId = "PETER",
  ContactName = "Peter Scott",
  ContactTitle = "Sales Representative",
  CompanyName = "Hostforlife"


Remove (string cacheKey)
Remove an object by passing its cache key

Remove all entities from the cache which match the query identifier
Tag tag = new Tag(queryIdentifier);

Caching using NCache extension methods
NCache's Extension methods for Entity Framework Core

Gets the cache instance.
using (var context = new NorthwindContext())
Cache cache = context.GetCache();

If there is cached data, then it will be returned without going through the data source. If there is no data cached, then data will be returned from the data source and cached.
var options = new CachingOptions
StoreAs = StoreAs.SeperateEntities

var resultSet = (from cust in context.Customers
             where cust.CustomerId == 10
             select cust).FromCache(options);

Returning the cacheKey from the result set
var options = new CachingOptions
StoreAs = StoreAs.Collection

var resultSet = (from cust in context.Customers
            where cust.CustomerId == 10
            select cust).FromCache(out string cacheKey, options);

Every request goes first to the data source, caches its result set, and returns it.
var options = new CachingOptions
    StoreAs = StoreAs.SeperateEntities

var resultSet = (from custOrder in context.Orders
                 where custOrder.Customer.CustomerId == 10
                 select custOrder)).LoadIntoCache(options);

Returning the cache key from the result set
var options = new CachingOptions
StoreAs = StoreAs.Collection

var resultSet = (from custOrder in context.Orders
             where custOrder.Customer.CustomerId == 10
             select custOrder)).LoadIntoCache(out string cacheKey, options);

Never goes to the data source. The request is only going to the cache, if no matching result is cached, then it will be returned as an empty result set.
Includes and joins are not supported by FromCacheOnly().
var resultSet = (from cust in context.Customers
             where cust.CustomerId == someCustomerId
             select cust).FromCacheOnly();

NCache Implementation Step by Step
0. Pre-Requisites

Have NCache running on your machine. Access your NCache through http://localhost:8251/

1. The application
Create a C# console application targeting .NET 6.0 and install the following nugets packages:
The following NCache files will be inserted into your project after installing the Nuget Packages.

2. Models
For this sample, it was created a very simple model relationship, as follows:
public class Product
public int Id { get; set; }
public string Name { get; set; }
public double Price { get; set; }
public List<Transaction> Transactions { get; set; }
public Store Store { get; set; }
public int? StoreId { get; set; }

public class Store
public int Id { get; set; }
public string Name { get; set; }
public string Location { get; set; }
public ICollection<Product> AvailableProducts { get; set; }
public ICollection<Consumer> RegularConsumers { get; set; }

public class Consumer
public int Id { get; set; }
public string Name { get; set; }
public Store FavouriteStore { get; set; }
public int? FavouriteStoreId { get; set; }
public List<Transaction> Transactions { get; set; }

public class Transaction
 public int Id { get; set; }

 public Consumer Consumer { get; set; }
 public int ConsumerId { get; set; }

 public Product Product { get; set; }
 public int ProductId { get; set; }

The DBContext class has NCache initialization settings and the model's relationship.

public class SampleDbContext : DbContext
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    // configure cache with SQLServer DependencyType and CacheInitParams
    CacheConnectionOptions initParams = new CacheConnectionOptions();
    initParams.RetryInterval = new TimeSpan(0, 0, 5);
    initParams.ConnectionRetries = 2;
    initParams.ConnectionTimeout = new TimeSpan(0, 0, 5);
    initParams.AppName = "appName";
    initParams.CommandRetries = 2;
    initParams.CommandRetryInterval = new TimeSpan(0, 0, 5);
    initParams.Mode = IsolationLevel.Default;

    NCacheConfiguration.Configure("democache", DependencyType.SqlServer, initParams);

    optionsBuilder.UseSqlServer(@"Data Source=DESKTOP-AT3H2E;Initial Catalog=sampleDatabase;Integrated Security=True");

protected override void OnModelCreating(ModelBuilder modelBuilder)
        .HasMany(x => x.AvailableProducts)
        .WithOne(x => x.Store)
        .HasForeignKey(x => x.StoreId)

        .HasMany(x => x.RegularConsumers)
        .WithOne(x => x.FavouriteStore)
        .HasForeignKey(x => x.FavouriteStoreId)

        .HasOne(x => x.Consumer)
        .WithMany(x => x.Transactions)
        .HasForeignKey(x => x.ConsumerId)

        .HasOne(x => x.Product)
        .WithMany(x => x.Transactions)
        .HasForeignKey(x => x.ProductId)

public DbSet<Store> Stores { get; set; }
public DbSet<Consumer> Consumers { get; set; }
public DbSet<Product> Products { get; set; }
public DbSet<Transaction> Transactions { get; set; }

3. NCache Methods
Here is the class with NCache methods needed to manipulate objects from and to the cache.
public class NCacheExtensions
private SampleDbContext Database { get; set; }
private CachingOptions CachingOptions { get; set; }
private Cache Cache { get; set; }

public NCacheExtensions(SampleDbContext database)
    this.Database = database;
    this.CachingOptions = new CachingOptions
        QueryIdentifier = "Sample QueryIdentifier",
        Priority = Alachisoft.NCache.Runtime.CacheItemPriority.Default,
        CreateDbDependency = false,
        StoreAs = StoreAs.Collection

    Cache = database.GetCache();

public string AddSingleEntity<T>(T entity)
    Cache.Insert(entity, out string cacheKey, this.CachingOptions);
    return cacheKey;
public void RemoveSingleEntity<T>(T entity)
public void RemoveSingleEntity(string cacheKey)
public void RemoveByQueryIdentifier(string queryIdentifier)
    var tag = new Tag(queryIdentifier);

public IEnumerable<Consumer> GetAllConsumersFromCache(CachingOptions cachingOptions)
    return Database.Consumers.Include(x => x.Transactions).ThenInclude(x => x.Product).FromCache(cachingOptions);
public async Task<IEnumerable<Consumer>> GetAllConsumersFromCacheAsync(CachingOptions cachingOptions)
    return await Database.Consumers.Include(x => x.Transactions).ThenInclude(x => x.Product).FromCacheAsync(cachingOptions);
public IEnumerable<Consumer> LoadAllConsumersIntoCache(CachingOptions cachingOptions)
    return Database.Consumers.Include(x => x.Transactions).ThenInclude(x => x.Product).LoadIntoCache(cachingOptions);
public async Task<IEnumerable<Consumer>> LoadAllConsumersIntoCacheAsync(CachingOptions cachingOptions)
    return await Database.Consumers.Include(x => x.Transactions).ThenInclude(x => x.Product).LoadIntoCacheAsync(cachingOptions);
public IEnumerable<Consumer> GetAllConsumersFromCacheOnly(CachingOptions cachingOptions)
    return Database.Consumers.FromCacheOnly();

4.The program.cs class
Here we have the start point of our console application. With an example on how to connect to NCache and use its extension methods that were provided above.
class Program
static void Main(string[] args)
    Console.WriteLine("Hello World!");

    using (var context = new SampleDbContext())
        var cachedContext = new NCacheExtensions(context);

        Console.WriteLine("start LoadAllConsumersIntoCache " + DateTime.Now.ToString("HH:mm:ss.f"));
        var loadInCache = cachedContext.LoadAllConsumersIntoCache(new CachingOptions { StoreAs = StoreAs.Collection, QueryIdentifier = "Sample QueryIdentifier" });
        Console.WriteLine("finish LoadAllConsumersIntoCache" + DateTime.Now.ToString("HH:mm:ss.f"));

        Console.WriteLine("start GetAllConsumersFromCache " + DateTime.Now.ToString("HH:mm:ss.f"));
        var getFromCache = cachedContext.GetAllConsumersFromCache(new CachingOptions { Priority = Alachisoft.NCache.Runtime.CacheItemPriority.Default });
        Console.WriteLine("finish GetAllConsumersFromCache " + DateTime.Now.ToString("HH:mm:ss.f"));

        Console.WriteLine("start load from DBContext " + DateTime.Now.ToString("HH:mm:ss.f"));
        var getFromDb = context.Consumers.Include(x => x.Transactions).ThenInclude(x => x.Product);
        Console.WriteLine("finishg load from DBContext " + DateTime.Now.ToString("HH:mm:ss.f"));

        var cachedEntity = cachedContext.AddSingleEntity<Consumer>(getFromDb.FirstOrDefault());
        Console.WriteLine("cache key: " + cachedEntity);

        cachedContext.RemoveByQueryIdentifier("Sample QueryIdentifier");


Application working: