VahidN / EFSecondLevelCache.Core

Entity Framework Core Second Level Caching Library
Apache License 2.0
326 stars 51 forks source link

EFCachedDbSet & OrderBy #58

Closed seroche closed 4 years ago

seroche commented 4 years ago

Summary of the issue

When I try to order a result set obtained through an EFCachedDbSet I get the exception

Unable to cast object of type 'EFSecondLevelCache.Core.EFCachedQueryable`1[XXX]' to type 'System.Linq.IOrderedQueryable`1

Environment

.NET Core SDK version: 3.0
Microsoft.EntityFrameworkCore version: latest stable
EFSecondLevelCache.Core version: latest stable

Example code/Steps to reproduce:

**DbContext:**

  public class CoreDbContext : DbContext
    {
        public EFCachedDbSet<Client> Clients => Set<Client>().Cacheable();

        public CoreDbContext(DbContextOptions<CoreDbContext> options)
            : base(options) { }

        protected override void OnModelCreating(ModelBuilder builder)
        {
            builder.Entity<Client>(m =>
            {
                #region Mappings
                m.ToTable("Client", "client");

                m.Property(x => x.Id)
                    .HasColumnName("ClientId");

                m.HasKey(x => x.Id);
                #endregion
            });
        }
    }

**Code:**

var test = _db.Clients.OrderByDescending(x => x.CentreId).ToList();

Output:

Exception message: Unable to cast object of type 'EFSecondLevelCache.Core.EFCachedQueryable`1[TEC.CoreSocialApi.Application.Domain.Clients.Client]' to type 'System.Linq.IOrderedQueryable`1[TEC.CoreSocialApi.Application.Domain.Clients.Client]'.

Full Stack trace:
System.InvalidCastException: Unable to cast object of type 'EFSecondLevelCache.Core.EFCachedQueryable`1[TEC.CoreSocialApi.Application.Domain.Clients.Client]' to type 'System.Linq.IOrderedQueryable`1[TEC.CoreSocialApi.Application.Domain.Clients.Client]'.
   at System.Linq.Queryable.OrderByDescending[TSource,TKey](IQueryable`1 source, Expression`1 keySelector)
   at TEC.CoreSocialApi.Application.Features.Search.SearchClients.Handler.Handle(Query request, CancellationToken token) in C:\Repository\core-social-api\src\TEC.CoreSocialApi.Application\Features\Search\SearchClients.cs:line 97
   at TEC.CoreCommon.AspNetCore.ValidationBehavior`2.Handle(TRequest request, CancellationToken token, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestPostProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at MediatR.Pipeline.RequestPreProcessorBehavior`2.Handle(TRequest request, CancellationToken cancellationToken, RequestHandlerDelegate`1 next)
   at TEC.CoreSocialApi.Application.Features.Search.SearchController.SearchClients(Query query, CancellationToken token) in C:\Repository\core-social-api\src\TEC.CoreSocialApi.Application\Features\Search\SearchController.cs:line 45
   at lambda_method(Closure , Object )
   at Microsoft.Extensions.Internal.ObjectMethodExecutorAwaitable.Awaiter.GetResult()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.AwaitableObjectResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeActionMethodAsync>g__Awaited|12_0(ControllerActionInvoker invoker, ValueTask`1 actionResultValueTask)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.<InvokeNextActionFilterAsync>g__Awaited|10_0(ControllerActionInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Rethrow(ActionExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeInnerFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeNextResourceFilter>g__Awaited|24_0(ResourceInvoker invoker, Task lastTask, State next, Scope scope, Object state, Boolean isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Rethrow(ResourceExecutedContextSealed context)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.InvokeFilterPipelineAsync()
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.AspNetCore.Mvc.Infrastructure.ResourceInvoker.<InvokeAsync>g__Logged|17_1(ResourceInvoker invoker)
   at Microsoft.AspNetCore.Routing.EndpointMiddleware.<Invoke>g__AwaitRequestTask|6_0(Endpoint endpoint, Task requestTask, ILogger logger)
   at AspNetCoreRateLimit.RateLimitMiddleware`1.Invoke(HttpContext context)
   at Serilog.AspNetCore.RequestLoggingMiddleware.Invoke(HttpContext httpContext)
   at TEC.CoreCommon.AspNetCore.ErrorHandlingMiddleware.Invoke(HttpContext context)
VahidN commented 4 years ago

You should put the Cacheable() method at the end of the chain:

var users = await _context.Set<Post>().OrderByDescending(x => x.Id).Cacheable().ToListAsync();

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

seroche commented 4 years ago

I agree with you. However it's very convenient to add the Cacheable() in the DbContext. It allows me to control the caching mechanism from one single place.

Example:

    public class CoreDbContext : DbContext
    {
        public EFCachedDbSet<Event> Events => Set<Event>().Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromHours(1));
        public EFCachedDbSet<Reservation> Reservations => Set<Reservation>().Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromHours(1));
        public EFCachedDbSet<Format> Formats => Set<Format>().Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromDays(7));
        public EFCachedDbSet<Category> Categories => Set<Category>().Cacheable(CacheExpirationMode.Absolute, TimeSpan.FromDays(7));

        public EFCachedDbSet<Client> Clients => Set<Client>().Cacheable();
        public EFCachedDbSet<Centre> Centres => Set<Centre>().Cacheable();
        public EFCachedDbSet<Industry> Industries => Set<Industry>().Cacheable();

        public EFCachedDbSet<User> Users => Set<User>().Cacheable();
        public EFCachedDbSet<Profile> Profiles => Set<Profile>().Cacheable();
}

Shall I understand that EFCachedDbSet is not the recommended approach to cache data?

VahidN commented 4 years ago

Fixed it via #https://github.com/VahidN/EFSecondLevelCache.Core/commit/0bb70493e1740f95e2c73621426c7ca01a5a616f

lock[bot] commented 4 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related problems.