Skip to content

A combination of downcast and owned types could not be translated in 3.1, but could in 3.0 #19138

New issue

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

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

Already on GitHub? # to your account

Closed
ze1tgeist opened this issue Dec 3, 2019 · 5 comments · Fixed by #19377
Closed

Comments

@ze1tgeist
Copy link

When running the supplied code in Efcore 3.0.1, it successfully runs to completion. When running it in Efcore 3.1.0-preview3.19554.8, it throws an InvalidOperationException with the message Could not be translated.

Code to reproduce

using Microsoft.EntityFrameworkCore;
using System;
using System.Linq;

namespace ReproEfcore31CastError
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new AppDbContext())
            {
                db.Database.EnsureDeleted();
                db.Database.EnsureCreated();
                
                // THE FOLLOWING EXPRESSION CAUSES THE ERROR
                var thisCausesAnErrorIn31ButNot30 =
                    db
                    .BaseEntities
                    .Select(b =>
                        db.OtherEntities
                        .Where(o => o.OtherEntityData == ((SubEntity)b).Data)
                        .FirstOrDefault()
                    );
                  var errorHappensOnThisLine = thisCausesAnErrorIn31ButNot30.ToArray();
            }
        }
    }

    class BaseEntity
    {
        public int Id { get; set; }
    }

    class SubEntity : BaseEntity
    {
        public string Data { get; set; }
        public Owned Owned { get; set; }
    }


    public class Owned
    {
        public string OwnedData { get; set; }
    }

    class OtherEntity
    {
        public string Id { get; set; }
        public string OtherEntityData { get; set; }
    }

    class AppDbContext : DbContext
    {
        public DbSet<BaseEntity> BaseEntities { get; set; }
        public DbSet<OtherEntity> OtherEntities { get; set; }
        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer("server=localhost;database=ReproEfCore31CastError;integrated security=true");
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<BaseEntity>();
            modelBuilder.Entity<SubEntity>().OwnsOne(se => se.Owned);
            modelBuilder.Entity<OtherEntity>();
        }
    }
}

Code that doesn't produce the error

If the Owned property is removed from SubEntity, it runs fine.
It also works, if the cast is removed, by adding an OfType as follows:

var thisWorks =
    db
    .BaseEntities
    .OfType<SubEntity>()
    .Select(b =>
        db.OtherEntities
        .Where(o => o.OtherEntityData == b.Data)
        .FirstOrDefault()
    )
    .ToArray();

Full error message

The LINQ expression 'DbSet<OtherEntity>
    .Where(o => o.OtherEntityData == ((SubEntity)(IncludeExpression(
        (EntityShaperExpression: 
            EntityType: BaseEntity
            ValueBufferExpression: 
                (ProjectionBindingExpression: EmptyProjectionMember)
            IsNullable: False
        ), 
        (EntityShaperExpression: 
            EntityType: Owned
            ValueBufferExpression: 
                [EntityProjectionExpression]
            IsNullable: True
        ), Owned)
    )).Data)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

Technical details

EF Core version: Efcore 3.1.0-preview3.19554.8 (but not in 3.0.1)
Target Framework: netcoreapp3.0
Database Provider: Microsoft.EntityFrameworkCore.SqlServer
Operating System: Windows Server 2016 Datacenter 10.0.14393 Build 14393
IDE: Visual Studio Professional 2019 16.3.4

@ajcvickers
Copy link
Contributor

Note for triage: stack trace:

Unhandled exception. System.InvalidOperationException: The LINQ expression 'DbSet<OtherEntity>
    .Where(o => o.OtherEntityData == ((SubEntity)(IncludeExpression(
        (EntityShaperExpression:
            EntityType: BaseEntity
            ValueBufferExpression:
                (ProjectionBindingExpression: EmptyProjectionMember)
            IsNullable: False
        ),
        (EntityShaperExpression:
            EntityType: Owned
            ValueBufferExpression:
                [EntityProjectionExpression]
            IsNullable: True
        ), Owned)
    )).Data)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), As
AsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.<VisitMethodCall>g__CheckTranslated|8_0(ShapedQueryExpression translated, <>c__DisplayClass8_0& )
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.TranslateSubquery(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.RelationalSqlTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Translate(SelectExpression selectExpression, Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateSelect(ShapedQueryExpression source, LambdaExpression selector)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.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 Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   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 Program.Main(String[] args) in C:\Stuff\ThreeOne\ThreeOne\Program.cs:line 22

@smitpatel smitpatel self-assigned this Dec 5, 2019
@ajcvickers
Copy link
Contributor

Note for triage: my understanding from triage was that OfType could be a workaround. However, I may have misunderstood since this:

var thisCausesAnErrorIn31ButNot30 =
    db
        .BaseEntities
        .OfType<SubEntity>()
        .Select(b =>
            db.OtherEntities
                    .Where(o => o.OtherEntityData == b.Data)
                .FirstOrDefault()
        );
var errorHappensOnThisLine = thisCausesAnErrorIn31ButNot30.ToArray();

returns different results. Specifically, in will filter out any non-SubEntity instances rather than returning null for them.

@smitpatel
Copy link
Contributor

smitpatel commented Dec 7, 2019

Scratch that!

@smitpatel
Copy link
Contributor

Probably combination of other filters/set operations etc can get desired behavior.

@ze1tgeist
Copy link
Author

Hi,

Thanks for looking into this :-)

I have posted the minimum repro for the issue, which could be worked around. As it stands, the code actually looks sloppy. The expression in our code that uncovered the issue contains a ternary expression, which, while not required to reproduce the error, gives some purpose to the structure of the code.

db
  .BaseEntities
  .Select(b =>
    b is SubEntity
    ? db.OtherEntities
         .Where(o => o.OtherEntityData == ((SubEntity)b).Data)
         .FirstOrDefault()
    : ((SubEntity2)b).OtherEntity 
  );

...

public class OtherSubEntity2 : BaseEntity
{
  public OtherEntity OtherEntity { get; set; }
}

So a workaround using OfType doesn't work for me (unless there is a clever way of using it that I haven't thought of, which is entirely possible).

@ajcvickers ajcvickers added this to the 5.0.0 milestone Dec 9, 2019
smitpatel added a commit that referenced this issue Dec 20, 2019
smitpatel added a commit that referenced this issue Dec 27, 2019
smitpatel added a commit that referenced this issue Dec 30, 2019
Resolves #18140
Resolves #18374
Resolves #18672
Resolves #18734
Resolves #19138
Resolves #19207
smitpatel added a commit that referenced this issue Dec 30, 2019
- Skip/Take does not force applying pending selector and changing shape.
- Throw translation failure message for Querayble methods which we don't translate (hence we don't process in navigation expansion). Earlier we threw query failed message. Now Navigation Expansion does not throw QueryFailed error message from any place.
- Unwrap type conversion for validating member access during include expansion so that we don't generate include when derived type's member is accessed.

Resolves #18140
Resolves #18374
Resolves #18672
Resolves #18734
Resolves #19138
Resolves #19207
smitpatel added a commit that referenced this issue Dec 30, 2019
- Skip/Take does not force applying pending selector and changing shape.
- Throw translation failure message for Querayble methods which we don't translate (hence we don't process in navigation expansion). Earlier we threw query failed message. Now Navigation Expansion does not throw QueryFailed error message from any place.
- Unwrap type conversion for validating member access during include expansion so that we don't generate include when derived type's member is accessed.

Resolves #18140
Resolves #18374
Resolves #18672
Resolves #18734
Resolves #19138
Resolves #19207
smitpatel added a commit that referenced this issue Jan 1, 2020
- Skip/Take does not force applying pending selector and changing shape.
- Throw translation failure message for Querayble methods which we don't translate (hence we don't process in navigation expansion). Earlier we threw query failed message. Now Navigation Expansion does not throw QueryFailed error message from any place.
- Unwrap type conversion for validating member access during include expansion so that we don't generate include when derived type's member is accessed.

Resolves #18140
Resolves #18374
Resolves #18672
Resolves #18734
Resolves #19138
Resolves #19207
smitpatel added a commit that referenced this issue Jan 1, 2020
- Skip/Take does not force applying pending selector and changing shape.
- Throw translation failure message for Querayble methods which we don't translate (hence we don't process in navigation expansion). Earlier we threw query failed message. Now Navigation Expansion does not throw QueryFailed error message from any place.
- Unwrap type conversion for validating member access during include expansion so that we don't generate include when derived type's member is accessed.

Resolves #18140
Resolves #18374
Resolves #18672
Resolves #18734
Resolves #19138
Resolves #19207
@ajcvickers ajcvickers modified the milestones: 5.0.0, 5.0.0-preview1 Mar 13, 2020
@ajcvickers ajcvickers modified the milestones: 5.0.0-preview1, 5.0.0 Nov 7, 2020
# for free to join this conversation on GitHub. Already have an account? # to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants