dotnet/efcore · error · InvalidOperationException
Unable to translate set operation after client projection…
Error message
Unable to translate set operation after client projection has been applied. Consider moving the set operation before the last 'Select' call.
What it means
The InMemory provider implements set operations (Union, Intersect, Except) by composing two server-side ValueBuffer streams. If client projections have already been applied to either side (i.e. _clientProjections is non-empty on a source), the provider can no longer merge at the server level and throws SetOperationsNotAllowedAfterClientEvaluation. The else-branch that throws is reached when at least one source has client projections that cannot be lifted.
Solutions
- Move the set operation (Union/Intersect/Except) before the last Select call so both operands are still server-translatable.
- Insert AsEnumerable() before the set operation to perform the union/intersect on the client side after materialization.
- Ensure both operands of the set operation project the same simple, fully-translatable shape before combining.
Example fix
// before — set op after client-projection Select
var query = ctx.Blogs
.Select(b => new { b.Id, Label = CustomLabel(b) })
.Union(ctx.Posts.Select(p => new { p.Id, Label = CustomLabel(p) }));
// after — move set op before the Select that needs client eval
var query = ctx.Blogs
.Select(b => new { b.Id, Label = b.Title })
.Union(ctx.Posts.Select(p => new { p.Id, Label = p.Title }))
.AsEnumerable()
.Select(x => new { x.Id, Label = CustomLabel(x) }); Defensive patterns
Strategy: validation
Validate before calling
// Validate query structure: ensure set operations (Union/Intersect/Except)
// appear before the last Select. If a Select must do client-side work,
// move it after AsEnumerable.
// Rule: no Select with client-side logic between the set op and the source.
// In practice, structure queries so both operands are fully translatable:
var part1 = ctx.Blogs.Select(b => new { b.Id, Key = b.Title });
var part2 = ctx.Posts.Select(p => new { p.Id, Key = p.Title });
var union = part1.Union(part2); // safe — both sides translatable Try / catch
try
{
return query.ToList();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("set operation after client projection"))
{
// Move the set operation before the client Select, or use AsEnumerable
var serverUnion = part1.Union(part2);
return serverUnion.AsEnumerable().Select(clientProject).ToList();
} Prevention
- Place Union/Intersect/Except before any Select that might cause client evaluation.
- Ensure both operands of a set operation project identical, translatable shapes.
- If client-side projection is needed, insert AsEnumerable() after the set operation.
- Test set-operation queries against InMemory provider in integration tests.
When it happens
Trigger: Calling a set operation (Union/Intersect/Except/Concat) after a Select that caused client-side projection evaluation in the InMemory provider. The throw happens during ApplySetOperation in InMemoryQueryExpression.
Common situations: Building a query like ctx.Blogs.Select(...complex...).Union(other) where the Select includes a non-translatable expression, or chaining a set operation after a projection that materializes a subquery. Moving from SQL Server (which can push the set op down) to InMemory tests and hitting the limitation.
Related errors
- The LINQ expression ' ' could not be translated. Either…
- The LINQ expression ' ' could not be translated. Additional…
- 'DefaultIfEmpty' cannot be applied after a client-evaluated…
- Using 'Distinct' operation on a projection containing a…
- A synchronous store management operation was performed and…
AI-assisted analysis of dotnet/efcore@3a2006ef56 (2026-08-11).
Data as JSON: /api/errors/1d6c332af472d48a.
Report an issue: GitHub.
Appendix: source
Thrown at src/EFCore.InMemory/Query/Internal/InMemoryQueryExpression.cs:446
NewArrayInit(
typeof(object),
source1SelectorExpressions.Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e))),
CurrentParameter));
source2.ServerQueryExpression = Call(
EnumerableMethods.Select.MakeGenericMethod(source2.ServerQueryExpression.Type.GetSequenceType(), typeof(ValueBuffer)),
source2.ServerQueryExpression,
Lambda(
New(
ValueBufferConstructor,
NewArrayInit(
typeof(object),
source2SelectorExpressions.Select(e => e.Type.IsValueType ? Convert(e, typeof(object)) : e))),
source2.CurrentParameter));
}
else
{
throw new InvalidOperationException(InMemoryStrings.SetOperationsNotAllowedAfterClientEvaluation);
}
ServerQueryExpression = Call(
setOperationMethodInfo.MakeGenericMethod(typeof(ValueBuffer)), ServerQueryExpression, source2.ServerQueryExpression);
}
/// <summary>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
/// the same compatibility standards as public APIs. It may be changed or removed without notice in
/// any release. You should only use it directly in your code with extreme caution and knowing that
/// doing so can result in application failures when updating to a new Entity Framework Core release.
/// </summary>
public virtual void ApplyDefaultIfEmpty()
{
if (_clientProjections.Count != 0)
{
throw new InvalidOperationException(InMemoryStrings.DefaultIfEmptyAppliedAfterProjection);
}View on GitHub (pinned to 3a2006ef56)