EFCoreSecondLevelCacheInterceptor 4.9.0

dotnet add package EFCoreSecondLevelCacheInterceptor --version 4.9.0                
NuGet\Install-Package EFCoreSecondLevelCacheInterceptor -Version 4.9.0                
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EFCoreSecondLevelCacheInterceptor" Version="4.9.0" />                
For projects that support PackageReference, copy this XML node into the project file to reference the package.
paket add EFCoreSecondLevelCacheInterceptor --version 4.9.0                
#r "nuget: EFCoreSecondLevelCacheInterceptor, 4.9.0"                
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
// Install EFCoreSecondLevelCacheInterceptor as a Cake Addin
#addin nuget:?package=EFCoreSecondLevelCacheInterceptor&version=4.9.0

// Install EFCoreSecondLevelCacheInterceptor as a Cake Tool
#tool nuget:?package=EFCoreSecondLevelCacheInterceptor&version=4.9.0                

EF Core 3.1.x, 5x, 6x, 7x & 8x Second Level Cache Interceptor

EFCoreSecondLevelCacheInterceptor

Second level caching is a query cache. The results of EF commands will be stored in the cache, so that the same EF commands will retrieve their data from the cache rather than executing them against the database again.

Install via NuGet

To install EFCoreSecondLevelCacheInterceptor, run the following command in the Package Manager Console:

Nuget

PM> Install-Package EFCoreSecondLevelCacheInterceptor

You can also view the package page on NuGet.

Usage (1 & 2 are mandatory)

1- Register a preferred cache provider:

You can use the following cache providers:

Using the built-in In-Memory cache provider

performance

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        private readonly string _contentRootPath;

        public Startup(IConfiguration configuration, IWebHostEnvironment env)
        {
            _contentRootPath = env.ContentRootPath;
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                       // Fallback on db if the caching provider fails.
                       .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1)))

            // Please use the `CacheManager.Core` or `EasyCaching.Redis` for the Redis cache provider.
            );

            var connectionString = Configuration["ConnectionStrings:ApplicationDbContextConnection"];
            if (connectionString.Contains("%CONTENTROOTPATH%"))
            {
                connectionString = connectionString.Replace("%CONTENTROOTPATH%", _contentRootPath);
            }
            services.AddConfiguredMsSqlDbContext(connectionString);

            services.AddControllersWithViews();
        }
    }
}
Using EasyCaching.Core as the cache provider

Here you can use the EasyCaching.Core, as a highly configurable cache manager too. To use its in-memory caching mechanism, add this entry to the .csproj file:

  <ItemGroup>
    <PackageReference Include="EasyCaching.InMemory" Version="1.6.1" />
  </ItemGroup>

Then register its required services:

namespace EFSecondLevelCache.Core.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            const string providerName1 = "InMemory1";
            services.AddEFSecondLevelCache(options =>
                    options.UseEasyCachingCoreProvider(providerName1, isHybridCache: false).ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                           // Fallback on db if the caching provider fails.
                           .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
            );

            // Add an in-memory cache service provider
            // More info: https://easycaching.readthedocs.io/en/latest/In-Memory/
            services.AddEasyCaching(options =>
            {
                // use memory cache with your own configuration
                options.UseInMemory(config =>
                {
                    config.DBConfig = new InMemoryCachingOptions
                    {
                        // scan time, default value is 60s
                        ExpirationScanFrequency = 60,
                        // total count of cache items, default value is 10000
                        SizeLimit = 100,

                        // enable deep clone when reading object from cache or not, default value is true.
                        EnableReadDeepClone = false,
                        // enable deep clone when writing object to cache or not, default value is false.
                        EnableWriteDeepClone = false,
                    };
                    // the max random second will be added to cache's expiration, default value is 120
                    config.MaxRdSecond = 120;
                    // whether enable logging, default is false
                    config.EnableLogging = false;
                    // mutex key's alive time(ms), default is 5000
                    config.LockMs = 5000;
                    // when mutex key alive, it will sleep some time, default is 300
                    config.SleepMs = 300;
                }, providerName1);
            });
        }
    }
}

If you want to use the Redis as the preferred cache provider with EasyCaching.Core, first install the following package:

  <ItemGroup>
    <PackageReference Include="EasyCaching.Redis" Version="1.6.1" />
    <PackageReference Include="EasyCaching.Serialization.MessagePack" Version="1.6.1" />
  </ItemGroup>

And then register its required services:

namespace EFSecondLevelCache.Core.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            const string providerName1 = "Redis1";
            services.AddEFSecondLevelCache(options =>
                    options.UseEasyCachingCoreProvider(providerName1, isHybridCache: false).ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                           // Fallback on db if the caching provider fails (for example, if Redis is down).
                           .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
            );

            // More info: https://easycaching.readthedocs.io/en/latest/Redis/
            services.AddEasyCaching(option =>
            {
                option.UseRedis(config =>
                {
                    config.DBConfig.AllowAdmin = true;
                    config.DBConfig.SyncTimeout = 10000;
                    config.DBConfig.AsyncTimeout = 10000;
                    config.DBConfig.Endpoints.Add(new EasyCaching.Core.Configurations.ServerEndPoint("127.0.0.1", 6379));
                    config.EnableLogging = true;
                    config.SerializerName = "Pack";
                    config.DBConfig.ConnectionTimeout = 10000;
                }, providerName1)
                .WithMessagePack(so =>
                                      {
                                         so.EnableCustomResolver = true;
                                         so.CustomResolvers = CompositeResolver.Create(
                                         new IMessagePackFormatter[]
                                         {
                                               DBNullFormatter.Instance, // This is necessary for the null values
                                         },
                                         new IFormatterResolver[]
                                         {
                                              NativeDateTimeResolver.Instance,
                                              ContractlessStandardResolver.Instance,
                                              StandardResolverAllowPrivate.Instance,
                                         });
                                       },
                                       "Pack");
            });
        }
    }
}

Here is a sample about it.

Using EasyCaching.Core as a dynamic cache provider

If you want to support multitenancy in your application and have a different Redis database per each tenant, first register multiple pre-configured providers with known providerNames and then select these providerNames based on the current tenant this way dynamically:

services.AddEFSecondLevelCache(options =>
    options.UseEasyCachingCoreProvider(
	   (serviceProvider, cacheKey) => "redis-db-" + serviceProvider.GetRequiredService<IHttpContextAccesor>().HttpContext.Request.Headers["tenant-id"],
	   isHybridCache: false)
	// `Or` you can set the cache key prefix per tenant dynamically  
	.UseCacheKeyPrefix(serviceProvider => "EF_" + serviceProvider.GetRequiredService<IHttpContextAccesor>().HttpContext.Request.Headers["tenant-id"])
	.ConfigureLogging(true)
	.UseCacheKeyPrefix("EF_")
        // Fallback on db if the caching provider fails.
        .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
);
Using CacheManager.Core as the cache provider [It's not actively maintained]

Also here you can use the CacheManager.Core, as a highly configurable cache manager too. To use its in-memory caching mechanism, add these entries to the .csproj file:

  <ItemGroup>
    <PackageReference Include="CacheManager.Core" Version="1.2.0" />
    <PackageReference Include="CacheManager.Microsoft.Extensions.Caching.Memory" Version="1.2.0" />
    <PackageReference Include="CacheManager.Serialization.Json" Version="1.2.0" />
  </ItemGroup>

Then register its required services:

namespace EFSecondLevelCache.Core.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
                options.UseCacheManagerCoreProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                       // Fallback on db if the caching provider fails.
                       .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
            );

            // Add an in-memory cache service provider
            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));
            services.AddSingleton(typeof(ICacheManagerConfiguration),
                new CacheManager.Core.ConfigurationBuilder()
                        .WithJsonSerializer()
                        .WithMicrosoftMemoryCacheHandle(instanceName: "MemoryCache1")
                        .Build());
        }
    }
}

If you want to use the Redis as the preferred cache provider with CacheManager.Core, first install the CacheManager.StackExchange.Redis package and then register its required services:

// Add Redis cache service provider
var jss = new JsonSerializerSettings
{
    NullValueHandling = NullValueHandling.Ignore,
    ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
    TypeNameHandling = TypeNameHandling.Auto,
    Converters = { new SpecialTypesConverter() }
};

const string redisConfigurationKey = "redis";
services.AddSingleton(typeof(ICacheManagerConfiguration),
    new CacheManager.Core.ConfigurationBuilder()
        .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)
        .WithUpdateMode(CacheUpdateMode.Up)
        .WithRedisConfiguration(redisConfigurationKey, config =>
        {
            config.WithAllowAdmin()
                .WithDatabase(0)
                .WithEndpoint("localhost", 6379)
                // Enables keyspace notifications to react on eviction/expiration of items.
                // Make sure that all servers are configured correctly and 'notify-keyspace-events' is at least set to 'Exe', otherwise CacheManager will not retrieve any events.
                // You can try 'Egx' or 'eA' value for the `notify-keyspace-events` too.
                // See https://redis.io/topics/notifications#configuration for configuration details.
                .EnableKeyspaceEvents();
        })
        .WithMaxRetries(100)
        .WithRetryTimeout(50)
        .WithRedisCacheHandle(redisConfigurationKey)
        .Build());
services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));

services.AddEFSecondLevelCache(options =>
    options.UseCacheManagerCoreProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
           // Fallback on db if the caching provider fails.
           .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
);

Here is the definition of the SpecialTypesConverter.

Using a custom cache provider

If you don't want to use the above cache providers, implement your custom IEFCacheServiceProvider and then introduce it using the options.UseCustomCacheProvider<T>() method.

2- Add SecondLevelCacheInterceptor to your DbContextOptionsBuilder pipeline:

    public static class MsSqlServiceCollectionExtensions
    {
        public static IServiceCollection AddConfiguredMsSqlDbContext(this IServiceCollection services, string connectionString)
        {
            services.AddDbContextPool<ApplicationDbContext>((serviceProvider, optionsBuilder) =>
                    optionsBuilder
                        .UseSqlServer(
                            connectionString,
                            sqlServerOptionsBuilder =>
                            {
                                sqlServerOptionsBuilder
                                    .CommandTimeout((int)TimeSpan.FromMinutes(3).TotalSeconds)
                                    .EnableRetryOnFailure()
                                    .MigrationsAssembly(typeof(MsSqlServiceCollectionExtensions).Assembly.FullName);
                            })
                        .AddInterceptors(serviceProvider.GetRequiredService<SecondLevelCacheInterceptor>()));
            return services;
        }
    }

Note: Some database providers don't support special fields such as DateTimeOffset, TimeSpan, etc. For these scenarios you will need the related converters.

3- Setting up the cache invalidation:

This library doesn't need any settings for the cache invalidation. It watches for all of the CRUD operations using its interceptor and then invalidates the related cache entries automatically. But if you want to invalidate the whole cache manually, inject the IEFCacheServiceProvider service and then call its _cacheServiceProvider.ClearAllCachedEntries() method or use it this way to specify the root cache keys which are a collection of a Prefix+TableName:

// Partial cache invalidation using the specified table names
// This is useful when you are monitoring your DB's changes using the SqlTableDependency
_cacheServiceProvider.InvalidateCacheDependencies(new EFCacheKey(new HashSet<string>()
{
   "EF_TableName1", // "EF_" is the cache key's prefix
   "EF_TableName2"
} {  KeyHash = "empty" }));

I you want to get notified about the cache-invalidation events and involved cache dependencies, use the NotifyCacheInvalidation method:

services.AddEFSecondLevelCache(options =>
{
   options.UseMemoryCacheProvider(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(value: 30))
                .NotifyCacheInvalidation(invalidationInfo =>
                {
                    invalidationInfo.ServiceProvider.GetRequiredService<ILoggerFactory>()
                        .CreateLogger(categoryName: "NotifyCacheInvalidation")
                        .LogWarning(message: "{Message}",
                            invalidationInfo.ClearAllCachedEntries
                                ? "Invalidated all the cache entries!"
                                : $"Invalidated [{string.Join(separator: ", ", invalidationInfo.CacheDependencies)}] dependencies.");
                })

4- To cache the results of the normal queries like:

var post1 = context.Posts
                   .Where(x => x.Id > 0)
                   .OrderBy(x => x.Id)
                   .FirstOrDefault();

We can use the new Cacheable() extension method:

var post1 = context.Posts
                   .Where(x => x.Id > 0)
                   .OrderBy(x => x.Id)
                   .Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5))
                   .FirstOrDefault();  // Async methods are supported too.

NOTE: It doesn't matter where the Cacheable method is located in this expression tree. It just adds the standard TagWith method to mark this query as Cacheable. Later SecondLevelCacheInterceptor will use this tag to identify the Cacheable queries.

Also it's possible to set the Cacheable() method's settings globally:

services.AddEFSecondLevelCache(options => options.UseMemoryCacheProvider(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5)).ConfigureLogging(true)
                                          	 .UseCacheKeyPrefix("EF_")
                                                 // Fallback on db if the caching provider fails.
                                                 .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
);

In this case the above query will become:

var post1 = context.Posts
                   .Where(x => x.Id > 0)
                   .OrderBy(x => x.Id)
                   .Cacheable()
                   .FirstOrDefault();  // Async methods are supported too.

If you specify the settings of the Cacheable() method explicitly such as Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5)), its setting will override the global setting.

Caching all of the queries

To cache all of the system's queries, just set the CacheAllQueries() method:

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
            {
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_");
                options.CacheAllQueries(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30));
                // Fallback on db if the caching provider fails.
                options.UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1));
            });

            // ...

This will put the whole system's queries in cache. In this case calling the Cacheable() methods won't be necessary. If you specify the Cacheable() method, its setting will override this global setting. If you want to exclude some of the queries from this global cache, apply the NotCacheable() method to them.

Caching some of the queries

To cache some of the system's queries based on their entity-types or table-names, use CacheQueriesContainingTypes or CacheQueriesContainingTableNames methods:

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
            {
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                       // Fallback on db if the caching provider fails.
                       .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
                    /*.CacheQueriesContainingTypes(
                        CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableTypeComparison.Contains,
                        typeof(Post), typeof(Product), typeof(User)
                        )*/
                    .CacheQueriesContainingTableNames(
                        CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30), TableNameComparison.ContainsOnly,
                        "posts", "products", "users"
                        );
            });

            // ...

This will put the the specified system's queries in cache. In this case calling the Cacheable() methods won't be necessary. If you specify the Cacheable() method, its setting will override this global setting. If you want to exclude some of the queries from this global cache, apply the NotCacheable() method to them. Also you can skip caching some of the defined DbContexts using SkipCachingDbContexts() method.

Skip caching of some of the queries

To skip caching some of the system's queries based on their SQL commands, set the SkipCachingCommands predicate:

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
            {
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                       // Fallback on db if the caching provider fails.
                       .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
                        // How to skip caching specific commands
                       .SkipCachingCommands(commandText =>
                                commandText.Contains("NEWID()", StringComparison.InvariantCultureIgnoreCase));
            });
            // ...

Skip caching of some of the queries based on their results

To skip caching some of the system's queries based on their results, set the SkipCachingResults predicate:

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
            {
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                        // Fallback on db if the caching provider fails.
                        .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
                        // Don't cache null values. Remove this optional setting if it's not necessary.
                        .SkipCachingResults(result =>
                                result.Value == null || (result.Value is EFTableRows rows && rows.RowsCount == 0));
            });
            // ...

Skip caching some of the queries based on their table names

To do not cache some of the system's queries based on their entity-types or table-names, use CacheAllQueriesExceptContainingTypes or CacheAllQueriesExceptContainingTableNames methods:

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
            {
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                      // Fallback on db if the caching provider fails.
                      .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
                    /*.CacheAllQueriesExceptContainingTypes(
                        CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30),
                        typeof(Post), typeof(Product), typeof(User)
                        )*/
                    .CacheAllQueriesExceptContainingTableNames(
                        CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30),
                        "posts", "products", "users"
                        );
            });

            // ...

This will not put the the specified system's queries in cache. In this case calling the Cacheable() methods won't be necessary. If you specify the Cacheable() method, its setting will override this global setting.

Sometimes you don't want to invalidate the cache immediately, such when you are updating a post's likes or views count. In this case to skip invalidating the related cache entries of a given CRUD command, set the SkipCacheInvalidationCommands predicate:

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
            {
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                    // Fallback on db if the caching provider fails.
                    .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))
                    .SkipCacheInvalidationCommands(commandText =>
                                // How to skip invalidating the related cache entries of this query
                                commandText.Contains("NEWID()", StringComparison.InvariantCultureIgnoreCase));
            });
            // ...

Using a different hash provider

This library uses the XxHash64Unsafe class to calculate the hash of a query and its parameters to produce a corresponding cache-key. xxHash is an extremely fast non-cryptographic Hash algorithm. If you don't like it or you want to change it, just implement the IEFHashProvider interface and then introduce it this way:

namespace EFCoreSecondLevelCacheInterceptor.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache(options =>
            {
                options.UseCustomHashProvider<MyCustomHashProvider>();
                // ...                
            });

            // ...

Disabling the interceptor for a while

If you want to disable this interceptor for a while, use the .EnableCachingInterceptor(enable: false) method. Its default value is true.

Providing options to control the serialization behavior

The EFCacheKeyProvider class serializes parameter values of a DbCommand to JSON values. If it causes an exception in some cases, you can specify a custom JsonSerializerOptions for it using the UseJsonSerializerOptions(options) method.

Does it work?!

You should enable the logging system to see the behind the scene of the caching interceptor. First set the ConfigureLogging(true):

 services.AddEFSecondLevelCache(options =>
                options.UseMemoryCacheProvider().ConfigureLogging(true).UseCacheKeyPrefix("EF_")
                       // Fallback on db if the caching provider fails.
                      .UseDbCallsIfCachingProviderIsDown(TimeSpan.FromMinutes(1))

And then change the log level to Debug in your appsettings.json file:

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Debug",
      "Microsoft": "Debug",
      "Microsoft.Hosting.Lifetime": "Debug"
    }
  }
}

Or ... you can use the second optional parameter of the ConfigureLogging method to access the published events of this library more easily:

 .ConfigureLogging(enable: environment.IsDevelopment(), cacheableEvent: args =>
            {
                    switch (args.EventId)
                    {
                        case CacheableLogEventId.CacheHit:
                            break;
                        case CacheableLogEventId.QueryResultCached:
                            break;
                        case CacheableLogEventId.QueryResultInvalidated:
                            args.ServiceProvider.GetRequiredService<ILoggerFactory>()
                                .CreateLogger(nameof(EFCoreSecondLevelCacheInterceptor))
                                .LogWarning(message: "{EventId} -> {Message} -> {CommandText}", args.EventId,
                                    args.Message, args.CommandText);
                            break;
                        case CacheableLogEventId.CachingSkipped:
                            break;
                        case CacheableLogEventId.InvalidationSkipped:
                            break;
                        case CacheableLogEventId.CachingSystemStarted:
                            break;
                        case CacheableLogEventId.CachingError:
                            break;
                        case CacheableLogEventId.QueryResultSuppressed:
                            break;
                        case CacheableLogEventId.CacheDependenciesCalculated:
                            break;
                        case CacheableLogEventId.CachePolicyCalculated:
                            break;
                    }
            })

Now after running a query multiple times, you should have these logged lines:

Suppressed result with a TableRows[ee20d2d7-ffc7-4ff9-9484-e8d4eecde53e] from the cache[KeyHash: EB153BD4, CacheDependencies: Page.].
Using the TableRows[ee20d2d7-ffc7-4ff9-9484-e8d4eecde53e] from the cache.

Notes:

  • Having the Suppressed the result with the TableRows message means the caching interceptor is working fine.
  • The next Executed DbCommand means nothing and it always will be logged by EF.
  • At the beginning there will be a lot of internal commands executed by the EF to run migrations, etc. Ignore these commands, because you will see the Suppressed the result with the TableRows messages for the frequently running queries.
  • Also you should verify it with a real DB profiler. It will log the 1st executed query and then on the 2nd run, you won't see it anymore.

Samples

Guidance

When to use

Good candidates for query caching are global site settings and public data, such as infrequently changing articles or comments. It can also be beneficial to cache data specific to a user so long as the cache expires frequently enough relative to the size of the user base that memory consumption remains acceptable. Small, per-user data that frequently exceeds the cache's lifetime, such as a user's photo path, is better held in user claims, which are stored in cookies, than in this cache.

Scope

This cache is scoped to the application, not the current user. It does not use session variables. Accordingly, when retrieving cached per-user data, be sure queries in include code such as .Where(x => .... && x.UserId == id).

Invalidation

This cache is updated when an entity is changed (insert, update, or delete) via a DbContext that uses this library. If the database is updated through some other means, such as a stored procedure or trigger, the cache becomes stale.

Transactions

To avoid complications, all of the queries inside an explicit transaction (context.Database.BeginTransaction()) will not be cached. But the cache invalidations due to its CRUD operations will occur. You can use .AllowCachingWithExplicitTransactions(true) setting to disable it.

Product Compatible and additional computed target framework versions.
.NET net5.0 is compatible.  net5.0-windows was computed.  net6.0 is compatible.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 is compatible.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 is compatible. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 is compatible. 
.NET Framework net461 was computed.  net462 is compatible.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (16)

Showing the top 5 NuGet packages that depend on EFCoreSecondLevelCacheInterceptor:

Package Downloads
NanoCore

The project is inspired by years of tedious repetitions, continuously re-writing similar code-snippets and libraries, to handle common functionality, not related to the business domain, such as logging, data persistence, message queuing, documentation, validation and similar.

GreatUtilities.Infrastructure

Essencial tools to agile development.

EaCloud.EntityFrameworkCore

EaCloud 数据访问组件,封装 EntityFrameworkCore 数据访问功能的实现。

FFCEI.Microservices

A free library for ASP.NET Core 6+ Microservices development, with Model, Model Repository, Entity Framework Core and common Web Api features like CORS, Json serialization fixes, Swagger generation, JWT Authentication for simple and objective microservices development

R8.EntityFrameworkCore

A predefined pattern for EntityFramework Core to easily store Audits.

GitHub repositories (5)

Showing the top 5 popular GitHub repositories that depend on EFCoreSecondLevelCacheInterceptor:

Repository Stars
ldqk/Masuit.MyBlogs
基于C#/.NET8的 masuit.org个人博客站项目源码,https://masuit.org ,供参考、学习、引用、非商业性质的部署。
rabbal/DNTFrameworkCore
Lightweight and Extensible Infrastructure for Building Web Applications - Web Application Framework
TheUltimateC0der/listrr
listrr.pro creates and maintains lists on trakt.tv completely automated based on your filters.
vesoapp/veso
Open source media server.
Kukks/NNostr
A Nostr Relay and Client written in C#
Version Downloads Last updated
4.9.0 3,146 12/10/2024
4.8.8 13,848 11/13/2024
4.8.7 4,319 11/10/2024
4.8.6 118 11/10/2024
4.8.5 537 11/8/2024
4.8.4 10,864 10/25/2024
4.8.3 13,720 10/13/2024
4.8.2 11,600 9/28/2024
4.8.1 289 9/27/2024
4.8.0 5,663 9/21/2024
4.7.1 41,771 8/21/2024
4.7.0 6,536 8/14/2024
4.6.0 32,421 7/21/2024
4.5.0 83,958 5/24/2024
4.4.3 62,085 4/22/2024
4.4.2 881 4/21/2024
4.4.1 15,455 4/5/2024
4.4.0 382 4/5/2024
4.3.1 10,955 3/31/2024
4.3.0 441 3/30/2024
4.2.3 46,628 3/2/2024
4.2.2 19,517 2/20/2024
4.2.1 9,508 2/19/2024
4.2.0 41,347 1/24/2024
4.1.2 11,780 1/19/2024
4.1.1 46,893 12/12/2023
4.1.0 1,352 12/9/2023
4.0.1 505 12/9/2023
4.0.0 57,873 10/31/2023
3.9.5 8,308 10/24/2023
3.9.4 6,544 10/17/2023
3.9.3 639 10/17/2023
3.9.2 269,496 5/27/2023
3.9.1 44,583 5/1/2023
3.9.0 5,450 4/27/2023
3.8.8 29,474 4/15/2023
3.8.7 303 4/15/2023
3.8.6 110,334 3/15/2023
3.8.5 55,049 2/25/2023
3.8.3 188,373 2/3/2023
3.8.2 63,683 1/10/2023
3.8.1 70,114 12/9/2022
3.8.0 29,284 11/26/2022
3.7.5 18,688 11/12/2022
3.7.4 3,297 11/9/2022
3.7.3 27,278 10/14/2022
3.7.2 1,837 10/10/2022
3.7.1 933 10/10/2022
3.7.0 17,426 9/30/2022
3.6.3 79,397 8/3/2022
3.6.2 205,762 7/8/2022
3.6.1 56,934 6/15/2022
3.6.0 1,330 6/14/2022
3.5.1 40,223 6/9/2022
3.5.0 11,185 5/25/2022
3.4.0 152,718 3/18/2022
3.3.0 146,969 1/8/2022
3.2.5 17,320 1/6/2022
3.2.4 40,859 12/8/2021
3.2.3 32,949 11/23/2021
3.2.2 7,141 11/18/2021
3.2.1 12,955 11/11/2021
3.2.0 105,754 9/1/2021
3.1.2 64,438 7/12/2021
3.1.1 92,487 6/4/2021
3.1.0 13,113 5/26/2021
3.0.0 3,732 5/20/2021
2.9.0 4,824 5/16/2021
2.8.0 2,318 5/9/2021
2.7.0 29,474 4/30/2021
2.6.0 1,128 4/29/2021
2.5.0 15,060 4/14/2021
2.4.1 104,117 2/9/2021
2.4.0 4,350 2/5/2021
2.3.1 28,236 1/20/2021
2.3.0 19,298 12/28/2020
2.2.0 4,877 12/21/2020
2.1.0 23,610 12/9/2020
2.0.1 14,390 11/11/2020
2.0.0 12,542 10/16/2020
1.9.2 1,894 10/15/2020
1.9.1 313 10/15/2020
1.9.0 5,692 10/14/2020
1.8.2 260,982 7/25/2020
1.8.1 4,029 7/21/2020
1.8.0 1,843 7/17/2020
1.7.2 28,068 5/18/2020
1.7.1 1,097 5/14/2020
1.7.0 24,848 5/1/2020
1.6.0 4,554 4/24/2020
1.5.5 1,346 4/22/2020
1.5.4 1,504 4/19/2020
1.5.3 1,145 4/17/2020
1.5.2 1,355 4/13/2020
1.5.1 10,129 4/3/2020
1.5.0 1,081 4/3/2020
1.4.0 3,947 4/3/2020
1.3.4 2,180 3/31/2020
1.3.3 1,128 3/30/2020
1.3.2 1,011 3/30/2020
1.3.1 1,892 3/22/2020
1.3.0 1,062 3/22/2020
1.2.0 1,081 3/22/2020
1.1.2 1,164 3/20/2020
1.1.0 2,686 3/2/2020
1.0.0 1,778 2/15/2020