dotnet / dotnet/efcore

NewExpression constructor translations (e.g. new DateTimeOffset) don't work in projections (Select)

Open
#38,083 0 comments 0 reactions 0 assignees View on GitHub
area-query
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

## Summary

Constructor translations in the SQL translating expression visitor (e.g. `new DateTimeOffset(dateTime, offset)` → `TODATETIMEOFFSET(datetime, offset)`) do not work inside `Select()` projections. They only work in `Where()`, `OrderBy()`, `GroupBy()`, etc.

This is because `RelationalProjectionBindingExpressionVisitor.Visit()` routes `NewExpression` directly to `base.Visit()` → `VisitNew()`, which performs client-side DTO/anonymous type construction, rather than routing it through `TranslateProjection()` which would invoke the SQL translator's `VisitNew` override.

## Minimal repro

```csharp
// This WORKS (Where):
var results = await ctx.BasicTypes
.Where(b => new DateTimeOffset(b.DateTime, new TimeSpan(2, 0, 0)) == someValue)
.ToListAsync();

// This DOES NOT WORK (Select) - the constructor is evaluated client-side instead of being translated:
var results = await ctx.BasicTypes
.Select(b => new DateTimeOffset(b.DateTime, new TimeSpan(2, 0, 0)))
.ToListAsync();
```

## Details

In `RelationalProjectionBindingExpressionVisitor.Visit()`, `NewExpression` is always routed to `base.Visit()` which calls `VisitNew()` for client-side construction:

```csharp
case NewExpression or MemberInitExpression or StructuralTypeShaperExpression or IncludeExpression:
return base.Visit(expression);
```

To support constructor translation in projections, `NewExpression` in index-based binding mode needs to be routed through `TranslateProjection()` (similar to how other expressions are handled). However, this must be done carefully to avoid breaking existing scenarios like `new List { ... }` collection initializers, which use `ListInitExpression` containing a `NewExpression`.

A naive fix of routing all `NewExpression` through `TranslateProjection` in index-based binding mode breaks tests like `GearsOfWarQuerySqlServerTest.Optional_navigation_type_compensation_works_with_list_initializers`.

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.