dotnet / dotnet/efcore

Set operation fails with client projection error

Open
#36,326 1 comment 0 reactions 0 assignees View on GitHub
area-query area-set-operations customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### Bug description

This is similar to https://github.com/dotnet/efcore/issues/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

```C#
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 { get; set; }
public DbSet EntityB { get; set; }
public DbSet 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()
.HasMany(x => x.EntityCs);
modelBuilder
.Entity()
.HasMany(x => x.EntityCs);
}
}

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

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

public class EntityB : EntityBase
{
public ICollection 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
```C#
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()
.ForMember(x => x.EntityCDto, o => o.MapFrom(x => x.EntityCs.OrderBy(x => x.Id).FirstOrDefault()));

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

a.CreateMap();
}, new LoggerFactory())
.CreateMapper();

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

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

var queryB = ctx.EntityB.ProjectTo(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

```text
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.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.d__67`1.MoveNext()
at Program.<$>d__0.MoveNext() in C:\Users\steven.darby\source\repos\Playground\Playground\Program.cs:line 39
at Program.<$>d__0.MoveNext() in C:\Users\steven.darby\source\repos\Playground\Playground\Program.cs:line 39
at Program.(String[] args)

```

### Verbose output

```text

```

### EF Core version

9.0.6

### Database provider

SqlServer

### Target framework

_No response_

### Operating system

_No response_

### IDE

_No response_

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.