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

"No coercion operator is defined" with dynamically generated selection expression #11165

Closed
samfozz opened this issue Mar 6, 2018 · 2 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

@samfozz
Copy link

samfozz commented Mar 6, 2018

I'm trying to generate a selection expression at runtime using System.Linq.Expressions.

This expression selects a navigation property - I am able to generate an expression equivalent to the following with no problems and it works well correctly :

 a => new DocumentTag()
                {
                   new Tag()
                        {
                            Id = a.Tag.Id
                        } 
                }

However, the issue seems to come in when I attempt to add a null check to the generated expression, like this :

 a => new DocumentTag()
                {
                    Tag = a.Tag != null
                        ? new Tag()
                        {
                            Id = a.Tag.Id
                        }
                        : null

                };

This expression when manually coded as above works fine, But when I generate one using Expression.Lambda, I get the "No coercion operator" error executing the expression.

I've created an example of the issue here ( this uses the Microsoft.EntityFramworkCore 2.0.1 / Microsoft.EntityFramworkCore.InMemory / 2.0.1 nuget packages ) - This is a full dotnet core app with the dynamic expression generation being in the MakeDynamicExpression method there.

https://gist.github.com/samfozz/58c799d88b24f6c3f70c5a6dff5b57c1

I've can't seem to find the difference between the generated expression and the hard coded one - they are identical in debug view, and I have not been able to find the difference inspecting the objects in the debugger. The generated expression works correctly when compiled to a Func<TEntity,object>.

Exception message: 

No coercion operator is defined between types 'System.Int32' and 'DynamicExpressionProblem.Tag'.

Stack trace:
   at System.Linq.Expressions.Expression.GetUserDefinedCoercionOrThrow(ExpressionType coercionType, Expression expression, Type convertToType)
   at System.Linq.Expressions.Expression.Convert(Expression expression, Type type, MethodInfo method)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.NavigationRewritingExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.NavigationRewritingExpressionVisitor.VisitConditional(ConditionalExpression node)
   at System.Linq.Expressions.ConditionalExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.NavigationRewritingExpressionVisitor.VisitMemberAssignment(MemberAssignment node)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at System.Linq.Expressions.ExpressionVisitor.Visit[T](ReadOnlyCollection`1 nodes, Func`2 elementVisitor)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberInit(MemberInitExpression node)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Remotion.Linq.Clauses.SelectClause.TransformExpressions(Func`2 transformation)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.NavigationRewritingExpressionVisitor.NavigationRewritingQueryModelVisitor.VisitSelectClause(SelectClause selectClause, QueryModel queryModel)
   at Remotion.Linq.Clauses.SelectClause.Accept(IQueryModelVisitor visitor, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.Internal.NavigationRewritingExpressionVisitor.Rewrite(QueryModel queryModel, QueryModel parentQueryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.OptimizeQueryModel(QueryModel queryModel, Boolean asyncQuery)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
--- End of stack trace from previous location where exception was thrown ---
   at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](Expression query, INodeTypeProvider nodeTypeProvider, IDatabase database, IDiagnosticsLogger`1 logger, Type contextType)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass15_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Remotion.Linq.QueryableBase`1.GetEnumerator()
   at System.Collections.Generic.List`1.AddEnumerable(IEnumerable`1 enumerable)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at DynamicExpressionProblem.Program.Main(String[] args) in c:\users\sam.foster\documents\visual studio 2017\Projects\DynamicExpressionProblem\DynamicExpressionProblem\Program.cs:line 126

Steps to reproduce

See here :
https://gist.github.com/samfozz/58c799d88b24f6c3f70c5a6dff5b57c1

Further technical details

EF Core version: 2.0.1
Database Provider: InMemory
Operating system: Windows 10
IDE: Visual Studio 2017

@ajcvickers
Copy link
Member

@samfozz Looking in the debugger, the difference in the expression trees is in the type of the null constant used int the conditional. Changing it to type object in the explicitly built tree results in identitical expression trees and it then works with EF query:

var mainAssignment = Expression.Bind(navProp,
    Expression.Condition(
        Expression.MakeBinary(ExpressionType.NotEqual, Expression.MakeMemberAccess(parent, navProp),
            Expression.Constant(null)), // <-- Change is here
        Expression.MemberInit(Expression.New(ctor), subAssignments),
        Expression.Constant(null, navProp.PropertyType)));

Leaving this issue open to discuss in triage if this is something we need to address in the query pipeline.

@samfozz
Copy link
Author

samfozz commented Mar 7, 2018

@ajcvickers Sorry I didn't spot that myself - Thanks for this!

@ajcvickers ajcvickers added this to the 2.1.0 milestone Mar 7, 2018
smitpatel added a commit that referenced this issue Mar 30, 2018
- Introduce rewrite for reference navigation (which was being done in nav expansion before)
- Disallow rewrite for owned entities (forces client eval for now) See #11485
- Streamline rewrite between Equals method & `==`
- Convert to false when 2 unrelated entityTypes are being compared

Resolves #11165
smitpatel added a commit that referenced this issue Mar 30, 2018
- Introduce rewrite for reference navigation (which was being done in nav expansion before)
- Disallow rewrite for owned entities (forces client eval for now) See #11485
- Streamline rewrite between Equals method & `==`
- Convert to false when 2 unrelated entityTypes are being compared

Resolves #11165
smitpatel added a commit that referenced this issue Mar 30, 2018
- Introduce rewrite for reference navigation (which was being done in nav expansion before)
- Disallow rewrite for owned entities (forces client eval for now) See #11485
- Streamline rewrite between Equals method & `==`
- Convert to false when 2 unrelated entityTypes are being compared

Resolves #11165
smitpatel added a commit that referenced this issue Mar 30, 2018
- Introduce rewrite for reference navigation (which was being done in nav expansion before)
- Disallow rewrite for owned entities (forces client eval for now) See #11485
- Streamline rewrite between Equals method & `==`
- Convert to false when 2 unrelated entityTypes are being compared

Resolves #11165
smitpatel added a commit that referenced this issue Mar 30, 2018
- Introduce rewrite for reference navigation (which was being done in nav expansion before)
- Disallow rewrite for owned entities (forces client eval for now) See #11485
- Streamline rewrite between Equals method & `==`
- Convert to false when 2 unrelated entityTypes are being compared

Resolves #11165
@smitpatel smitpatel added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Mar 30, 2018
@ajcvickers ajcvickers modified the milestones: 2.1.0-preview2, 2.1.0 Nov 11, 2019
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

3 participants