Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InvalidCastException when casting from one value type to another in a simple select statement #8652

Closed
samb0t opened this issue May 31, 2017 · 7 comments
Assignees
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Milestone

Comments

@samb0t
Copy link

samb0t commented May 31, 2017

InvalidCastApp.zip
When casting a property from a nullable double to a nullable int in a Select function, an InvalidCastException occurs. Likewise, the same exception occurs if the types are not nullable.

Attached is a working example of the issue. Just create the database and table first and then update the connection string in Program.cs.

The select looks like this:

.Select(o => new DTO() {  
  Limit = o.Limit__c.HasValue ? (int?)o.Limit__c : null,   
  Slots = o.Slots__c.HasValue ? (int?)o.Slots__c : null  
  })  
  .Where(o => cIds.Contains(o.CId))  
  .ToListAsync());  

Limit__c and Slots__c are nullable doubles
The SQL profiler does not show any casting before the exception is thrown.
The same query was working in EF 6.x (Full Framework).

Exception message: Specified cast is not valid.
Stack trace:
<error errorId="7f262bc2-3b41-439b-be10-ed2c6e480a0f" application="/LM/W3SVC/2/ROOT" host="" type="System.InvalidCastException" message="Specified cast is not valid." source="Anonymously Hosted DynamicMethods Assembly" detail="System.InvalidCastException: Specified cast is not valid.;  
 at lambda_method(Closure , QueryContext , ValueBuffer );  
 at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.ProjectionShaper.TypedProjectionShaper`3.Shape(QueryContext queryContext, ValueBuffer valueBuffer);  
 at Microsoft.EntityFrameworkCore.Query.AsyncQueryMethodProvider.&lt;&gt;c__DisplayClass3_0`1.&lt;_ShapedQuery&gt;b__0(ValueBuffer vb);  
 at System.Linq.AsyncEnumerable.SelectEnumerableAsyncIterator`2.&lt;MoveNextCore&gt;d__7.MoveNext();--- 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.Linq.AsyncEnumerable.AsyncIterator`1.&lt;MoveNext&gt;d__10.MoveNext();--- 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 Microsoft.EntityFrameworkCore.Query.Internal.AsyncLinqOperatorProvider.ExceptionInterceptor`1.EnumeratorExceptionInterceptor.&lt;MoveNext&gt;d__5.MoveNext();--- 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.Linq.AsyncEnumerable.&lt;Aggregate_&gt;d__6`3.MoveNext();--- 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.TaskAwaiter`1.GetResult();  
 at --snip--.&lt;WithContext&gt;d__5`1.MoveNext();--- 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.TaskAwaiter`1.GetResult();  
 at --snip--.&lt;GetByContractIds&gt;d__13.MoveNext();--- 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.TaskAwaiter`1.GetResult();  
 at --snip--d__54.MoveNext() in 
 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task);  
 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task);  
 at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult();  
 at --snip--Load&gt;d__14.MoveNext() in 
 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task);  
 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task);  
 at System.Runtime.CompilerServices.TaskAwaiter.GetResult();  
 at --snip--Contract&gt;d__70.MoveNext() in 
 at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task);  
 at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task);  
 at System.Web.Mvc.Async.TaskAsyncActionDescriptor.EndExecute(IAsyncResult asyncResult);  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.&lt;&gt;c__DisplayClass37.&lt;BeginInvokeAsynchronousActionMethod&gt;b__36(IAsyncResult asyncResult);  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethod(IAsyncResult asyncResult);  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.&lt;InvokeActionMethodFilterAsynchronouslyRecursive&gt;b__3d();  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.AsyncInvocationWithFilters.&lt;&gt;c__DisplayClass46.&lt;InvokeActionMethodFilterAsynchronouslyRecursive&gt;b__3f();  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeActionMethodWithFilters(IAsyncResult asyncResult);  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.&lt;&gt;c__DisplayClass21.&lt;&gt;c__DisplayClass2b.&lt;BeginInvokeAction&gt;b__1c();  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.&lt;&gt;c__DisplayClass21.&lt;BeginInvokeAction&gt;b__1e(IAsyncResult asyncResult);  
 at System.Web.Mvc.Async.AsyncControllerActionInvoker.EndInvokeAction(IAsyncResult asyncResult);  
 at System.Web.Mvc.Controller.&lt;BeginExecuteCore&gt;b__1d(IAsyncResult asyncResult, ExecuteCoreState innerState);  
 at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult);  
 at System.Web.Mvc.Controller.EndExecuteCore(IAsyncResult asyncResult);  
 at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult);  
 at System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult);  
 at System.Web.Mvc.MvcHandler.&lt;BeginProcessRequest&gt;b__5(IAsyncResult asyncResult, ProcessRequestState innerState);  
 at System.Web.Mvc.Async.AsyncResultWrapper.WrappedAsyncVoid`1.CallEndDelegate(IAsyncResult asyncResult);  
 at System.Web.Mvc.MvcHandler.EndProcessRequest(IAsyncResult asyncResult);  
 at System.Web.HttpApplication.CallHandlerExecutionStep.OnAsyncHandlerCompletion(IAsyncResult ar)" user="" time="2017-05-31T19:14:36.8015246Z">

Steps to reproduce

  1. Create a table with a column defined as decimal(18,0),null and an int primary key.
  2. Use the project in the attached zip, first updating the connection string in Program.cs.
  3. Running the application will give the invalid cast exception.
.Select(o => new DTO() {  
  ModelWithNullableInt = o.ModelWithNullableDecimal.HasValue ? (int?)o.ModelWithNullableDecimal: null})  

Further technical details

EF Core version: Reproduced in 1.1.2 as well as 2.0.0-preview1-final
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating system: Windows 10 64-bit
IDE: Visual Studio 2017 - build 26510.0 Preview

@samb0t samb0t changed the title InvalidCastException when casting from double? to int? in a simple select statement - EFCore 2.0.0-preview1-final InvalidCastException when casting from double? to int? in a simple select statement - EFCore 1.1.2 and 2.0.0-preview1-final Jun 1, 2017
@ajcvickers ajcvickers added this to the 2.0.0 milestone Jun 2, 2017
@maumar maumar changed the title InvalidCastException when casting from double? to int? in a simple select statement - EFCore 1.1.2 and 2.0.0-preview1-final InvalidCastException when casting from one value type to another in a simple select statement Jun 15, 2017
maumar added a commit that referenced this issue Jun 15, 2017
…to another in a simple select statement

Problem was for queries with value types being projected in a select expression when also using convert. The problem was that getValue is typed as object which then was converted to an expected type. However if the value returned by SQL was not exactly the same type, exception would get thrown due to boxing/unboxing.

Fix is to detect when we apply convert on a top level projection, and in this case use explicit cast, so that type returned by SQL was exactly the same as the type that was expected after unboxing.
maumar added a commit that referenced this issue Jun 15, 2017
…to another in a simple select statement

Problem was for queries with value types being projected in a select expression when also using convert. The problem was that getValue is typed as object which then was converted to an expected type. However if the value returned by SQL was not exactly the same type, exception would get thrown due to boxing/unboxing.

Fix is to detect when we apply convert on a top level projection, and in this case use explicit cast, so that type returned by SQL was exactly the same as the type that was expected after unboxing.

Also added support for translating Negate expression, which was previously evaluated on the client.
maumar added a commit that referenced this issue Jun 15, 2017
…to another in a simple select statement

Problem was for queries with value types being projected in a select expression when also using convert. The problem was that getValue is typed as object which then was converted to an expected type. However if the value returned by SQL was not exactly the same type, exception would get thrown due to boxing/unboxing.

Fix is to detect when we apply convert on a top level projection, and in this case use explicit cast, so that type returned by SQL was exactly the same as the type that was expected after unboxing.

Also added support for translating Negate expression, which was previously evaluated on the client.
maumar added a commit that referenced this issue Jun 15, 2017
…to another in a simple select statement

Problem was for queries with value types being projected in a select expression when also using convert. The problem was that getValue is typed as object which then was converted to an expected type. However if the value returned by SQL was not exactly the same type, exception would get thrown due to boxing/unboxing.

Fix is to detect when we apply convert on a top level projection, and in this case use explicit cast, so that type returned by SQL was exactly the same as the type that was expected after unboxing.

Also added support for translating Negate expression, which was previously evaluated on the client.
maumar added a commit that referenced this issue Jun 15, 2017
…to another in a simple select statement

Problem was for queries with value types being projected in a select expression when also using convert. The problem was that getValue is typed as object which then was converted to an expected type. However if the value returned by SQL was not exactly the same type, exception would get thrown due to boxing/unboxing.

Fix is to detect when we apply convert on a top level projection, and in this case use explicit cast, so that type returned by SQL was exactly the same as the type that was expected after unboxing.

Also added support for translating Negate expression, which was previously evaluated on the client.
maumar added a commit that referenced this issue Jun 16, 2017
…to another in a simple select statement

Problem was for queries with value types being projected in a select expression when also using convert. The problem was that getValue is typed as object which then was converted to an expected type. However if the value returned by SQL was not exactly the same type, exception would get thrown due to boxing/unboxing.

Fix is to detect when we apply convert on a top level projection, and in this case use explicit cast, so that type returned by SQL was exactly the same as the type that was expected after unboxing.

Also added support for translating Negate expression, which was previously evaluated on the client.
@maumar
Copy link
Contributor

maumar commented Jun 16, 2017

fixed in 44e7316

@maumar maumar closed this as completed Jun 16, 2017
@maumar maumar added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Jun 16, 2017
@natelaff
Copy link

I'm seeing this in 2.0.3 preview.

The following will throw an InvalidCastException. Worked fine in 1.1

    public async Task<decimal> GetSubtotalTotalAsync(string cartId)
    {
        return await _context.CartItems
            .Include(c => c.ProductVariant)
            .Where(c => c.CartId == cartId)
            .Select(c => c.ProductVariant.Price.GetValueOrDefault() * c.Quantity)
            .SumAsync();
    }

@ajcvickers
Copy link
Member

@natelaff Is this something that worked for you in 2.0.0 but is now failing in 2.0.3 preview? Can you post or link to a full project or code listing that demonstrates the issue so we can investigate it?

@natelaff
Copy link

@ajcvickers I'm uncertain. There were so many regressions in 2.0 that I couldn't even get this far into migrating my app until now. I will try to repro in a clean project.

@ajcvickers
Copy link
Member

@maumar Can you please investigate this?

@maumar
Copy link
Contributor

maumar commented Oct 24, 2017

I verified this is a bug in the current bits, but it's not a regression from 2.0. The bug is not related to this one, so I will file a new issue to track this.

workaround is to perform the multiplication and sum on the client (it would be done so anyway, because we don't know how to translate GetValueOrDefault - will file an issue for this also)

here is the code that should work:

public async Task<decimal> GetSubtotalTotalAsync(string cartId)
{
	using (var ctx = new MyContext())
	{
		var result = await ctx.CartItems
			//.Include(c => c.ProductVariant) - include was not needed here navigation access will do this automatically
			.Where(c => c.CartId == cartId)
			.Select(c => new { Price = c.ProductVariant.Price, c.Quantity })
			.ToListAsync();

		return result
			.Select(c => c.Price.GetValueOrDefault() * c.Quantity)
			.Sum();
	}
}

@maumar
Copy link
Contributor

maumar commented Oct 24, 2017

filed #10152 and #10153

maumar added a commit that referenced this issue Feb 14, 2018
…r decimal to float cast

Problem was that as part of fixing #8652 we were removing explicit casts when processing result operators. This is fine for most aggregate operators, but is not correct for Min/Max. This is because the final result type is the same as the argument - original type of the argument must be preserved or we risk type mismatches in the final result.

Additionally improved the fix for #1251 - we should be stripping order by clauses for Min/Max operators as well. Previously we exempt all ChoiceResultOperatorBase, instead of correct First/Single/Last.
maumar added a commit that referenced this issue Feb 14, 2018
…r decimal to float cast

Problem was that as part of fixing #8652 we were removing explicit casts when processing result operators. This is fine for most aggregate operators, but is not correct for Min/Max. This is because the final result type is the same as the argument - original type of the argument must be preserved or we risk type mismatches in the final result.

Additionally improved the fix for #1251 - we should be stripping order by clauses for Min/Max operators as well. Previously we exempt all ChoiceResultOperatorBase, instead of correct First/Single/Last.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-bug
Projects
None yet
Development

No branches or pull requests

4 participants