All Projects → VahidN → Efcoresecondlevelcacheinterceptor

VahidN / Efcoresecondlevelcacheinterceptor

Licence: apache-2.0
EF Core Second Level Cache Interceptor

Projects that are alternatives of or similar to Efcoresecondlevelcacheinterceptor

Dntidentity
A highly customized sample of the ASP.NET Core Identity
Stars: ✭ 145 (-36.12%)
Mutual labels:  entity-framework, entity-framework-core, aspnetcore
Efsecondlevelcache.core
Entity Framework Core Second Level Caching Library
Stars: ✭ 300 (+32.16%)
Mutual labels:  entity-framework, entity-framework-core, aspnetcore
Onion Architecture Asp.net Core
WhiteApp API solution template which is built on Onion Architecture with all essential feature using .NET 5!
Stars: ✭ 196 (-13.66%)
Mutual labels:  entity-framework, entity-framework-core, aspnetcore
Aspnetcore Ddd
Full ASP.NET Core 3.1 LTS application with DDD, CQRS and Event Sourcing
Stars: ✭ 88 (-61.23%)
Mutual labels:  entity-framework, aspnetcore
Entityframework.commontools
Extensions, Auditing, Concurrency Checks, JSON properties and Transaction Logs for EntityFramework and EFCore
Stars: ✭ 82 (-63.88%)
Mutual labels:  entity-framework, entity-framework-core
Sample Dotnet Core Cqrs Api
Sample .NET Core REST API CQRS implementation with raw SQL and DDD using Clean Architecture.
Stars: ✭ 1,273 (+460.79%)
Mutual labels:  entity-framework, entity-framework-core
Entityframework.docs
Documentation for Entity Framework Core and Entity Framework 6
Stars: ✭ 888 (+291.19%)
Mutual labels:  entity-framework, entity-framework-core
Maikebing.entityframeworkcore.taos
Entity, Framework, EF, Core, Data, O/RM, entity-framework-core,TDengine
Stars: ✭ 113 (-50.22%)
Mutual labels:  entity-framework, entity-framework-core
Module Shop Mini Program
一个基于 .NET Core构建的简单、跨平台、模块化的商城系统
Stars: ✭ 89 (-60.79%)
Mutual labels:  entity-framework-core, aspnetcore
Dotnetlabs
.NET Labs -- Show Me the Tips and Tricks and Code
Stars: ✭ 135 (-40.53%)
Mutual labels:  entity-framework, entity-framework-core
Run Aspnetcore Realworld
E-Commerce real world example of run-aspnetcore ASP.NET Core web application. Implemented e-commerce domain with clean architecture for ASP.NET Core reference application, demonstrating a layered application architecture with DDD best practices. Download 100+ page eBook PDF from here ->
Stars: ✭ 208 (-8.37%)
Mutual labels:  entity-framework-core, aspnetcore
Run Aspnetcore Cqrs
Real world Enterprise CRM application example of ASP.NET Core + Angular web application. Implemented CQRS Design Pattern for ASP.NET Core + Angular reference application, demonstrating a layered application architecture with DDD best practices. Download 100+ page eBook PDF from here ->
Stars: ✭ 152 (-33.04%)
Mutual labels:  entity-framework, aspnetcore
Localization
🌏 Database Resource Localization for .NET Core with Entity Framework and In Memory Cache
Stars: ✭ 68 (-70.04%)
Mutual labels:  entity-framework, entity-framework-core
Znetcs.aspnetcore.logging.entityframeworkcore
This is Entity Framework Core logger and logger provider. A small package to allow store logs in any data store using Entity Framework Core.
Stars: ✭ 24 (-89.43%)
Mutual labels:  entity-framework-core, aspnetcore
Entityframework.lazyloading
LazyLoading for EF Core
Stars: ✭ 23 (-89.87%)
Mutual labels:  entity-framework, entity-framework-core
Blazorwasmefcoreexample
Example of a Blazor WebAssembly project that uses Entity Framework Core on the server for data access.
Stars: ✭ 105 (-53.74%)
Mutual labels:  entity-framework, entity-framework-core
Linq2db.entityframeworkcore
Bring power of Linq To DB to Entity Framework Core projects
Stars: ✭ 166 (-26.87%)
Mutual labels:  entity-framework, entity-framework-core
Efcorepowertools
Entity Framework Core Power Tools - reverse engineering, migrations and model visualization for EF Core
Stars: ✭ 774 (+240.97%)
Mutual labels:  entity-framework, entity-framework-core
Efcore.pg
Entity Framework Core provider for PostgreSQL
Stars: ✭ 838 (+269.16%)
Mutual labels:  entity-framework, entity-framework-core
Entityframework Plus
Entity Framework Plus extends your DbContext with must-haves features: Include Filter, Auditing, Caching, Query Future, Batch Delete, Batch Update, and more
Stars: ✭ 1,848 (+714.1%)
Mutual labels:  entity-framework, entity-framework-core

EF Core 3.1.x & 5.x Second Level Cache Interceptor

GitHub Actions status

Entity Framework Core 3.1.x & 5.x Second Level Caching Library

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:

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().DisableLogging(true)

            // 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.1.0" />
  </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).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 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.1.0" />
  </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).DisableLogging(true)
            );

            // 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));
                }, providerName1);
            });
        }
    }
}

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().DisableLogging(true)
            );

            // 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().DisableLogging(true)
);

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;
        }
    }

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 ClearAllCachedEntries() method.

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)).DisableLogging(true));

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().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.

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().DisableLogging(true)
                    /*.CacheQueriesContainingTypes(
                        CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30),
                        typeof(Post), typeof(Product), typeof(User)
                        )*/
                    .CacheQueriesContainingTableNames(
                        CacheExpirationMode.Absolute, TimeSpan.FromMinutes(30),
                        "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.

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().DisableLogging(true)
                        // 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().DisableLogging(true)
                        // 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 invalidating the related cache entries of a given query

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().DisableLogging(true)
                    .SkipCacheInvalidationCommands(commandText =>
                                // How to skip invalidating the related cache entries of this query
                                commandText.Contains("NEWID()", StringComparison.InvariantCultureIgnoreCase));
            });
            // ...

Does it work?!

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

 services.AddEFSecondLevelCache(options =>
                options.UseMemoryCacheProvider().DisableLogging(false)

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"
    }
  }
}

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.

Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].