Nested projection fails to translate to query

Open
#26,352 1 comment 3 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
4/5
Estimated time
3-5 days
Newbie friendliness
35/100
Issue type
Bug
Clarity
Mostly clear
Activity status
Stale
Tech stack
csharp
Domain
database

Research direction

Start with the repro in projection.Program.Main, reported at projection\Program.cs line 31, and inspect the RelationalProjectionBindingExpressionVisitor frames in the stack trace. Run the nested projection query and compare it with the first projection; done means the second projection translates and executes without the reported InvalidOperationException.

Written by the indexing model from the issue text.

Description

area-query customer-reported

Hi.
I am trying exposing entities as projected dto-s and I have additional middleware which applies final projections to dto-s to select only specific data. Currently it fails to translate query when trying to access projected collection.

var queryable = dbContext.ClaimCases.Select(c => new ClaimDto
{
    ClaimNumber = c.ClaimNumber,
    Policy = c.Policy == null
        ? null
        : new PolicyDto
        {
            PolicyNumber = c.Policy.PolicyNumber,
            TermsAndConditions = c.Policy.TermsAndConditions.Select(tc =>
                new TermsAndConditionsDto
                {
                    Label = tc.Label,
                    Code = tc.Code
                }).ToList()
        }
});

var dto = queryable.Select(c => new ClaimDto
    {
        ClaimNumber = c.ClaimNumber,
        Policy = c.Policy != null
            ? new PolicyDto
            {
                PolicyNumber = c.Policy.PolicyNumber,
                TermsAndConditions = c.Policy.TermsAndConditions.Select(t => // if this is included it will cause exception
                    new TermsAndConditionsDto
                    {
                        Code = t.Code
                    }).ToList()
            }
            : null
    })
    .ToList();
Stack trace
Compiling query expression:
      'DbSet<Claim>()
          .Select(c => new ClaimDto{
              ClaimNumber = c.ClaimNumber,
              Policy = c.Policy == null ? null : new PolicyDto{
                  PolicyNumber = c.Policy.PolicyNumber,
                  TermsAndConditions = c.Policy.TermsAndConditions
                      .Select(tc => new TermsAndConditionsDto{
                          Label = tc.Label,
                          Code = tc.Code
                      }
                      )
                      .ToList()
              }

          }
          )
          .Select(c => new ClaimDto{
              ClaimNumber = c.ClaimNumber,
              Policy = c.Policy != null ? new PolicyDto{
                  PolicyNumber = c.Policy.PolicyNumber,
                  TermsAndConditions = c.Policy.TermsAndConditions
                      .Select(t => new TermsAndConditionsDto{ Code = t.Code }
                      )
                      .ToList()
              }
               : null
          }
          )'
Unhandled exception. System.InvalidOperationException: The LINQ expression 't' 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 'AsEnumerab
le', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMember(MemberExpression memberExpression)
   at System.Linq.Expressions.MemberExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   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.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitConditional(ConditionalExpression conditionalExpression)
   at System.Linq.Expressions.ConditionalExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberAssignment(MemberAssignment memberAssignment)
   at System.Linq.Expressions.ExpressionVisitor.VisitMemberBinding(MemberBinding node)
   at Microsoft.EntityFrameworkCore.Query.Internal.RelationalProjectionBindingExpressionVisitor.VisitMemberInit(MemberInitExpression memberInitExpression)
   at System.Linq.Expressions.MemberInitExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   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 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.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.List`1..ctor(IEnumerable`1 collection)
   at System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   at projection.Program.Main(String[] args) in C:\repos\github\ef-projection-issue\projection\Program.cs:line 31

Repro steps
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;

namespace projection
{
    public static class Program
    {
        static void Main(string[] args)
        {
            using (var dbContext = new DatabaseContext())
            {
                dbContext.Database.EnsureDeleted();
                dbContext.Database.EnsureCreated();

                var queryable = dbContext.ClaimCases.Select(c => new ClaimDto
                {
                    ClaimNumber = c.ClaimNumber,
                    Policy = c.Policy == null
                        ? null
                        : new PolicyDto
                        {
                            PolicyNumber = c.Policy.PolicyNumber,
                            TermsAndConditions = c.Policy.TermsAndConditions.Select(tc =>
                                new TermsAndConditionsDto
                                {
                                    Label = tc.Label,
                                    Code = tc.Code
                                }).ToList()
                        }
                });

                var dto = queryable.Select(c => new ClaimDto
                    {
                        ClaimNumber = c.ClaimNumber,
                        Policy = c.Policy != null
                            ? new PolicyDto
                            {
                                PolicyNumber = c.Policy.PolicyNumber,
                                TermsAndConditions = c.Policy.TermsAndConditions.Select(
                                    t => // this will cause exception
                                        new TermsAndConditionsDto
                                        {
                                            Code = t.Code
                                        }).ToList()
                            }
                            : null
                    })
                    .ToList();
            }
        }
    }
    
    public class Claim
    {
        public int Id { get; set; }
        public int? PolicyId { get; set; }
        public Policy Policy { get; set; }
        public string ClaimNumber { get; set; }
    }

    public class Policy
    {
        public int Id { get; set; }
        public string PolicyNumber { get; set; }
        public List<Claim> ClaimCases { get; set; }
        public List<TermsAndConditions> TermsAndConditions { get; set; }
    }

    public class TermsAndConditions
    {
        public int Id { get; set; }
        public string Code { get; set; }
        public string Label { get; set; }
        public int PolicyId { get; set; }
        public Policy Policy { get; set; }
    }

    public class ClaimDto
    {
        public string ClaimNumber { get; set; }
        public PolicyDto Policy { get; set; }
    }

    public class PolicyDto
    {
        public string PolicyNumber { get; set; }
        public List<TermsAndConditionsDto> TermsAndConditions = new List<TermsAndConditionsDto>();
    }

    public class TermsAndConditionsDto
    {
        public string Label { get; set; }
        public string Code { get; set; }
    }

    public class DatabaseContext : DbContext
    {
        public DbSet<Claim> ClaimCases { get; set; }
        public DbSet<Policy> Policies { get; set; }
        public DbSet<TermsAndConditions> TermsAndConditions { get; set; }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            optionsBuilder.UseSqlServer(
                @"Server=localhost;Initial Catalog=ef-projection;Integrated Security=false;User ID=sa;Password=Password1234;");
            optionsBuilder.LogTo(Console.WriteLine);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Claim>().HasKey(x => x.Id);
            modelBuilder.Entity<Claim>().HasIndex(p => p.ClaimNumber).IsUnique();
            modelBuilder.Entity<Claim>().HasOne(x => x.Policy)
                .WithMany(x => x.ClaimCases)
                .HasForeignKey(x => x.PolicyId)
                .HasPrincipalKey(x => x.Id)
                .OnDelete(DeleteBehavior.Restrict);
            modelBuilder.Entity<Claim>().Property(p => p.ClaimNumber).HasMaxLength(50).IsRequired();

            modelBuilder.Entity<Policy>().HasKey(p => p.Id);
            modelBuilder.Entity<Policy>().Property(p => p.PolicyNumber).HasMaxLength(50).IsRequired();

            modelBuilder.Entity<TermsAndConditions>().HasKey(p => p.Id);
            modelBuilder.Entity<TermsAndConditions>().Property(x => x.Code).HasMaxLength(50).IsRequired();
            modelBuilder.Entity<TermsAndConditions>().Property(x => x.Label).HasMaxLength(50).IsRequired();
            modelBuilder.Entity<TermsAndConditions>().HasOne(x => x.Policy)
                .WithMany(policy => policy.TermsAndConditions)
                .HasForeignKey(x => x.PolicyId);

            base.OnModelCreating(modelBuilder);
        }
    }
}
Provider and version information

EF Core version: 5.0.11
Database provider: Microsoft.EntityFrameworkCore.SqlServer
Target framework: .NET Core 3.1

Dominant language
C#
Stars
14.8k
Forks
3.4k
Avg merge
2d 5h
Merged PRs (30d)
134

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from dotnet/efcore

All issues in dotnet/efcore

Similar issues

More C# issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.