Skip to content

Query translation fails when Where references a projection guarded by child != null && grandChild != null #38565

Description

@aheubusch

Bug description

A query that projects an object conditionally using child != null && grandChild != null ? ... : null executes successfully on its own, but fails to translate once a Where clause references a property of the projected object.

The equivalent logic expressed using nested ternary operators (child != null ? grandChild != null ? ... : null : null) translates successfully, even though it is semantically equivalent.

This suggests that EF Core generates a different expression tree for the two forms and does not correctly translate the && version when the projection is later referenced in a predicate.

Expected behavior

Both forms are semantically equivalent and should either translate successfully or fail consistently. The query using the && conditional should translate the same way as the nested ternary version.

Actual behavior

The query fails during translation with:

System.InvalidOperationException: The LINQ expression ... could not be translated.

Additional information

  • The initial projection (ToListAsync()) succeeds.
  • Translation only fails when a subsequent query operator references the projected conditional object.
  • Rewriting the condition as nested ternary operators is a viable workaround, indicating this is likely a query translation issue rather than an unsupported LINQ pattern.

Your code

// Entities
public class Parent
{
    public Guid Id { get; set; }
    public Guid? ChildId { get; set; }
}

public class Child
{
    public Guid Id { get; set; }
    public Guid? GrandChildId { get; set; }
}

public class GrandChild
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
}

// Reproduce the bug
var query = from parent in context.Parents
            join child in context.Children on parent.ChildId equals child.Id into childGroup
            from child in childGroup.DefaultIfEmpty()
            join grandChild in context.GrandChildren on child.GrandChildId equals grandChild.Id into grandChildGroup
            from grandChild in grandChildGroup.DefaultIfEmpty()
            select new
            {
                Id = parent.Id,
                Child = child != null && grandChild != null ? new
                {
                    GrandChildId = child.GrandChildId
                } : null,
            };

// ✅ This works
var result = await query.ToListAsync();

// ❌ This throws InvalidOperationException - cannot be translated
var result2 = await query.Where(x => x.Child.GrandChildId != null).ToListAsync();


// Workaround - Nested ternary operators
var workingQuery = from parent in context.Parents
                   join child in context.Children on parent.ChildId equals child.Id into childGroup
                   from child in childGroup.DefaultIfEmpty()
                   join grandChild in context.GrandChildren on child.GrandChildId equals grandChild.Id into grandChildGroup
                   from grandChild in grandChildGroup.DefaultIfEmpty()
                   select new
                   {
                       Id = parent.Id,
                       // Workaround: nest the ternary operators
                       Child = child != null ? grandChild != null ? new
                       {
                           GrandChildId = child.GrandChildId
                       } : null : null,
                   };
				   
// ✅ Workaround using nested ternary operator works
var workingResult = await workingQuery.ToListAsync();

Stack traces

System.InvalidOperationException: The LINQ expression 'DbSet<Parent>()
    .LeftJoin(
        inner: DbSet<Child>(),
        outerKeySelector: p => (object)p.ChildId,
        innerKeySelector: c => (object)(Guid?)c.Id,
        resultSelector: (p, c) => new TransparentIdentifier<Parent, Child>(
            Outer = p,
            Inner = c
        ))
    .LeftJoin(
        inner: DbSet<GrandChild>(),
        outerKeySelector: ti => (object)ti.Inner.GrandChildId,
        innerKeySelector: g => (object)(Guid?)g.Id,
        resultSelector: (ti, g) => new TransparentIdentifier<TransparentIdentifier<Parent, Child>, GrandChild>(
            Outer = ti,
            Inner = g
        ))
    .Where(ti0 => ti0.Outer.Inner != null && ti0.Inner != null ? new Child{ GrandChildId = ti0.Outer.Inner.GrandChildId }
     : null.GrandChildId != null)' 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 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
   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__DisplayClass11_0`1.<ExecuteCore>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
``

Verbose output


EF Core version

10.0.9

Database provider

Microsoft.EntityFrameworkCore.SqlServer

Target framework

.NET 10

Operating system

Windows 11

IDE

Visual Studio 2026 18.7.3

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions