Support translating coalesce over rows (two subquery results)
- 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
Assessment
This issue has not been assessed yet.