Skip to content

Set operation fails with client projection error #36326

@stevendarby

Description

@stevendarby

Bug description

This is similar to #32977 but without the collection projection.

I'm including two repros. I first discovered the issue with AutoMapper and then tried to make a repro without it to keep it minimal. However, my repro without AutoMapper has a different expression tree. I believe they likely fail due to the same root cause, but it may be good to verify any fix against both repros. (I could probably try harder to do a repro that produces the same expression tree as AutoMapper, but then you'd start to question why anyone would write a query that way!)

Your code

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

var queryA = ctx.EntityA
    .Select(x => new CombinedDto
    {
        Id = x.Id,
        EntityCDto = x.EntityCs
            .OrderBy(x => x.Id)
            .Select(x => new EntityCDto { Value1 = x.Value1, Value2 = x.Value2 })
            .FirstOrDefault()
    });

var queryB = ctx.EntityB
    .Select(x => new CombinedDto
    {
        Id = x.Id,
        EntityCDto = x.EntityCs
            .OrderBy(x => x.Id)
            .Select(x => new EntityCDto { Value1 = x.Value1, Value2 = x.Value2 })
            .FirstOrDefault()
    });

// Query A works on its own
_ = await queryA.ToListAsync();

// Query B works on its own
_ = await queryB.ToListAsync();

// Query A + B fails
var concatQuery = queryA.Concat(queryB);
_ = await concatQuery.ToListAsync();

public class BlogContext : DbContext
{
    public DbSet<EntityA> EntityA { get; set; }
    public DbSet<EntityB> EntityB { get; set; }
    public DbSet<EntityC> EntityC { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        => optionsBuilder
            .UseSqlServer("Server=(localdb)\\mssqllocaldb;Database=SetFailure;Trusted_Connection=True;Encrypt=False")
            .LogTo(Console.WriteLine, LogLevel.Information)
            .EnableSensitiveDataLogging();

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);

        modelBuilder
            .Entity<EntityA>()
            .HasMany(x => x.EntityCs);
        modelBuilder
            .Entity<EntityB>()
            .HasMany(x => x.EntityCs);
    }
}

public class EntityBase
{
    public Guid Id { get; set; }
}

public class EntityA : EntityBase
{
    public ICollection<EntityC> EntityCs { get; set; } = [];
}

public class EntityB : EntityBase
{
    public ICollection<EntityC> EntityCs { get; set; } = [];
}

public class EntityC : EntityBase
{
    public required string Value1 { get; set; }
    public required string Value2 { get; set; }
}

public class CombinedDto
{
    public Guid Id { get; set; }
    public EntityCDto? EntityCDto { get; set; }
}

public class EntityCDto
{
    public required string Value1 { get; set; }
    public required string Value2 { get; set; }
}

Using AutoMapper 15.0.0 nuget

using AutoMapper;
using AutoMapper.QueryableExtensions;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;

var mapper = new MapperConfiguration(
    a =>
    {
        a.CreateMap<EntityA, CombinedDto>()
            .ForMember(x => x.EntityCDto, o => o.MapFrom(x => x.EntityCs.OrderBy(x => x.Id).FirstOrDefault()));

        a.CreateMap<EntityB, CombinedDto>()
            .ForMember(x => x.EntityCDto, o => o.MapFrom(x => x.EntityCs.OrderBy(x => x.Id).FirstOrDefault()));

        a.CreateMap<EntityC, EntityCDto>();
    }, new LoggerFactory())
    .CreateMapper();

await using var ctx = new BlogContext();
await ctx.Database.EnsureDeletedAsync();
await ctx.Database.EnsureCreatedAsync();

var queryA = ctx.EntityA.ProjectTo<CombinedDto>(mapper.ConfigurationProvider);

var queryB = ctx.EntityB.ProjectTo<CombinedDto>(mapper.ConfigurationProvider);

// Query A works on its own
_ = await queryA.ToListAsync();

// Query B works on its own
_ = await queryB.ToListAsync();

// Query A + B fails
var concatQuery = queryA.Concat(queryB);
_ = await concatQuery.ToListAsync();

// snip - same context and models as above

Stack traces

System.InvalidOperationException
  HResult=0x80131509
  Message=Unable to translate set operation after client projection has been applied. Consider moving the set operation before the last 'Select' call.
  Source=Microsoft.EntityFrameworkCore.Relational
  StackTrace:
   at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.ApplySetOperation(SetOperationType setOperationType, SelectExpression select2, Boolean distinct)
   at Microsoft.EntityFrameworkCore.Query.SqlExpressions.SelectExpression.ApplyUnion(SelectExpression source2, Boolean distinct)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.TranslateConcat(ShapedQueryExpression source1, ShapedQueryExpression source2)
   at Microsoft.EntityFrameworkCore.Query.QueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryableMethodTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   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() in /_/src/libraries/System.Private.CoreLib/src/System/Runtime/CompilerServices/ConfiguredCancelableAsyncEnumerable.cs:line 44
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.<ToListAsync>d__67`1.MoveNext()
   at Program.<<Main>$>d__0.MoveNext() in C:\Users\steven.darby\source\repos\Playground\Playground\Program.cs:line 39
   at Program.<<Main>$>d__0.MoveNext() in C:\Users\steven.darby\source\repos\Playground\Playground\Program.cs:line 39
   at Program.<Main>(String[] args)

Verbose output


EF Core version

9.0.6

Database provider

SqlServer

Target framework

No response

Operating system

No response

IDE

No response

Metadata

Metadata

Assignees

Type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions

    pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy