Efcore: Count() throws InvalidCastException in specific cases

Created on 24 Jul 2019  ยท  11Comments  ยท  Source: dotnet/efcore

When using .Count() following specific operations, the following exception is thrown :

System.InvalidCastException: Unable to cast object of type 'Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions.SqlFunctionExpression' to type 'System.Linq.Expressions.ConstantExpression'.
   at Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.RelationalShapedQueryCompilingExpressionVisitor.RelationalProjectionBindingRemovingExpressionVisitor.GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression)
   at Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.RelationalShapedQueryCompilingExpressionVisitor.RelationalProjectionBindingRemovingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.RelationalShapedQueryCompilingExpressionVisitor.VisitShapedQueryExpression(ShapedQueryExpression shapedQueryExpression)
   at Microsoft.EntityFrameworkCore.Query.Pipeline.ShapedQueryCompilingExpressionVisitor.VisitExtension(Expression extensionExpression)
   at System.Linq.Expressions.Expression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   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 System.Linq.Queryable.Count[TSource](IQueryable`1 source)
   at Software.Controllers.ApplicationStatusController.Crash() in C:\software\src\Software\Controllers\ApplicationStatusController.cs:line 142
   at lambda_method(Closure , Object , Object[] )
   at Microsoft.Extensions.Internal.ObjectMethodExecutor.Execute(Object target, Object[] parameters)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ActionMethodExecutor.SyncActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeActionMethodAsync()
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted)
   at Microsoft.AspNetCore.Mvc.Infrastructure.ControllerActionInvoker.InvokeNextActionFilterAsync()
--- End of stack trace from previous location where exception was thrown ---
   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 Microsoft.AspNetCore.Authentication.AuthenticationMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.ResponseCompression.ResponseCompressionMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.StatusCodePagesMiddleware.Invoke(HttpContext context)
   at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddleware.Invoke(HttpContext context)

Steps to reproduce

There are multiple ways to reproduce this error :

```c#
_context.Client
.Select(c => new
{
Id = c.Id.ToString("000000")
})
.Count();

```c#
_context.Client
    .Select(c => c.Id.ToString("000000"))
    .Count();

```c#
_context.Client
.Select(c => new
{
Id = c.Id.CodeFormat()
})
.Count();

`CodeFormat` being simply this :

```c#
public static class NamingTools
{

    public static string CodeFormat(this int str)
    {
        return str.ToString();
    }
}

Weirdly enough, doing .ToList() doesn't throw.

c# _context.Client .Select(c => new { Id = c.Id.ToString("000000") }) .ToList(); // This works

Further technical details

EF Core version: 3.0.0-preview7.19365.7
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows 10 x64
IDE: Visual Studio 2019 16.2.0 Preview 4.0

area-test closed-fixed customer-reported

Most helpful comment

This has been fixed in my current work of Nav rewrite.

All 11 comments

@fschlaef All these are cases where the projection cannot (currently) be translated to SQL. In 3.0, we allow a final projection to be evaluated on the client, but then when the aggregate (Count) operator is applied the projection is no longer the final projection.

The plan for 3.0 is to throw an exception with a clearer message for this. (@smitpatel #16133 or is #15937 this case?)

For the future, putting this on the backlog to investigate silently dropping the projection when discarded by the final query anyway. However, we need to be careful about side-effects.

15937

This has been fixed in my current work of Nav rewrite.

@smitpatel Merge before Monday?

:trollface: :trollface: :trollface: :trollface: :trollface: :trollface:

The mountain isn't going anywhere.... ๐Ÿ˜‰

:trollface: :trollface: :trollface: :trollface: :trollface: :trollface:

๐ŸŒจ โ›ˆ ๐ŸŒฉ ๐ŸŒง keep on moving...

This has been fixed in my current work of Nav rewrite.

@smitpatel @ajcvickers

What does this statement refer to? Is it fixed or not? According to the issue milestone it is not?

I will put another +1 on this issue because this will be a big problem for everyone implementing a basic paginated list.

Example:

var entries = 
(from c in context.Customers
select new CustomerListItem
{
  c.Id,
  c.Name,
  ClientEvalProperty = // Some client eval expression
});

var viewModel = new ViewModel
{
   Entries = entries.Skip(..).Take(..).ToList(),
   Total = entries.Count() // Boom => Crash!
};

@davidroth - We believe that this issue has been fixed in latest daily builds.

@smitpatel Confirmed working with 3.0.0-rc1 ๐Ÿ‘

@smitpatel Same here ๐Ÿ‘ Works in 3.0.0-rc1.19424.9

Need to add a test for this.

This still fails: Unable to cast object of type 'Microsoft.EntityFrameworkCore.Query.SqlExpressions.SqlFunctionExpression' to type 'System.Linq.Expressions.ConstantExpression'.

Specifically with .Distinct() which obviously shouldn't be stopping any query from being executed properly. Or to put another way, if a query can execute fine and materialize as the last step without .Distinct, it certainly can with it since it's just SELECT DISTINCT ... instead of SELECT ...

Was this page helpful?
0 / 5 - 0 ratings

Related issues

ghost picture ghost  ยท  3Comments

mohsin91 picture mohsin91  ยท  3Comments

HappyNomad picture HappyNomad  ยท  3Comments

miguelhrocha picture miguelhrocha  ยท  3Comments

julienshepherd picture julienshepherd  ยท  3Comments