dotnet / dotnet/efcore

Support translating coalesce over rows (two subquery results)

Open
#36,362 4 comments 0 reactions 0 assignees View on GitHub
area-query customer-reported
Dominant language
C#
Stars
14.8k
Forks
3.4k
PR merge metrics
PR metrics pending

Description

### Bug description

Hello.

Consider there is a `Parent` entity containing a collection property of some related `Child` entities:
```c#
public class Parent
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int Id { get; set; }

public virtual ICollection Children { get; set; }
}
```
```c#
public class Child
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int Id { get; set; }

public virtual Parent Parent { get; set; }

[ForeignKey(nameof(Parent))]
public int ParentId { get; set; }

public int ConditionValue { get; set; }

public int SomeOtherValue { get; set; }
}
```
Now I want to make a query:
* That uses `.Select(...)` projection to select a parent and an one child using some condition over `Children` collection, but when there are no such child, then fallback to some other condition over `Children` collection (main condition and fallback condition are separated by the null coalescing operator `??`)
* That uses `.Where(...)` filter later by some property of a projected child

_See "Your code" section for a query example_

But when I try to execute this query, it would fail with a `System.InvalidOperationException`:
```
The LINQ expression 'DbSet()
.Where(p => (DbSet()
.Where(c => EF.Property(p, "Id") != null && object.Equals(
objA: (object)EF.Property(p, "Id"),
objB: (object)EF.Property(c, "ParentId")))
.OrderBy(c => c.ConditionValue)
.Where(c => c.ConditionValue >= 4)
.FirstOrDefault() ?? DbSet()
.Where(c0 => EF.Property(p, "Id") != null && object.Equals(
objA: (object)EF.Property(p, "Id"),
objB: (object)EF.Property(c0, "ParentId")))
.OrderBy(c0 => c0.ConditionValue)
.FirstOrDefault()).SomeOtherValue <= 2)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.
```

However, if I:
* remove null coalescing operator `??` and fallback collection selector,

OR

* keep null coalescing, but remove `.Where(...)` condition part

this query would be executed successfully.

Repo: https://github.com/byxworm/EFCoreNullCoalescingWithConditionBug/blob/master/Tests/NullCoalescingTests.cs

I expected, that if some of the `IQueryable` chain can be translated, then all later chained methods to this `IQueryable` chain could be translated as well without any need to modify previous `IQueryable` chain calls.

Background: currently I investigate a possibility to migrate from EF 6 to EF Core in one of our apps, and I have a lot of queries with those null coalescing collection selectors (usually they are ordered by date and then we try to find "actual on the current date or first in the future" related entity to perform some additional conditional check later; sometimes there could be even several sequential null coalescing operators in queries). All of these queries are currently working fine in old EF 6, somehow: https://github.com/byxworm/EFCoreNullCoalescingWithConditionBug/blob/ef6/Tests/NullCoalescingTests.cs

### Your code

```csharp
await using var context = new TestDbContext();
var results = await context
.Parents
.Select(
parent => new QueryResult
{
Parent = parent,
Child =
parent
.Children
.OrderBy(child => child.ConditionValue)
.FirstOrDefault(child => child.ConditionValue >= 4)
?? parent
.Children
.OrderBy(child => child.ConditionValue)
.FirstOrDefault()
})
.Where(data => data.Child.SomeOtherValue <= 2)
.ToArrayAsync();
```

### Stack traces

```text
QueryableMethodTranslatingExpressionVisitor.Translate(Expression expression)
QueryCompilationContext.CreateQueryExecutorExpression[TResult](Expression query)
QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
Database.CompileQuery[TResult](Expression query, Boolean async)
QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
<>c__DisplayClass11_0`1.b__0()
CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
QueryCompiler.ExecuteCore[TResult](Expression query, Boolean async, CancellationToken cancellationToken)
QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
EntityFrameworkQueryableExtensions.ToArrayAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
NullCoalescingTests.ConditionWithNullCoalescing() line 72
NullCoalescingTests.ConditionWithNullCoalescing() line 90
...
```

### Verbose output

```text

```

### EF Core version

9.0.7

### Database provider

Microsoft.EntityFrameworkCore.SqlServer

### Target framework

.NET 8.0

### Operating system

Windows 10

### IDE

Visual Studio Professional 2022 17.13.6

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.