Set operation fails with client projection error
- 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
Assessment
This issue has not been assessed yet.