Giter Site home page Giter Site logo

vahidn / efsecondlevelcache.core Goto Github PK

View Code? Open in Web Editor NEW
326.0 18.0 51.0 273 KB

Entity Framework Core Second Level Caching Library

License: Apache License 2.0

C# 99.75% Batchfile 0.25%
aspnetcore entity-framework efcore entityframework entity-framework-core

efsecondlevelcache.core's Introduction

EFSecondLevelCache.Core

GitHub Actions status

Entity Framework Core 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 EFSecondLevelCache.Core, run the following command in the Package Manager Console:

Nuget

PM> Install-Package EFSecondLevelCache.Core

You can also view the package page on NuGet.

This library also uses the CacheManager.Core, as a highly configurable cache manager. To use its in-memory caching mechanism, add these entries to the .csproj file:

  <ItemGroup>
    <PackageReference Include="EFSecondLevelCache.Core" Version="2.9.0" />
    <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>

And to get the latest versions of these libraries you can run the following command in the Package Manager Console:

PM> Update-Package

Usage

1- Register the required services of EFSecondLevelCache.Core and also CacheManager.Core

namespace EFSecondLevelCache.Core.AspNetCoreSample
{
    public class Startup
    {
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddEFSecondLevelCache();

            // 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")
                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))
                        .Build());
        }
    }
}

If you want to use the Redis as the preferred cache provider, 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
};

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.
                // See https://redis.io/topics/notifications#configuration for configuration details.
                .EnableKeyspaceEvents();
        })
        .WithMaxRetries(100)
        .WithRetryTimeout(50)
        .WithRedisCacheHandle(redisConfigurationKey)
        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))
        .Build());
services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));

2- Setting up the cache invalidation by overriding the SaveChanges method to prevent stale reads:

namespace EFSecondLevelCache.Core.AspNetCoreSample.DataLayer
{
    public class SampleContext : DbContext
    {
        public SampleContext(DbContextOptions<SampleContext> options) : base(options)
        { }

        public virtual DbSet<Post> Posts { get; set; }

        public override int SaveChanges()
        {
            var changedEntityNames = this.GetChangedEntityNames();

            this.ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.
            var result = base.SaveChanges();
            this.ChangeTracker.AutoDetectChangesEnabled = true;

            this.GetService<IEFCacheServiceProvider>().InvalidateCacheDependencies(changedEntityNames);

            return result;
        }
    }
}

3- Then to cache the results of the normal queries like:

var products = context.Products.Include(x => x.Tags).FirstOrDefault();

We can use the new Cacheable() extension method:

// If you don't specify the `EFCachePolicy`, the global `new CacheManager.Core.ConfigurationBuilder().WithExpiration()` setting will be used automatically.
var products = context.Products.Include(x => x.Tags).Cacheable().FirstOrDefault(); // Async methods are supported too.

// Or you can specify the `EFCachePolicy` explicitly to override the global settings.
var post1 = context.Posts
                   .Where(x => x.Id > 0)
                   .OrderBy(x => x.Id)
                   .Cacheable(CacheExpirationMode.Sliding, TimeSpan.FromMinutes(5))
                   .FirstOrDefault();

// NOTE: It's better to add the `Cacheable()` method before the materialization methods such as `ToList()` or `FirstOrDefault()` to cover the whole expression tree.

Also AutoMapper's ProjectTo() method is supported:

var posts = context.Posts
                   .Where(x => x.Id > 0)
                   .OrderBy(x => x.Id)
                   .Cacheable()
                   .ProjectTo<PostDto>(configuration: _mapper.ConfigurationProvider)
                   .ToList();

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 retriveing 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.

efsecondlevelcache.core's People

Contributors

artemdintecom avatar breyed avatar exytab avatar vahidn avatar wei-taoxu avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

efsecondlevelcache.core's Issues

Request support for Find / FindAsync

Request functionality to support Find / FindAsync calls. When using a Generic Repository pattern, I use these functions to retrieve a record by Id where the Id is of type Object.

خطای AD0001

بعد از افزودن خط زیر خطای زیر مشاهده می شود

services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));

Warning AD0001 Analyzer 'Microsoft.CodeAnalysis.CSharp.Analyzers.MetaAnalyzers.CSharpDiagnosticAnalyzerApiUsageAnalyzer' threw an exception of type 'System.NullReferenceException' with message 'Object reference not set to an instance of an object.'.

Queries are not cached if a variable is used in the query.

Summary of the issue

Queries are not cached (IsCacheHit is always false) if a variable is used in the query.

Environment

The in-use version: 2.4.0
Operating system:  Windows 10 / .NET Core 2.2
IDE: (e.g. Visual Studio 2015) Visual Studio 2019
Database: MySQL with Pomelo.EntityFrameworkCore.Mysql (2.2.0)

Example code/Steps to reproduce:

Working code:

var result = _dbContext.Tests.Where(p => p.TestId == 1).Cacheable(dinfo).FirstOrDefault();

as expected, on the first call IsCacheHit is false, on subsequent calls it's true

Non-Working code:

var t = 1;
var result = _dbContext.Tests.Where(p => p.TestId == t).Cacheable(dinfo).FirstOrDefault();

on all calls, IsCacheHit is false. Also EFCacheKey.KeyHash changes on every call, as does the Key inside EFCacheKey.Key

Output:

Non-Working output of EFCacheDebugInfo (KeyHash changes with every call):

IsCacheHit: False

EFCacheKey.KeyHash: 13A01C94

EFCacheKey.Key:
SELECT `p`.`TestId`, `p`.`Content`
FROM `Tests` AS `p`
WHERE `p`.`TestId` = 1
LIMIT 1;6EED9AE3;.Call System.Linq.Queryable.FirstOrDefault(.Call Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTracking(.Call System.Linq.Queryable.Where(
            .Constant(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[corecache.Data.Test]),
            '(.Lambda #Lambda1))))

.Lambda #Lambda1(corecache.Data.Test $p) {
    $p.TestId == .Constantc__DisplayClass6_0>(corecache.Controllers.HomeController+<>c__DisplayClass6_0).t
};

Working output of EFCacheDebugInfo

IsCacheHit: True

EFCacheKey.KeyHash: 41B68F6F

EFCacheKey.Key:
SELECT `p`.`TestId`, `p`.`Content`
FROM `Tests` AS `p`
WHERE `p`.`TestId` = 1
LIMIT 1;3213A02B;.Call System.Linq.Queryable.FirstOrDefault(.Call Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTracking(.Call System.Linq.Queryable.Where(
            .Constant(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[corecache.Data.Test]),
            '(.Lambda #Lambda1))))

.Lambda #Lambda1(corecache.Data.Test $p) {
    $p.TestId == 1
};

Same problem occurs with FirstOrDefault:

 var result = _dbContext.Tests.Cacheable(dinfo).FirstOrDefault(p => p.TestId == t);

It dosen't work in my application

I used redis as cache provider but it dosen't work, i can't find any key in redis db,
so I switched to another model(WithMicrosoftMemoryCacheHandle), It does not seem to work too.
I wrote two action for compare cache and nocache, but the time-consuming seems almost.
I need your help @VahidN .

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IConfigurationRoot>(provider => { return Configuration; });
            services.AddDbContext<SinoautoContext>(ServiceLifetime.Scoped);
            // Add framework services.
            var redisConnStr = Configuration.GetConnectionString("RedisConnetionString");
            var redisConn = ConnectionMultiplexer.Connect(redisConnStr);
            
            services.AddEFSecondLevelCache();
            services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));
            services.AddSingleton(typeof(ICacheManagerConfiguration),
                new CacheManager.Core.ConfigurationBuilder()
                    .WithJsonSerializer()
                    //.WithRedisConfiguration("Redis", redisConn)
                    //.WithRedisCacheHandle("Redis")
                    .WithMicrosoftMemoryCacheHandle()
                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromSeconds(10))
                    .Build()
            );
            services.AddMvc();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
            app.UseEFSecondLevelCache();
            app.UseMvc();
        }
        [HttpGet]
        public IActionResult Get()
        {
            var list = db.HttpLogs.Take(1000).ToList();
            return Json(list);
        }

        [HttpGet("cache")]
        public IActionResult GetByCache()
        {
            var list = db.HttpLogs.Take(1000).Cacheable().ToList();
            return Json(list);
        }

Breaking changes regarding efcore 3.0.0 type: ExpressionPrinter

Summary of the issue

Migrating to dotnetcore 3 (preview 8) from version 2.2, seems to break caching.

Environment

.NET Core SDK version: 3.0.0-preview8.19405.11
Microsoft.EntityFrameworkCore version: 3.0.0.0
EFSecondLevelCache.Core version: 2.6.1

Example code/Steps to reproduce:

public static async Task<IPagedList<T>> ToPagedListAsync<T>(this IQueryable<T> source, int pageIndex, int pageSize, int indexFrom = 0, CancellationToken cancellationToken = default(CancellationToken))
        {
            if (indexFrom > pageIndex)
            {
                throw new ArgumentException($"indexFrom: {indexFrom} > pageIndex: {pageIndex}, must indexFrom <= pageIndex");
            }

            var count = await source.CountAsync(cancellationToken).ConfigureAwait(false);
            var items = await source.Skip((pageIndex - indexFrom) * pageSize)
                                    .Take(pageSize)
                                    .Cacheable<T>()
                                    .ToListAsync(cancellationToken).ConfigureAwait(false);

            var pagedList = new PagedList<T>()
            {
                PageIndex = pageIndex,
                PageSize = pageSize,
                IndexFrom = indexFrom,
                TotalCount = count,
                Items = items,
                TotalPages = (int)Math.Ceiling(count / (double)pageSize)
            };

            return pagedList;
        }

Output:

Exception message: An unhandled exception occurred while processing the request.
TypeLoadException: Could not load type 'Microsoft.EntityFrameworkCore.Query.Internal.ExpressionPrinter' from assembly 'Microsoft.EntityFrameworkCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'.

EFSecondLevelCache.Core.EFCacheKeyProvider.GetEFCacheKey<T>(IQueryable<T> query, Expression expression, string saltKey)

Full Stack trace:

EFSecondLevelCache.Core.EFCacheKeyProvider.GetEFCacheKey<T>(IQueryable<T> query, Expression expression, string saltKey)
EFSecondLevelCache.Core.EFCachedQueryProvider<TType>.Materialize(Expression expression, Func<object> materializer)
EFSecondLevelCache.Core.EFCachedQueryable<TType>.GetAsyncEnumerator(CancellationToken cancellationToken)
System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable<T>.GetAsyncEnumerator()
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken)
Microsoft.EntityFrameworkCore.IQueryablePageListExtensions.ToPagedListAsync<T>(IQueryable<T> source, int pageIndex, int pageSize, int indexFrom, CancellationToken cancellationToken) in IQueryablePageListExtensions.cs

Does the example you have work with the standard library

I used to have this working in 1.x but just recently started to move to 2.x.

I think I am missing a ref cause vs2017 complains about:

.WithMicrosoftMemoryCacheHandle()
.WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))

not sure what i am missing, at one point you had an example for the standard library but don't see it anymore

Include functionality giving error

Summary of the issue

I am using Cacheable in the following manner in Repository to include the entities which are passed but it is giving object ref error after long thinking, I am using it with Redis cache

Environment

The in-use version:
.net core 2.0
ef core 2.1.0 rc1 final
Operating system:  Win 10
IDE: (e.g. Visual Studio 2015)

Example code/Steps to reproduce:

public async Task<IEnumerable<TEntity>> GetAllAsync(params Expression<Func<TEntity, object>>[] includes)
        {
            IQueryable<TEntity> set = dbContext.Set<TEntity>();
            return  (await includes.Aggregate(set, (current, include) => current.Include(include)).Cacheable().ToListAsync() ?? default(IEnumerable<TEntity>));
                
        }

//Calling

GetAllAsync(b=>b.IncludeEntity1,c=>c.IncludeEntity2)

Output:

Exception message: Object reference not set to an instance of an object.
Full Stack trace:    at System.Text.StringBuilder.Append(String value)
   at System.IO.StringWriter.Write(String value)
   at Newtonsoft.Json.JsonWriter.AutoCompleteClose(JsonContainerType type)
   at Newtonsoft.Json.JsonWriter.AutoCompleteAll()
   at CacheManager.Serialization.Json.JsonCacheSerializer.Serialize[T](T value)
   at CacheManager.Redis.RedisValueConverter.CacheManager.Redis.IRedisValueConverter<System.Object>.ToRedisValue(Object value)
   at CacheManager.Redis.RedisCacheHandle`1.Set(CacheItem`1 item, When when, Boolean sync)
   at CacheManager.Redis.RetryHelper.Retry[T](Func`1 retryme, Int32 timeOut, Int32 retries, ILogger logger)
   at CacheManager.Core.BaseCacheManager`1.AddItemToHandle(CacheItem`1 item, BaseCacheHandle`1 handle)
   at CacheManager.Core.BaseCacheManager`1.AddInternal(CacheItem`1 item)
   at EFCache.EFCacheServiceProvider.InsertValue(String cacheKey, Object value, ISet`1 rootCacheKeys) in <path>\EFCacheServiceProvider.cs:line 77

EF Core 3.0 support

Summary of the issue

It doesn't support EF Core 3.0, could you add support for EF Core 3.0?
Thanks.

How to separate IQueryable With Key ?

.NET Core SDK version: 2.2.7
Microsoft.EntityFrameworkCore version: 2.2.6
EFSecondLevelCache.Core version: 2.8.1


## Example code/Steps to reproduce:

IQueryable<OrderListDTO> AsNoTracking(IOrderedQueryable<OrderListDTO> OrderByDescending(
    source: IQueryable<OrderListDTO> Take(
        source: IQueryable<OrderListDTO> Skip(
            source: IQueryable<OrderListDTO> Select(
                source: IOrderedQueryable<OrderRM> OrderByDescending(
                    source: IQueryable<OrderRM> Where(
                        source: DbSet<OrderRM>, 
                        predicate: o => value(System.Collections.Generic.List`1[ADTN.Branding.Common.Enums.OrderStatus]).Contains(o.Status)
                    ), 
                    keySelector: o => o.ForDate
                ), 
                selector: o => new OrderListDTO() {Id = o.Id, Code = Convert(o.Code, Int64), Status = o.Status, StationCode = o.Station.Code, StationName = o.Station.Name, IsStatusChanged = o.IsStatusChanged, Date = o.Date.ToUnixTime(), ForDate = o.ForDate.ToUnixTime(), Description = o.Description, FuelPackage = new FuelPackageDTO() {Title = o.FuelPackage.Title, Quantity = o.FuelPackage.Quantity, Fuel = new FuelDTO() {Type = o.FuelPackage.Fuel.Type, Name = o.FuelPackage.Fuel.Name}}, Assignment = o.Assignments.Select(a => new AssignmentDTO() {Id = a.Id, SerialNo = a.SerialNo, DeliveryDate = a.DeliveryDate.ToUnixTime(), Date = a.Date.ToUnixTime(), Quantity = a.Quantity, PaymentType = a.PaymentType, BillAmount = a.BillAmount, IsConfirmed = a.IsConfirmed, AssignmentBankBillTotalAmount = a.AssignmentBankBills.Sum(c => c.Amount), IsMultiPrice = a.IsMultiPrice, AssignmentAdditionalInformations = value(AutoMapper.Mapper).Map(a.AssignmentAdditionalInformations), Fuel = new FuelDTO() {Type = o.FuelPackage.Fuel.Type, Name = o.FuelPackage.Fuel.Name}}).SingleOrDefault(), CMR = o.CMRs.Select(cmr => new CMRDTO() {Id = cmr.Id, SentDate = cmr.SentDate.ToUnixTime(), ReceivedDate = cmr.ReceivedDate.ToUnixTime(), IsMultiPrice = cmr.IsMultiPrice, CMRAdditionalInformations = value(AutoMapper.Mapper).Map(cmr.CMRAdditionalInformations)}).SingleOrDefault()}
            ), 
            count: 0), 
        count: 10), 
    keySelector: T => T.Date
));.Call Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsNoTracking(.Call System.Linq.Queryable.OrderByDescending(
        .Call System.Linq.Queryable.Take(
            .Call System.Linq.Queryable.Skip(
                .Call System.Linq.Queryable.Select(
                    .Call System.Linq.Queryable.OrderByDescending(
                        .Call System.Linq.Queryable.Where(
                            .Constant<Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ADTN.Branding.ReadSide.Model.Entities.OrderRM]>(Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[ADTN.Branding.ReadSide.Model.Entities.OrderRM]),
                            '(.Lambda #Lambda1<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.OrderRM,System.Boolean]>)),
                        '(.Lambda #Lambda2<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.OrderRM,System.DateTimeOffset]>)),
                    '(.Lambda #Lambda3<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.OrderRM,ADTN.Branding.Common.DTOs.CustomDTOs.OrderListDTO]>))
                ,
                0),
            10),
        '(.Lambda #Lambda4<System.Func`2[ADTN.Branding.Common.DTOs.CustomDTOs.OrderListDTO,System.Int64]>)))

.Lambda #Lambda1<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.OrderRM,System.Boolean]>(ADTN.Branding.ReadSide.Model.Entities.OrderRM $o)
{
    .Call ((.Constant<ADTN.Branding.ReadSide.Service.Services.OrderService+<>c__DisplayClass1_0>(ADTN.Branding.ReadSide.Service.Services.OrderService+<>c__DisplayClass1_0).filter.Constant<ADTN.Branding.ReadSide.Service.Services.OrderService+<>c__DisplayClass1_0>(ADTN.Branding.Common.CustomEntities.Filters.OrderListFilter)).OrderStatus).Contains($o.Status)
}

.Lambda #Lambda2<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.OrderRM,System.DateTimeOffset]>(ADTN.Branding.ReadSide.Model.Entities.OrderRM $o)
{
    $o.ForDate
}

.Lambda #Lambda3<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.OrderRM,ADTN.Branding.Common.DTOs.CustomDTOs.OrderListDTO]>(ADTN.Branding.ReadSide.Model.Entities.OrderRM $o)
{
    .New ADTN.Branding.Common.DTOs.CustomDTOs.OrderListDTO(){
        Id = $o.Id,
        Code = (System.Int64)$o.Code,
        Status = $o.Status,
        StationCode = ($o.Station).Code,
        StationName = ($o.Station).Name,
        IsStatusChanged = $o.IsStatusChanged,
        Date = .Call ADTN.Branding.Common.Extensions.DateExtensions.ToUnixTime($o.Date),
        ForDate = .Call ADTN.Branding.Common.Extensions.DateExtensions.ToUnixTime($o.ForDate),
        Description = $o.Description,
        FuelPackage = .New ADTN.Branding.Common.DTOs.FuelPackageDTO(){
            Title = ($o.FuelPackage).Title,
            Quantity = ($o.FuelPackage).Quantity,
            Fuel = .New ADTN.Branding.Common.DTOs.FuelDTO(){
                Type = (($o.FuelPackage).Fuel).Type,
                Name = (($o.FuelPackage).Fuel).Name
            }
        },
        Assignment = .Call System.Linq.Enumerable.SingleOrDefault(.Call System.Linq.Enumerable.Select(
                $o.Assignments,
                .Lambda #Lambda5<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.AssignmentRM,ADTN.Branding.Common.DTOs.AssignmentDTO]>)
        ),
        CMR = .Call System.Linq.Enumerable.SingleOrDefault(.Call System.Linq.Enumerable.Select(
                $o.CMRs,
                .Lambda #Lambda6<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.CMRRM,ADTN.Branding.Common.DTOs.CMRDTO]>))
    }
}

.Lambda #Lambda4<System.Func`2[ADTN.Branding.Common.DTOs.CustomDTOs.OrderListDTO,System.Int64]>(ADTN.Branding.Common.DTOs.CustomDTOs.OrderListDTO $T)
{
    $T.Date
}

.Lambda #Lambda5<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.AssignmentRM,ADTN.Branding.Common.DTOs.AssignmentDTO]>(ADTN.Branding.ReadSide.Model.Entities.AssignmentRM $a)
{
    .New ADTN.Branding.Common.DTOs.AssignmentDTO(){
        Id = $a.Id,
        SerialNo = $a.SerialNo,
        DeliveryDate = .Call ADTN.Branding.Common.Extensions.DateExtensions.ToUnixTime($a.DeliveryDate),
        Date = .Call ADTN.Branding.Common.Extensions.DateExtensions.ToUnixTime($a.Date),
        Quantity = $a.Quantity,
        PaymentType = $a.PaymentType,
        BillAmount = $a.BillAmount,
        IsConfirmed = $a.IsConfirmed,
        AssignmentBankBillTotalAmount = .Call System.Linq.Enumerable.Sum(
            $a.AssignmentBankBills,
            .Lambda #Lambda7<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.AssignmentBankBillRM,System.Decimal]>),
        IsMultiPrice = $a.IsMultiPrice,
        AssignmentAdditionalInformations = .Call (.Constant<ADTN.Branding.ReadSide.Service.Services.OrderService>(ADTN.Branding.ReadSide.Service.Services.OrderService).Mapper.Constant<ADTN.Branding.ReadSide.Service.Services.OrderService>(AutoMapper.Mapper)).Map($a.AssignmentAdditionalInformations)
        ,
        Fuel = .New ADTN.Branding.Common.DTOs.FuelDTO(){
            Type = (($o.FuelPackage).Fuel).Type,
            Name = (($o.FuelPackage).Fuel).Name
        }
    }
}

.Lambda #Lambda6<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.CMRRM,ADTN.Branding.Common.DTOs.CMRDTO]>(ADTN.Branding.ReadSide.Model.Entities.CMRRM $cmr)
{
    .New ADTN.Branding.Common.DTOs.CMRDTO(){
        Id = $cmr.Id,
        SentDate = .Call ADTN.Branding.Common.Extensions.DateExtensions.ToUnixTime($cmr.SentDate),
        ReceivedDate = .Call ADTN.Branding.Common.Extensions.DateExtensions.ToUnixTime($cmr.ReceivedDate),
        IsMultiPrice = $cmr.IsMultiPrice,
        CMRAdditionalInformations = .Call (.Constant<ADTN.Branding.ReadSide.Service.Services.OrderService>(ADTN.Branding.ReadSide.Service.Services.OrderService).Mapper.Constant<ADTN.Branding.ReadSide.Service.Services.OrderService>(AutoMapper.Mapper)).Map($cmr.CMRAdditionalInformations)
    }
}

.Lambda #Lambda7<System.Func`2[ADTN.Branding.ReadSide.Model.Entities.AssignmentBankBillRM,System.Decimal]>(ADTN.Branding.ReadSide.Model.Entities.AssignmentBankBillRM $c)
{
    $c.Amount
};

سلام وحید جان

سلام و روز بخیر وحید جان. من میخوام از سکوند لول کشت استفاده کنم توی یه پروژه نسبتن بزرگ. اگه موردی بود تونستم اصلاح کنم پول رکوست میفرستم

مرسی برای زحمتی که کشیدی

How To Mock Or Config EFSecondLevelCache.Core In Unit Testing?

Summary of the issue

When we use EFSecondLevelCache.Core for for caching query, and write a test for these methods, get an exception in saveChange.

Exception:

Unable to resolve service for type 'EFSecondLevelCache.Core.Contracts.IEFCacheServiceProvider'. This is often because no database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.

Is there a way for mocked or inject config EFSecondLevelCache.Core in unit testing?

returning incorrect rows

Summary of the issue

returning incorrect rows in this specific case - or my error :(

Environment

Visual Studio 2017 Community
.NET Core 2.0
PostgreSQL 10, Windows, 64bit
Win 10 Pro 64bit

Example code/Steps to reproduce:

table:

 CREATE TABLE dict.payment_type_tr (
   id SERIAL,
   id_payment_type INTEGER,
   def BOOLEAN DEFAULT false,
   language VARCHAR(11),
   value VARCHAR(25),
   mod_date TIMESTAMP WITH TIME ZONE DEFAULT now() NOT NULL,
   mod_id_user BIGINT NOT NULL,
   CONSTRAINT payment_type_tr_pkey PRIMARY KEY(id),
 ) 
 WITH (oids = false);
 
 CREATE UNIQUE INDEX payment_type_tr_idx ON dict.payment_type_tr
   USING btree (id_payment_type, language COLLATE pg_catalog."default");

example data:
image

DBContext:

 modelBuilder.Entity<PaymentTypeTr>(entity =>
            {
                entity.ToTable("payment_type_tr", "dict");
                entity.HasIndex(e => new { e.IdPaymentType, e.Language }).HasName("payment_type_tr_idx").IsUnique();
                entity.Property(e => e.Id).HasColumnName("id").HasDefaultValueSql("nextval('dict.payment_type_tr_id_seq'::regclass)");
                entity.Property(e => e.Def).HasColumnName("def");
                entity.Property(e => e.IdPaymentType).HasColumnName("id_payment_type");
                entity.Property(e => e.Language).HasColumnName("language");
                entity.Property(e => e.ModDate).HasColumnName("mod_date").HasColumnType("timestamptz");
                entity.Property(e => e.ModIdUser).HasColumnName("mod_id_user");
                entity.Property(e => e.Value).HasColumnName("value");
            });

Getting data:

public class TranslateHelper : ITranslateHelper, IRepository
    {       
        public async Task<T> GetTranslate<T>(DbSet<T> dbSet, int IdObject, string language) where T : class, ITranslate
        => await dbSet
                  .Where(u => u.IdMainObject == IdObject && u.Language.Equals(language))
                  .Cacheable()
                  .SingleOrDefaultAsync();

Output:

In this case, IdMainObject, IdObject point to the id_payment_type in the array
Change of the second parameter (language) causes returning the correct line, changing the IdObject with the constant language returns one row all the time:
first call:

IdObject = 1, language = en - the row returned with id 1 is correct

another with diffrent IdObject:

IdObject = 2, language = en => id 1 incorrect
IdObject = 3, language = en => id 1 incorrect

and change language:

IdObject = 3, language = pl => id 6. correct

and again another calls with diffrent IdObject:

IdObject = 2, language = pl => id 6. incorrect
IdObject = 1, language = pl => id 6. incorrect

I tried to modify the table - the main key was IdPaymentType and language but the effect was identical ...

But the downloading of lines immediately after the ID works fine

   public async Task<PaymentTypeTr> GetAsync(int id)
            => await _context.PaymentTypeTr
                        .Where(u => u.Id == id)
                        .Cacheable()
                        .FirstOrDefaultAsync();

Rationale for cache limitations

The readme recommends only caching public data, It would be good to provide the rationale for this, especially since caching commonly displayed per-user data can sometimes provide a nice performance improvement.

Help with DI and EFSecondLevelCache

Hi -

I am trying to implement EFSecondLevelCache into my project which also uses SimpleInjector. My issue is when the DI container is verified, it cannot resolve the "IEFCacheServiceProvider cacheServiceProvider". I assume there is something simple I am missing, but if anyone has any insights, I appreciate it. My Startup looks like this:

public partial class Startup
    {
    private readonly Container _container = new Container();

    public IConfigurationRoot Configuration { get; }

    public Startup(IHostingEnvironment env)
    {
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json", optional: true, reloadOnChange: true)
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
            .AddEnvironmentVariables();

        Configuration = builder.Build();
    }
    
    // This method gets called by the runtime. Use this method to add services to the _container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddCors(options =>
        {
            options.AddPolicy("corsPolicy", policy =>
            {
                policy
                    .AllowAnyOrigin()
                    .AllowCredentials()
                    .AllowAnyHeader()
                    .AllowAnyMethod();
            });
        });

        var loggerFactory = new LoggerFactory();
        loggerFactory.AddNLog();
        loggerFactory.AddDebug(LogLevel.Debug);
        
        var builder = services.AddMvc();
        builder.AddMvcOptions(o => { o.Filters.Add(new CorsAuthorizationFilterFactory("corsPolicy")); });
        builder.AddMvcOptions(o => { o.Filters.Add(new GlobalExceptionFilter(loggerFactory)); });

        services.Configure<AppSettings>(Configuration.GetSection("AppSettings"));
        services.Configure<ConnectionStrings>(Configuration.GetSection("ConnectionStrings"));

        AddDbContextToService(services);
        
        services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        services.AddSingleton<IControllerActivator>(new SimpleInjectorControllerActivator(_container));
        services.AddSingleton<IViewComponentActivator>(new SimpleInjectorViewComponentActivator(_container));
        services.AddSingleton<IControllerActivator>(new SimpleInjectorControllerActivator(_container));
        services.AddSingleton<IViewComponentActivator>(new SimpleInjectorViewComponentActivator(_container));

        services.UseSimpleInjectorAspNetRequestScoping(_container);

        services.AddEFSecondLevelCache();
        AddRedisCacheServiceProvider(services);
    }
    
    public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
    {
        app.UseEFSecondLevelCache();
        app.UseCors("corsPolicy");
        app.UseDefaultFiles();
        app.UseStaticFiles(new StaticFileOptions
        {
            OnPrepareResponse = ctx =>
            {
                ctx.Context.Response.Headers["Access-Control-Allow-Origin"] = "*";
            }
        });

        loggerFactory.AddProvider(new DbLogging.SqlLoggerProvider());
        loggerFactory.AddNLog();
        env.ConfigureNLog("nlog.config");
        
        InitializeContainer(app);

        _container.Verify();

        //clear the cache
        var cacheManager = new Common.Caching.CacheManager(Configuration.GetConnectionString("RedisCache"));
        cacheManager.FlushServer();

        loggerFactory.AddConsole(Configuration.GetSection("Logging"));
        loggerFactory.AddDebug();

        app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
        {
            //Authority = "http://localhost:5000/",
            Authority = "http://xxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
            RequireHttpsMetadata = false,
            ApiName = "Jsa"
        });

        app.UseMvc();
    }

    private void InitializeContainer(IApplicationBuilder app)
    {
        _container.Options.DefaultScopedLifestyle = new AsyncScopedLifestyle();

        _container.RegisterMvcControllers(app);
        _container.RegisterMvcViewComponents(app);
        _container.RegisterSingleton(app.ApplicationServices.GetService<ILoggerFactory>());
        _container.RegisterSingleton(app.ApplicationServices.GetService<IHostingEnvironment>());
        
        RegisterMediator();
        RegisterRepositories();
        RegisterJsaSpecificItems();
        RegisterAutomapper();
        RegisterDbContext();
    }

    private static void AddRedisCacheServiceProvider(IServiceCollection services)
    {
        var jss = new JsonSerializerSettings
        {
            NullValueHandling = NullValueHandling.Ignore,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore
        };

        const string redisConfigurationKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=";
        services.AddSingleton(typeof(ICacheManagerConfiguration),
            new CacheManager.Core.ConfigurationBuilder()
                .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)
                .WithUpdateMode(CacheUpdateMode.Up)
                .WithRedisConfiguration(redisConfigurationKey, config =>
                {
                    config.WithAllowAdmin()
                        .WithDatabase(0)
                        .WithEndpoint("xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", 6380);
                })
                .WithMaxRetries(100)
                .WithRetryTimeout(50)
                .WithRedisCacheHandle(redisConfigurationKey)
                .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))
                .Build());
        services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));
    }
}`

Keys not being generated

Summary of the issue

I am unable to get the correct response from the Redis Cache, it's not creating the collection of keys or creating other keys once it already has the keys for any other get request.

Environment

The in-use version: 1.6.1
Operating system: Windows 10
IDE: VS 2017

Example code/Steps to reproduce:

  1. Add a couple of Employees
  2. Copy & Paste any Id from the Get All request to the Get By Id
  3. See the Redis Cache
  4. Try a couple of other Ids and check the results both on Redis and response you get

Opiniated.zip

Output:

I am not getting the correct response.

Problem faced in q.cachable(debuginfo)

Summary of the issue

        q.Cacheable(debugInfo) throws error.

If I remove the q.Cacheable(debuginfo) line, the error vanishes. The application is running without any issues without this line. When we include this line, the error starts.

Environment

The in-use version:
Operating system:  windows 10 professional
IDE: (e.g. Visual Studio 2015) :VS 2017 Pro

Example code/Steps to reproduce:

the query() method joins two tables and contains a include statement (query1.include(query))
paste your core code
var q = from c in this.Query()
                where c.Buid == buid
                orderby c.Buid
                select c;
        var debugInfo = new EFCacheDebugInfo();
        debugInfo.EFCacheKey = new EFCacheKey();
        
        var temp = debugInfo.IsCacheHit;
        q.Cacheable(debugInfo);
        return await q.ToListAsync();

Output:

Exception message:
Full Stack trace:
  at EFSecondLevelCache.Core.EFCachedQueryProvider`1.Materialize(Expression expression, Func`1 materializer)
   at EFSecondLevelCache.Core.EFCachedQueryable`1.System.Collections.Generic.IEnumerable<TType>.GetEnumerator()
   at EFSecondLevelCache.Core.EFCachedQueryable`1.get_AsyncEnumerable()
   at Microsoft.EntityFrameworkCore.Extensions.Internal.QueryableExtensions.AsAsyncEnumerable[TSource](IQueryable`1 source)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

Variable item expiration

Hi again

What's the best way to implement specific cache expiration rules for a single query? We have one query that needs to be refreshed much more frequently than the other 99% of queries. It would be nice if there were a simple override to Cacheable() that just let us pass TotalSeconds to cache.

thanks!
jasen

How can I Cache SP's with EFSecondLevelCache lib

Hi! I am using EFSecondLevelCache and it's working thanks.
I am using SP's in EF
I want to cache SP's with EFSecondLevelCache ? Is this possible?

string Result = string.Format("exec Get_BinaryImg_By_UserId9600 @IdUser = {0}", Userid );
GetBinaryImg _Result = DataSQLHelper.ExecSQL<GetBinaryImg>(Result, _context).ToList()[0]; 

Support order by?

I tried plugging in your cache, but I get this when i do an order by:

Unable to cast object of type 'EFSecondLevelCache.Core.EFCachedQueryable`1[MontagePortal.DTO.Base.CrmRefDTO]' to type 'System.Linq.IOrderedQueryable

Cache incorrectly used when include is changed

Summary of the issue

If you call the same query but only change the include, the cached value is used, ignoring the different include.

Environment

The in-use version: 2.3.0
Operating system: Windows 10
IDE: Visual Studio Code

Example code/Steps to reproduce:

public class LineItem {
	public string Id { get; set; }
	public string OrderId { get; set; }
	public virtual Order Order { get; set; }
}
public class Test {
	public override IEnumerable<LineItem> Get(Expression<Func<LineItem, bool>> filter = null, params Expression<Func<LineItem, object>>[] include)
	{
		IQueryable<LineItem> query = _dbSet;
		query = query.Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromMinutes(5));

		if (filter != null)
			query = query.Where(filter);

		if (include != null)
		{
			foreach (var includeProperty in include.ToList())
				query = query.Include(includeProperty);
		}
		return query.ToList();
	}
	public void Test() {
		var lineItem1 = Get(x => x.Id == "abc");
		var lineItem2 = Get(x => x.Id == "abc", x => x.Order);
		Console.WriteLine(lineItem2.Order.Id);
	}
}

Output:

Exception message: object not set to an instance of an object
lineItem2.Order is null, if you comment out the Cacheable line lineItem2.Order is not null, so it appears a cached value is being returned even through the include has changed between lineItem1 and lineItem2.

Cannot cast EFCachedQueryable to IOrderedQueryable

Summary of the issue

Message: System.InvalidCastException : Unable to cast object of type 'EFSecondLevelCache.Core.EFCachedQueryable1[Whatever.Model.PricingEntities.PriceCalculationParameter]' to type 'System.Linq.IOrderedQueryable1[Whatever.Model.PricingEntities.PriceCalculationParameter]'.

Environment

All

Example code/Steps to reproduce:

        var priceCalculationParameters = await pricingContext.PriceCalculationParameters.Cacheable()
            .Where(x => x.PricingId == currentPricing.PricingId)
            .OrderBy(x => x.PriceValidityIntervals.Min(y => y.Start))
            .ToListAsync().ConfigureAwait(false);

Output:

Test Name: CalculateRoughReservedEndTime_WithReservedAmountAndStartingWithinPaidTimeInterval_ShouldReturnTheSameEndTime(Monday)
Test FullName: Whatever.Backend.Tests.Service.PriceCalculationServiceTests.CalculateRoughReservedEndTime_WithReservedAmountAndStartingWithinPaidTimeInterval_ShouldReturnTheSameEndTime(Monday)
Test Source: C:\Users\gkisonas\Projects\Whatever\Whatever.Backend.Tests\Service\PriceCalculationServiceTests.cs : line 2824
Test Outcome: Failed
Test Duration: 0:00:07.111

Result StackTrace:
at System.Linq.Queryable.OrderBy[TSource,TKey](IQueryable1 source, Expression1 keySelector)
at Whatever.Backend.Services.PriceCalculationService.d__46.MoveNext() in C:\Users\gkisonas\Projects\Whatever\Whatever.Backend\Services\PriceCalculationService.cs:line 934
--- End of stack trace from previous location where exception was thrown ---
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter1.GetResult() at Whatever.Backend.Tests.Service.PriceCalculationServiceTests.<CalculateRoughReservedEndTime_WithReservedAmountAndStartingWithinPaidTimeInterval_ShouldReturnTheSameEndTime>d__71.MoveNext() in C:\Users\gkisonas\Projects\Whatever\Whatever.Backend.Tests\Service\PriceCalculationServiceTests.cs:line 2841 --- End of stack trace from previous location where exception was thrown --- at NUnit.Framework.Internal.AsyncInvocationRegion.AsyncTaskInvocationRegion.WaitForPendingOperationsToComplete(Object invocationResult) at NUnit.Framework.Internal.Commands.TestMethodCommand.RunAsyncTestMethod(TestExecutionContext context) Result Message: System.InvalidCastException : Unable to cast object of type 'EFSecondLevelCache.Core.EFCachedQueryable1[Whatever.Model.PricingEntities.PriceCalculationParameter]' to type 'System.Linq.IOrderedQueryable`1[Whatever.Model.PricingEntities.PriceCalculationParameter]'.

Unable to cast object of type 'EFSecondLevelCache.Core.Contracts.EFCacheKey' to type **

I used redis for cahce provider in my app, when the first time to fetch data from db, it will return a empty result and cache the result to the redis, but i found the cached data is incorrent, the cached value is '{"CacheDependencies": []}' and the value type is 'EFSecondLevelCache.Core.Contracts.EFCacheKey, EFSecondLevelCache.Core, Version=1.0.3.0, Culture=neutral, PublicKeyToken=null' , so it willl throw a castexception when attemp read again.

Code:
var entity=context.Set<ProductCategorie>().Where(m=>!m.Deleted).Cacheable().FirstOrDefault();

Error:
System.InvalidCastException:“Unable to cast object of type 'EFSecondLevelCache.Core.Contracts.EFCacheKey' to type 'Jazea.Chain.Domain.Entity.ProductCategorie'.”

Thread safe caching

Summary of the issue

In a multi threaded application i receive random the following error:

System.InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()

Environment

The in-use version: EFSecondLevelCache.Core 1.4.0
Operating system: Windows 10
IDE: Visual Studio 2017

Example code/Steps to reproduce:

public async Task<List<T>> GetAllAsync()
{
	var cacheKey = $"{typeof(T).Name}.{nameof(this.GetAllAsync)}";

	return await this.dbContext
		.Query<T>()
		.Cacheable(cacheKey)
		.ToListAsync();
}

Output:

InvalidOperationException: A second operation started on this context before a previous operation completed. Any instance members are not guaranteed to be thread safe.
Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider+ExceptionInterceptor+EnumeratorExceptionInterceptor.MoveNext()
System.Collections.Generic.LargeArrayBuilder.AddRange(IEnumerable<T> items)
System.Collections.Generic.EnumerableHelpers.ToArray<T>(IEnumerable<T> source)
System.Linq.Enumerable.ToArray<TSource>(IEnumerable<TSource> source)
EFSecondLevelCache.Core.EFCachedQueryProvider.Materialize(Expression expression, Func<object> materializer)
EFSecondLevelCache.Core.EFCachedQueryable.System.Collections.Generic.IEnumerable<TType>.GetEnumerator()
EFSecondLevelCache.Core.EFCachedQueryable.get_AsyncEnumerable()
Microsoft.EntityFrameworkCore.Extensions.Internal.QueryableExtensions.AsAsyncEnumerable<TSource>(IQueryable<TSource> source)
Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync<TSource>(IQueryable<TSource> source, CancellationToken cancellationToken)
Cre8ion.Database.EntityFramework.CachedRepository+<GetAllAsync>d__3.MoveNext()

Solution

I have downloaded the source code and added SyncLocks to EFCachedQueryProvider in the Materialize method.

This is working as expected.

How can I commit this change to your project on Github?

Best regards,
Colin

FileNotFound

Summary of the issue

FileNotFoundException: Could not load file or assembly 'EFSecondLevelCache.Core, Version=2.6.3.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified

Environment

.NET Core SDK version: 3.0.100-preview9-014004 
Microsoft.EntityFrameworkCore version: 3.0.0-preview9.19423.6
EFSecondLevelCache.Core version: 2.6.3

Output:

Exception message:
FileNotFoundException: Could not load file or assembly 'EFSecondLevelCache.Core, Version=2.6.3.0, Culture=neutral, PublicKeyToken=null'. The system cannot find the file specified

Cache not invalidating after .Remove

Summary of the issue

When running this test, the object is put in the cache successfully, but after being deleted, the following query is still retrieving the value from the cache.

I see the InvalidateCacheDependencies being called within SaveChanges properly, and I see dependenciesCacheManager.Remove(rootCacheKey) being called as well. (where rootCacheKey is being set to "SC.Core.Entities.Analytic"

 // Create a new object w/ some test data..
 SC.Core.Entities.Analytic a = new Analytic();
 a.ApplicationID = 4171;
 a.EmailsSent = 1;

// Save it
_context.Analytics.Add(a);
_context.SaveChanges();

// Retrieve it.   DB Call is made and object is cached.
 var ab = _context.Analytics.Cacheable(debugger).FirstOrDefault(x=>x.ID==a.ID);

// Delete it from DB
 _context.Analytics.Remove(a);
_context.SaveChanges();

// Test retrieving from cache.  Variable is INCORRECTLY set.  Object is still in cache.
var ac = _context.Analytics.Cacheable(debugger).FirstOrDefault(x => x.ID == a.ID);
            
 // Test retrieving directly.  Result is appropriately null.
var ad = _context.Analytics.FirstOrDefault(x => x.ID == a.ID);

Is there some other set up piece that I am missing?

Use EfSecondLevelCache.Core in apps other than asp.net core apps

Summary of the issue

I tried to use this library with Microsoft.EntityFrameworkCore.SQLite 2.0 in Xamarin based mobile/desktop app. (A .NET Standard 2.0 based project). I stopped with the following exception:

Please add AddEFSecondLevelCache method to IServiceCollection and also add UseEFSecondLevelCache method to IApplicationBuilder.

Environment

The in-use version:
Operating system: UWP 17763 | Android 9 | iOS 12
IDE: VS 2017 15.9
Version: EFSecondLevelCache.Core 1.7.1

I think it's not possible to use this library in other scnarios too. For example, if I write a .NET Core 2 console app which gets executed by azure jobs, I'll receive the same exception, because there is no asp.net core there.
I'm open to send a PR for this if you think it helps.
Thnaks in advance.

query.Cacheable(debugInfo) throws error

Summary of the issue

I am faced with the  problem of query.Cacheable(debugInfo) thrwing error.

Environment

VS 2017 Pro, dot net core 2.0
The in-use version:
Operating system: 
IDE: (e.g. Visual Studio 2015): Visual tudio 2017, Dot net core 2.0

Example code/Steps to reproduce:

**I have created extension method ToCacheList in a class.

next i am invoking the method with iqueryable object.

var q = from c in this.Query() select c;

return await q.ToCacheList();**

paste your core code

public static async Task<IEnumerable> ToCacheList(this IQueryable query)
{
EFCacheDebugInfo debugInfo = new EFCacheDebugInfo();
var result = redisOn ?
await query.Cacheable(debugInfo).ToListAsync() :
await query.ToListAsync();
return result;
}

Output:

Exception message:
System.InvalidOperationException: Unable to resolve service for type 'CacheManager.Core.ICacheManager`1[System.Object]' while attempting to activate 'EFSecondLevelCache.Core.EFCacheServiceProvider'.
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateArgumentCallSites(Type serviceType, Type implementationType, CallSiteChain callSiteChain, ParameterInfo[] parameters, Boolean throwIfCallSiteNotFound)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateConstructorCallSite(Type serviceType, Type implementationType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(ServiceDescriptor descriptor, Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.TryCreateExact(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.CallSiteFactory.CreateCallSite(Type serviceType, CallSiteChain callSiteChain)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.CreateServiceAccessor(Type serviceType)
   at System.Collections.Concurrent.ConcurrentDictionary`2.GetOrAdd(TKey key, Func`2 valueFactory)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType, ServiceProviderEngineScope serviceProviderEngineScope)
   at Microsoft.Extensions.DependencyInjection.ServiceLookup.ServiceProviderEngine.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProvider.GetService(Type serviceType)
   at Microsoft.Extensions.DependencyInjection.ServiceProviderServiceExtensions.GetService[T](IServiceProvider provider)
   at EFSecondLevelCache.Core.EFCachedQueryExtensions.configureProviders()

Caching entire tables

I'd like to do something like declare table Address be cached.

So the cache would aggressively cache the entire table and run queries like: context.Buildings.Include(x => x.Address).ToListAsync() and building data is gathered from the DB but the address include uses the cache data.

Does that make sense? Does this currently work that? Is it possible?

Thanks!

Cache Doesn't Appear to Flush After SaveChanges()

Summary of the issue

We are running into what appears to be a cache flush issue when a new record is added. In our case we query a table for a record something simple:

var debugger = new EFCacheDebugInfo();
(IQueryable).Where(x=>x.Column1 == "ABC" && x.Column2="XYZ").Cacheable(debugger).FirstOrDefault()

The result is null which is expected, since the result is null we go to another table and load the record. SaveChanges is called and InvalidateCacheDependencies(changedEntityNames); fires as expected, changedEntityNames contains the changed entity. We even tried using ClearAllCachedEntries() but the result was the same.

Later when we query for that record again the we are getting null back still and debugger.IsCacheHit == true. We are expecting IsCacheHit to be false and the query to go back to the database and get the newly added entity.

If we restart the app after the record is added everything works correctly, which is why we are leaning towards the cache flushing.

The app in question is a .Net Core Console app (v.2.2) using a .Net Standard 2.0 library dll where the caching takes place. The dll is a plugin, and loaded via MEF.

We have configured the second level cache in ConfigureServices like so:

// Add an in-memory cache service provider 
services.AddEFSecondLevelCache();
services.AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>));
services.AddSingleton(typeof(ICacheManagerConfiguration),
   new CacheManager.Core.ConfigurationBuilder("Mx.DataLoad")
      .WithJsonSerializer()
      .WithMicrosoftLogging(loggerFactory)
      .WithMicrosoftMemoryCacheHandle()
      .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(Configuration.GetValue("Caching:ExpirationInMinutes", 60)))
   .Build());

// Additional code to spin up our ServiceProvider
var serviceProvider = services.BuildServiceProvider();
EFServiceProvider.ApplicationServices = serviceProvider;

Environment

The in-use version: 1.7.1
Operating system: Windows 10
IDE: VS 2017

Packages

<PackageReference Include="EFSecondLevelCache.Core" Version="1.7.1" />
<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" />
<PackageReference Include="CacheManager.Microsoft.Extensions.Logging" Version="1.2.0" />

Questions

Is there a way to visualize the cache so we can see what is going on at each step?

Any suggestions on where to look to isolate the issue?

Anything like this available for EF6 / .NET Framework?

The documentation mentions the following:

This cache is updated when an entity is changed (insert, update, or delete) via a DbContext that uses this library.

Is anything like this implemented in any of the available EF6 libraries that you know of? I'm not using .NET Core unfortunately.

Error While InvalidateCacheDependencies

Summary of the issue

I am getting following error after save change call InvalidateCacheDependencies
my getting data and caching works perfect also, saving works but

this.GetService < IEFCacheServiceProvider > () does not resolved dependncy

Environment

.net core 2.1
ef core 2.1 rc1 final

The in-use version:
Operating system: 
IDE: (e.g. Visual Studio 2017)

Example code/Steps to reproduce:


Unable to resolve service for type 'EFSecondLevel.Core.Contracts.IEFCacheServiceProvider'. This is often because no database provider has been configured for this DbContext. A provider can be configured by overriding the DbContext.OnConfiguring method or by using AddDbContext on the application service provider. If AddDbContext is used, then also ensure that your DbContext type accepts a DbContextOptions<TContext> object in its constructor and passes it to the base constructor for DbContext.

Output:

Exception message:
Full Stack trace:

Issue about different linq query

Summary of the issue

after change Where right part of expression, still returns previous value.

Environment

The in-use version:
Operating system: 
IDE: (e.g. Visual Studio 2019)
EF-Core: 2.2.4
AspCore: 2.2.0

Example code/Steps to reproduce:

var query = _items.AsNoTracking()
                .Include(x => x.Property)
            .ThenInclude(x => x.Category);
            query = query.Include(x => x.Property)
            .ThenInclude(x => x.District);
            query = query.Include(x => x.Property)
            .ThenInclude(x => x.Pictures);
            query = query.Include(x => x.Property)
            .ThenInclude(x => x.PropertyFacilities)
            .ThenInclude(x => x.Facility);
            query = query.Include(x => x.Property)
            .ThenInclude(x => x.PropertyFeatures)
            .ThenInclude(x => x.Feature);
            query = query.Include(x => x.Property)
            .ThenInclude(x => x.PropertyOwnerships)
            .ThenInclude(x => x.Ownerships)
            .ThenInclude(x => x.Customer);
            query = query.Include(x => x.Category);
            query = query.Include(x => x.DealRequests);
            query = query.Include(x => x.ItemFeatures)
            .ThenInclude(x => x.Feature);
            query = query.Include(x => x.Applicants)
                .ThenInclude(x => x.Customer);

query = query.Where(x=> x.Id == "123");
var models = await query.Cacheable().ToListAsync().ConfigureAwait(false);

Output:

above code returns things that i wanted.
but when i use query.Where(x=> x.Id == "234") instead of query.Where(x=> x.Id == "123") still returns above code's return value.

my suggestion is, in EFDebug's Key, add .Where 's parameteres of left and right of expression

Cache subqueries

It may be cool, unique feauture to populate cache while enumerating related subqueries like this:

var query = context.DataSetA.Select(a => new
{
    prop1 = a.prop1,
    prop2 = context.DataSetB.Where(b => b.Id == a.Id).Cacheable().Select(b => new
    {
         b.prop2
    })
})

Cache cleans after app reload

Summary of the issue

I'm using Redis cache as handle. Everything work until i reload app, then it stop using existing redis cache, and make every request new cache/ Is it expected behavior? How can I avoid that. I want my app to work with the same cache after restart app.

Environment

The in-use version:
Operating system: 
IDE: (e.g. Visual Studio 2015)

Example code/Steps to reproduce:

      var jss = new JsonSerializerSettings
            {
                NullValueHandling = NullValueHandling.Ignore,
                ReferenceLoopHandling = ReferenceLoopHandling.Ignore
            };


   const string redisConfigurationKey = "redis";
            services.AddSingleton(typeof(ICacheManagerConfiguration),
                new CacheManager.Core.ConfigurationBuilder()
                    .WithJsonSerializer(serializationSettings: jss, deserializationSettings: jss)
                    .WithRedisConfiguration(redisConfigurationKey, redisConnectionString)
                    .WithUpdateMode(CacheUpdateMode.Up)
                    .WithMaxRetries(100)
                    .WithRetryTimeout(50)
                    .WithRedisCacheHandle(redisConfigurationKey)
                    .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))
                    .Build());

Output:

Exception message:
Full Stack trace:

Always returns first cached instance

Summary of the issue

It seems that always the first cached result is returned.

Environment

.NET Core SDK version: 3.0.100-rc1-014190
Microsoft.EntityFrameworkCore version: 3.0.0-rc1.19456.14
EFSecondLevelCache.Core version: 2.6.4, [ 2.6.3 ]

Example code/Steps to reproduce:

pretty basic setup indeed, with redis cache.

await dbContext.Brokers_Requests
                .Where(o =>  o.Id == request.Id)
                .Cacheable()
                .FirstOrDefaultAsync();

EFSecondLevelCache.Core for .net 4.5.1

currently i am using efcore v1.1.0 with .net v4.5.1 is there any chance you will have a version of EFSecondLevelCache.Core that will work with 4.5+ I was able to use EFSecondLevelCache but that also pulls in ef 6.1.3 as a dependency that i do not need.

InvalidateCacheDependencies for .Include queries

Hi -

I am trying to understand a little about invalidating cache entries in my scenario. I have a Customer entity that ".Includes" several related entities such as Address and Phone during a GetCustomer operation.

I have been noticing that when I add / update a Phone record, for instance, a subsequent GetCustomer call does not reflect the new / changed records.

Is there a best practices way that you can recommend to invalidate the cache of the related object?

Thanks!

Shawn

How to handle offline Redis cache?

When using redis cache you get exception if connection could not be established. How can I just skip the cache if Redis is not available? Does not make sense that everything stops working if cache is not available.

Cached value sometimes invalid

Sometimes when using cache with parameters returns incorrect data.

List<int> currencyIds = ...; // only a few ids
Listt<Currency> currencies = await database.Currencies.Where(c => currencyIds.Contains(c.Id)).Cacheable().ToListAsync();

Sometimes I get empty list and sometimes partial results (missing items in list). During the lifetime of the app this data is never changed and definitely is available in the database. If error occurs, it fixes itself when updating the data from database next time even though the data has not changed!

If I remove Cacheable() the code works. If I remove the Where-clause (just cache all items) the code works.

This code is run with different currencyIds so I suspect some kind of cache collision; for conditions X you get data for conditions Y.

Dependencies:

<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6"/>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.6"/>
<PackageReference Include="EFSecondLevelCache.Core" Version="2.6.2"/>
<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"/>

Edit: tested also with 2.6.3 and problem persists

It seems to be an issue with having List<T>.Contains(...) inside Where-clause. This is only case where I have had problems. Seems like Contains does not leverage parameterization. Maybe its related to this?

using EF Core 3.0: bugs in async cached query

Summary of the issue

System.ArgumentException: Expression of type 'TestEntity' cannot be used for return type 'System.Threading.Tasks.Task`1[TestEntity]'

Environment

The in-use version: 2.5.1
Operating system: Windows 10
IDE: Visual Studio 2019

Example code/Steps to reproduce:

await xxx.Cacheable().FirstOrDefaultAsync();

Output:

System.ArgumentException: Expression of type 'TestEntity' cannot be used for return type 'System.Threading.Tasks.Task`1[TestEntity]'
   at System.Linq.Expressions.Expression.ValidateLambdaArgs(Type delegateType, Expression& body, ReadOnlyCollection`1 parameters, String paramName)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, String name, Boolean tailCall, IEnumerable`1 parameters)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, Boolean tailCall, IEnumerable`1 parameters)
   at System.Linq.Expressions.Expression.Lambda[TDelegate](Expression body, ParameterExpression[] parameters)
   at Microsoft.EntityFrameworkCore.Query.Pipeline.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at EFSecondLevelCache.Core.EFCachedQueryProvider`1.<>c__DisplayClass10_0`1.<Execute>b__0()
   at EFSecondLevelCache.Core.EFCachedQueryProvider`1.Materialize(Expression expression, Func`1 materializer)
   at EFSecondLevelCache.Core.EFCachedQueryProvider`1.Execute[TResult](Expression expression)
   at EFSecondLevelCache.Core.EFCachedQueryProvider`1.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ExecuteAsync[TSource,TResult](MethodInfo operatorMethodInfo, IQueryable`1 source, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.FirstOrDefaultAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)

Cache not being invalidated randomly

Summary of the issue

Hi, I'm using the tool and it's working very nice,
However, I'm finding from time to time that updating an entity does not invalidate the cache; or that when a new entity is added, is correctly saved on database but cache is not invalidated and the new entity is not visible.

Environment

I use Redis for cache
The application is netcore 2

Operating system: Win 7 / Windows Server
IDE: Visual Studio 2017

Example code/Steps to reproduce:

I can't reproduce since it's happening randomly. Might not happen in days and suddendly it occurs

Does not have an implementation

Summary of the issue

System.TypeLoadException: Method 'ExecuteAsync' in type 'EFSecondLevelCache.Core.EFCachedQueryProvider`1' from assembly 'EFSecondLevelCache.Core, Version=1.7.1.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

It seems that I would need an implementation, but your examples don't seem to have any, so I was curious.

Environment

The in-use version: 1.7.1
Operating system: Manjaro Linux
IDE: Jetbrains Rider

Example code/Steps to reproduce:

.AddEFSecondLevelCache()
                
                .AddSingleton(typeof(ICacheManager<>), typeof(BaseCacheManager<>))
                .AddSingleton(typeof(ICacheManagerConfiguration),
                    new ConfigurationBuilder()
                        .WithJsonSerializer()
                        .WithMicrosoftMemoryCacheHandle()
                        .WithExpiration(ExpirationMode.Absolute, TimeSpan.FromMinutes(10))
                        .Build())
...
Services = ConfigureServices();
            EFServiceProvider.ApplicationServices = Services;
// BakaContext.cs
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using EFSecondLevelCache.Core;
using EFSecondLevelCache.Core.Contracts;

namespace BakaZero.Data
{
    public class BakaContext : DbContext
    {
        public BakaContext(DbContextOptions<BakaContext> options) : base(options)
        {        }

        public DbSet<BakaGuild> Guilds { get; set; }
        public DbSet<BakaUser> Users { get; set; }
        
        public override int SaveChanges()
        {
            ChangeTracker.DetectChanges();
            var changedEntityNames = this.GetChangedEntityNames();

            ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.
            var result = base.SaveChanges();
            ChangeTracker.AutoDetectChangesEnabled = true;

            this.GetService<IEFCacheServiceProvider>().InvalidateCacheDependencies(changedEntityNames);

            return result;
        }

        public override Task<int> SaveChangesAsync(CancellationToken cancellationToken = new CancellationToken())
        {
            ChangeTracker.DetectChanges();
            var changedEntityNames = this.GetChangedEntityNames();

            ChangeTracker.AutoDetectChangesEnabled = false; // for performance reasons, to avoid calling DetectChanges() again.
            var result = base.SaveChangesAsync(cancellationToken);
            ChangeTracker.AutoDetectChangesEnabled = true;

            this.GetService<IEFCacheServiceProvider>().InvalidateCacheDependencies(changedEntityNames);

            return result;
        }
    }
}

Output:

Exception message:

Command     Discord.Commands.CommandException: Error occurred executing "listprefix" for Kot#6386 in Kot and Friends/testing-2. ---> System.TypeLoadException: Method 'ExecuteAsync' in type 'EFSecondLevelCache.Core.EFCachedQueryProvider`1' from assembly 'EFSecondLevelCache.Core, Version=1.7.1.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.

Full Stack trace:

  Command     Discord.Commands.CommandException: Error occurred executing "listprefix" for Kot#6386 in Kot and Friends/testing-2. ---> System.TypeLoadException: Method 'ExecuteAsync' in type 'EFSecondLevelCache.Core.EFCachedQueryProvider`1' from assembly 'EFSecondLevelCache.Core, Version=1.7.1.0, Culture=neutral, PublicKeyToken=null' does not have an implementation.
     at EFSecondLevelCache.Core.EFCachedDbSet`1..ctor(DbSet`1 query, String saltKey, EFCacheDebugInfo debugInfo, IEFCacheKeyProvider cacheKeyProvider, IEFCacheServiceProvider cacheServiceProvider)
     at EFSecondLevelCache.Core.EFCachedQueryExtensions.Cacheable[TType](DbSet`1 query, String saltKey, EFCacheDebugInfo debugInfo, IEFCacheKeyProvider cacheKeyProvider, IEFCacheServiceProvider cacheServiceProvider)
     at BakaZero.Modules.SettingsModule.PrefixesDisplay()
     at Discord.Commands.ModuleClassBuilder.<>c__DisplayClass6_0.<<BuildCommand>g__ExecuteCallback|0>d.MoveNext()
  --- End of stack trace from previous location where exception was thrown ---
     at Discord.Commands.CommandInfo.ExecuteInternalAsync(ICommandContext context, Object[] args, IServiceProvider services)
     --- End of inner exception stack trace ---

I'm a bit new to C#, so please be gentle :)

.Cacheable() method is locking thread in async environment?

Summary of the issue

I have multiple EF select queries that run in async manner, bassically with Task.WhenAll using single DbContext object.

Environment

.NET Core SDK version: 2.2
Microsoft.EntityFrameworkCore version: 2.2.6
EFSecondLevelCache.Core version: 2.8

Example code/Steps to reproduce:

var q1 = productTypeUseCase.GetAll();
var q2 = applicationDataUseCase.GetAllApplicationTypes();
var q3 = caseTypeUseCase.GetAll();
await Task.WhenAll(q1, q2, q3);
return true;

## Output:
Application started. Press Ctrl+C to shut down.
[12:26:01 INF] Request starting HTTP/1.1 POST http://localhost:5000/graphql application/json 93
[12:26:01 INF] CORS policy execution successful.
ctx
product type started
app started.
refdata started
app finished. 179
refdata finished 153
product type finished 631
[12:26:03 INF] GraphQL request
[12:26:03 INF] {"query":"{\n  referenceData {\n    testAsync\n  }\n}\n","variables":{},"operationName":null}
[12:26:03 INF] GraphQL response
[12:26:03 INF] Request finished in 1698.1173ms 400 application/json

Now, when i add .Cacheable() to one of queries

##Output:
ctx
product type started
app started.
refdata started
app finished. 244
product type finished 705
[12:32:01 ERR] System.InvalidOperationException: A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe. This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.
   at Microsoft.EntityFrameworkCore.Internal.ConcurrencyDetector.EnterCriticalSection()
   at Microsoft.EntityFrameworkCore.Query.Internal.LinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.MoveNext()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)                                         at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at System.Linq.Enumerable.ToArray[TSource](IEnumerable`1 source)
   at EFSecondLevelCache.Core.EFMaterializer.Materialize[T](Expression expression, Func`1 materializer)
   at EFSecondLevelCache.Core.EFCachedQueryable`1.System.Collections.Generic.IEnumerable<TType>.GetEnumerator()
   at EFSecondLevelCache.Core.EFCachedQueryable`1.get_AsyncEnumerable()
   at Microsoft.EntityFrameworkCore.Extensions.Internal.QueryableExtensions.AsAsyncEnumerable[TSource](IQueryable`1 source)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at .UW.Features.Common.Infrastructure.Repositories.RefDataRepository.GetAll(RefDataMapping refDataMapping) in C:\work\projects\luca\uw-bff\.UW.Features\Common\Infrastructure\Repositories\RefDataRepository.cs:line 110
   at .UW.API.GraphQL.Schema.ReferenceDataType.<>c__DisplayClass0_0.<<-ctor>b__46>d.MoveNext() in C:\work\projects\luca\uw-bff\.UW.API\GraphQL\Schema\ReferenceDataType.cs:line 290
--- End of stack trace from previous location where exception was thrown ---
   at Instrumentation.MiddlewareResolver.Resolve(ResolveFieldContext context)
   at LUCA.UW.API.GraphQL.GraphQLErrorsFieldMiddleware.Resolve(ResolveFieldContext context, FieldMiddlewareDelegate next) in C:\work\projects\luca\uw-bff\LUCA.UW.API\GraphQL\GraphqlErrorsFieldMiddleware.cs:line 24
[12:32:01 INF] GraphQL request
[12:32:01 INF] {"query":"{\n  referenceData {\n    testAsync\n  }\n}\n","variables":{},"operationName":null}
[12:32:01 INF] GraphQL response

Invalid result from Cacheable when using FirstOrDefaultAsync

Summary of the issue

When an argument passes to the FirstOrDefaultAsync method ( and this issue may exist for other methods like ToListAsync, SingleAsync, ... ), the Cacheable caches the results correctly, but when another argument with different value passes to it, it returns the previous results again.

Seems like the Cacheable method only differs and watch for different argument values just in the Where clause, not the FirstOrDefaultAsync, SingleOrDefaultAsync and other EntityFramework.Core methods, because different argument values work correctly by using Where method.

I know that the Cacheable is written on top of IQueryable and it caches the result based on the Query, but with that, we're unable to use the Microsoft.EntityFramework.Core queryable extensions anymore.

Environment

The in-use version: 
EFSecondLevelCache.Core v1.8.1
CacheManager.Microsoft.Extensions.Caching.Memory v1.2.0

Example code/Steps to reproduce:

// Cache userA correctly here
var userA = await context.Users
	.Cacheable()
	.FirstOrDefaultAsync(user => user.Username == "ABC", cancellationToken);
	
// In this query Cacheable returns previous result (userA) again, But the username is different now!
var userB = await context.Users
	.Cacheable()
	.FirstOrDefaultAsync(user => user.Username == "EFG", cancellationToken);


// By using Where clause, arguments cache properly.

// Returns UserA based on "ABC" username.
var userA = await context.Users
	.Where(user => user.Username == "ABC")
	.Cacheable()
	.FirstOrDefaultAsync(cancellationToken);

// Returns UserB based on "EFG" username.
var userB = await context.Users
	.Where(user => user.Username == "EFG")
	.Cacheable()
	.FirstOrDefaultAsync(cancellationToken);

Where() statements never resolve from cache

Summary of the issue

I noticed that EFCore Where() statements never get pulled from the cache. Is this intentional? When I change the query to be a First() statement, it will pull from the cache just fine. I simply copy and pasted the install instructions to register the services for EFSecondLevelCache.Core. Do let me know if you need any other information.

Environment

EFSecondLevelCache.Core v1.6.4
Asp.Net Core 2.2
Windows 10
Visual Studio 2017

Example code/Steps to reproduce:

using System.Linq;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using EFSecondLevelCache.Core;
using My.Models;

namespace My.Controllers
{
    [Route("api/[controller]")]
    [ApiController]
    public class BlogCommentsController : ControllerBase
    {
        private readonly MyContext _context;

        public BlogCommentsController(MyContext context)
        {
            _context = context;
        }
        
        [HttpGet("{id}")]
        public ActionResult<List<BlogComment>> GetBlogComment(int id)
        {
	    // This is expected to resolve from the cache, but it always queries the database
            var blogComment = _context.BlogComments.Cacheable().Where(x => x.BlogId == id).ToList();

            // This does pull from the cache
            var singleComment = _context.BlogComments.Cacheable().First(x => x.BlogId == id);

            return blogComment;
        }
    }
}

Output:

When tracing transactions for my MS-SQL database, I see the following query produced every time I hit the GetBlogComment(int id) action above.

[SELECT [x].[Id], [x].[AuthorUserId], [x].[BlogId], [x].[Content]
FROM [BlogComments] AS [x]
WHERE [x].[BlogId] = @__context_Source_Id_0](url)

Again, when changing it to First() I do not see my database getting hit, implying that it pulled from the cache.

ToSql to slow, why not use Experession string=

Summary of the issue

I have a more general question.

The ToSql function you are using is very very very slow. It brings an extreme high CPU workload to a heavy cache using service.

Why you don't use the _query.Expression.ToString() function as base to build the hash value for an IQueryable? I think it gives us to exact the same result but at much higher speed?
Is there some point I'm missing?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.