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.
To install EFCoreSecondLevelCacheInterceptor, run the following command in the Package Manager Console:
PM>Install-Package EFCoreSecondLevelCacheInterceptorYou can also view the package page on NuGet.
namespaceEFCoreSecondLevelCacheInterceptor.AspNetCoreSample{publicclassStartup{privatereadonlystring_contentRootPath;publicStartup(IConfigurationconfiguration,IWebHostEnvironmentenv){_contentRootPath=env.ContentRootPath;Configuration=configuration;}publicIConfigurationConfiguration{get;}publicvoidConfigureServices(IServiceCollectionservices){services.AddEFSecondLevelCache(options =>options.UseMemoryCacheProvider().DisableLogging(true)// Please use the `CacheManager.Core` or `EasyCaching.Redis` for the Redis cache provider.);varconnectionString=Configuration["ConnectionStrings:ApplicationDbContextConnection"];if(connectionString.Contains("%CONTENTROOTPATH%")){connectionString=connectionString.Replace("%CONTENTROOTPATH%",_contentRootPath);}services.AddConfiguredMsSqlDbContext(connectionString);services.AddControllersWithViews();}}}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>
<PackageReferenceInclude="EasyCaching.InMemory"Version="0.8.8" />
</ItemGroup>Then register its required services:
namespaceEFSecondLevelCache.Core.AspNetCoreSample{publicclassStartup{publicvoidConfigureServices(IServiceCollectionservices){conststringproviderName1="InMemory1";services.AddEFSecondLevelCache(options =>options.UseEasyCachingCoreProvider(providerName1).DisableLogging(true));// 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 configurationoptions.UseInMemory(config =>{config.DBConfig=newInMemoryCachingOptions{// scan time, default value is 60sExpirationScanFrequency=60,// total count of cache items, default value is 10000SizeLimit=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 120config.MaxRdSecond=120;// whether enable logging, default is falseconfig.EnableLogging=false;// mutex key's alive time(ms), default is 5000config.LockMs=5000;// when mutex key alive, it will sleep some time, default is 300config.SleepMs=300;},providerName1);});}}}If you want to use the Redis as the preferred cache provider with EasyCaching.Core, first install the following package:
<ItemGroup>
<PackageReferenceInclude="EasyCaching.Redis"Version="0.8.8" />
</ItemGroup>And then register its required services:
namespaceEFSecondLevelCache.Core.AspNetCoreSample{publicclassStartup{publicvoidConfigureServices(IServiceCollectionservices){conststringproviderName1="Redis1";services.AddEFSecondLevelCache(options =>options.UseEasyCachingCoreProvider(providerName1).DisableLogging(true));// More info: https://easycaching.readthedocs.io/en/latest/Redis/services.AddEasyCaching(option =>{option.UseRedis(config =>{config.DBConfig.AllowAdmin=true;config.DBConfig.Endpoints.Add(newEasyCaching.Core.Configurations.ServerEndPoint("127.0.0.1",6379));},providerName1);});}}}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>
<PackageReferenceInclude="CacheManager.Core"Version="1.2.0" />
<PackageReferenceInclude="CacheManager.Microsoft.Extensions.Caching.Memory"Version="1.2.0" />
<PackageReferenceInclude="CacheManager.Serialization.Json"Version="1.2.0" />
</ItemGroup>Then register its required services:
namespaceEFSecondLevelCache.Core.AspNetCoreSample{publicclassStartup{publicvoidConfigureServices(IServiceCollectionservices){services.AddEFSecondLevelCache(options =>options.UseCacheManagerCoreProvider().DisableLogging(true));// Add an in-memory cache service providerservices.AddSingleton(typeof(ICacheManager<>),typeof(BaseCacheManager<>));services.AddSingleton(typeof(ICacheManagerConfiguration),newCacheManager.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 providervarjss=newJsonSerializerSettings{NullValueHandling=NullValueHandling.Ignore,ReferenceLoopHandling=ReferenceLoopHandling.Ignore,TypeNameHandling=TypeNameHandling.Auto,Converters={newSpecialTypesConverter()}};conststringredisConfigurationKey="redis";services.AddSingleton(typeof(ICacheManagerConfiguration),newCacheManager.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().DisableLogging(true));Here is the definition of the SpecialTypesConverter.
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:
publicstaticclassMsSqlServiceCollectionExtensions{publicstaticIServiceCollectionAddConfiguredMsSqlDbContext(thisIServiceCollectionservices,stringconnectionString){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>()));returnservices;}}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 ClearAllCachedEntries() method.
varpost1=context.Posts.Where(x =>x.Id>0).OrderBy(x =>x.Id).FirstOrDefault();We can use the new Cacheable() extension method:
varpost1=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)).DisableLogging(true));In this case the above query will become:
varpost1=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.
To cache all of the system's queries, just set the CacheAllQueries() method:
namespaceEFCoreSecondLevelCacheInterceptor.AspNetCoreSample{publicclassStartup{publicvoidConfigureServices(IServiceCollectionservices){services.AddEFSecondLevelCache(options =>{options.UseMemoryCacheProvider().DisableLogging(true);options.CacheAllQueries(CacheExpirationMode.Absolute,TimeSpan.FromMinutes(30));});// ...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.
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.
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).
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.