Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix to #35007 - EF Core fails to translate LINQ query to SQL if JOIN contains multiple columns #35019

Merged
merged 1 commit into from
Nov 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.Collections.ObjectModel;
using Microsoft.EntityFrameworkCore.Internal;
using ExpressionExtensions = Microsoft.EntityFrameworkCore.Infrastructure.ExpressionExtensions;

Expand Down Expand Up @@ -753,17 +754,52 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
innerSource);
}

var correlationPredicate = ReplacingExpressionVisitor.Replace(
outerKeySelector.Parameters[0],
resultSelector.Parameters[0],
Expression.AndAlso(
ExpressionExtensions.CreateEqualsExpression(
outerKeySelector.Body,
Expression.Constant(null),
negated: true),
ExpressionExtensions.CreateEqualsExpression(
outerKeySelector.Body,
innerKeySelector.Body)));
Expression correlationPredicate;
if (outerKeySelector.Body is NewExpression { Arguments: ReadOnlyCollection<Expression> outerArguments }
&& innerKeySelector.Body is NewExpression { Arguments: ReadOnlyCollection<Expression> innerArguments }
&& outerArguments.Count == innerArguments.Count
&& outerArguments.Count > 0)
{
Expression? outerNotEqualsNull = null;
Expression? outerEqualsInner = null;
for (var i = 0; i < outerArguments.Count; i++)
{
var outerArgumentNotEqualsNull = ExpressionExtensions.CreateEqualsExpression(outerArguments[i], Expression.Constant(null), negated: true);
var outerArgumentEqualsInnerArgument = ExpressionExtensions.CreateEqualsExpression(outerArguments[i], innerArguments[i]);

if (i == 0)
{
outerNotEqualsNull = outerArgumentNotEqualsNull;
outerEqualsInner = outerArgumentEqualsInnerArgument;
}
else
{
outerNotEqualsNull = Expression.AndAlso(outerNotEqualsNull!, outerArgumentNotEqualsNull);
outerEqualsInner = Expression.AndAlso(outerEqualsInner!, outerArgumentEqualsInnerArgument);
}
}

correlationPredicate = ReplacingExpressionVisitor.Replace(
outerKeySelector.Parameters[0],
resultSelector.Parameters[0],
Expression.AndAlso(
outerNotEqualsNull!,
outerEqualsInner!));
}
else
{
correlationPredicate = ReplacingExpressionVisitor.Replace(
outerKeySelector.Parameters[0],
resultSelector.Parameters[0],
Expression.AndAlso(
ExpressionExtensions.CreateEqualsExpression(
outerKeySelector.Body,
Expression.Constant(null),
negated: true),
ExpressionExtensions.CreateEqualsExpression(
outerKeySelector.Body,
innerKeySelector.Body)));
}

innerSource = Expression.Call(
QueryableMethods.Where.MakeGenericMethod(genericArguments[1]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,55 @@ join o in ss.Set<Order>().OrderBy(o => o.OrderID).Take(100) on c.CustomerID equa
from o in lo.Where(x => x.CustomerID.StartsWith("A"))
select new { c.CustomerID, o.OrderID });

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupJoin_aggregate_anonymous_key_selectors(bool async)
=> AssertQuery(
async,
ss => ss.Set<Customer>().GroupJoin(
ss.Set<Order>(),
x => new { x.CustomerID, x.City },
x => new { x.CustomerID, City = "London" },
(c, g) => new { c.CustomerID, Sum = g.Sum(x => x.CustomerID.Length) }),
elementSorter: e => e.CustomerID);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupJoin_aggregate_anonymous_key_selectors2(bool async)
=> AssertQuery(
async,
ss => ss.Set<Customer>().GroupJoin(
ss.Set<Order>(),
x => new { x.CustomerID, Year = 1996 },
x => new { x.CustomerID, x.OrderDate.Value.Year },
(c, g) => new { c.CustomerID, Sum = g.Sum(x => x.CustomerID.Length) }),
elementSorter: e => e.CustomerID);

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupJoin_aggregate_anonymous_key_selectors_one_argument(bool async)
=> AssertQuery(
async,
ss => ss.Set<Customer>().GroupJoin(
ss.Set<Order>(),
x => new { x.CustomerID },
x => new { x.CustomerID },
(c, g) => new { c.CustomerID, Sum = g.Sum(x => x.CustomerID.Length) }),
elementSorter: e => e.CustomerID);

[ConditionalTheory(Skip = "issue 35028")]
[MemberData(nameof(IsAsyncData))]
public virtual Task GroupJoin_aggregate_nested_anonymous_key_selectors(bool async)
=> AssertTranslationFailed(
() => AssertQuery(
async,
ss => ss.Set<Customer>().GroupJoin(
ss.Set<Order>(),
x => new { x.CustomerID, Nested = new { x.City, Year = 1996 } },
x => new { x.CustomerID, Nested = new { City = "London", x.OrderDate.Value.Year } },
(c, g) => new { c.CustomerID, Sum = g.Sum(x => x.CustomerID.Length) }),
elementSorter: e => e.CustomerID));

[ConditionalTheory]
[MemberData(nameof(IsAsyncData))]
public virtual Task Inner_join_with_tautology_predicate_converts_to_cross_join(bool async)
Expand Down Expand Up @@ -909,4 +958,21 @@ join o in ss.Set<Order>().Include(o => o.OrderDetails)
on c.CustomerID equals o.CustomerID into g
from o in g.DefaultIfEmpty()
select new { a = o != null ? o.OrderID : -1 });

[ConditionalTheory(Skip = "issue #35028")]
[MemberData(nameof(IsAsyncData))]
public virtual Task Join_with_key_selectors_being_nested_anonymous_objects(bool async)
=> AssertQuery(
async,
ss => ss.Set<Customer>().Order().Take(10).Join(
ss.Set<Order>(),
x => new { x.CustomerID, Nested = new { x.City, Year = 1996 } },
x => new { x.CustomerID, Nested = new { City = "London", x.OrderDate.Value.Year } },
(c, o) => new { c, o }),
elementSorter: e => (e.c.CustomerID, e.o.OrderID ),
elementAsserter: (e, a) =>
{
AssertEqual(e.c, a.c);
AssertEqual(e.o, a.o);
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -567,6 +567,55 @@ WHERE [o0].[CustomerID] LIKE N'A%'
""");
}

public override async Task GroupJoin_aggregate_anonymous_key_selectors(bool async)
{
await base.GroupJoin_aggregate_anonymous_key_selectors(async);

AssertSql(
"""
SELECT [c].[CustomerID], (
SELECT COALESCE(SUM(CAST(LEN([o].[CustomerID]) AS int)), 0)
FROM [Orders] AS [o]
WHERE [c].[City] IS NOT NULL AND [c].[CustomerID] = [o].[CustomerID] AND [c].[City] = N'London') AS [Sum]
FROM [Customers] AS [c]
""");
}

public override async Task GroupJoin_aggregate_anonymous_key_selectors2(bool async)
{
await base.GroupJoin_aggregate_anonymous_key_selectors2(async);

AssertSql(
"""
SELECT [c].[CustomerID], (
SELECT COALESCE(SUM(CAST(LEN([o].[CustomerID]) AS int)), 0)
FROM [Orders] AS [o]
WHERE [c].[CustomerID] = [o].[CustomerID] AND 1996 = DATEPART(year, [o].[OrderDate])) AS [Sum]
FROM [Customers] AS [c]
""");
}

public override async Task GroupJoin_aggregate_anonymous_key_selectors_one_argument(bool async)
{
await base.GroupJoin_aggregate_anonymous_key_selectors_one_argument(async);

AssertSql(
"""
SELECT [c].[CustomerID], (
SELECT COALESCE(SUM(CAST(LEN([o].[CustomerID]) AS int)), 0)
FROM [Orders] AS [o]
WHERE [c].[CustomerID] = [o].[CustomerID]) AS [Sum]
FROM [Customers] AS [c]
""");
}

public override async Task GroupJoin_aggregate_nested_anonymous_key_selectors(bool async)
{
await base.GroupJoin_aggregate_nested_anonymous_key_selectors(async);

AssertSql();
}

public override async Task Inner_join_with_tautology_predicate_converts_to_cross_join(bool async)
{
await base.Inner_join_with_tautology_predicate_converts_to_cross_join(async);
Expand Down Expand Up @@ -992,6 +1041,13 @@ ORDER BY [o].[OrderID]
""");
}

public override async Task Join_with_key_selectors_being_nested_anonymous_objects(bool async)
{
await base.Join_with_key_selectors_being_nested_anonymous_objects(async);

AssertSql();
}

private void AssertSql(params string[] expected)
=> Fixture.TestSqlLoggerFactory.AssertBaseline(expected);

Expand Down