Skip to content
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 @@ -1551,6 +1551,7 @@ private sealed class SharedTypeEntityExpandingExpressionVisitor(
{
private readonly SqlAliasManager _sqlAliasManager = queryableTranslator._sqlAliasManager;
private SelectExpression _selectExpression = null!;
private bool _bindComplexProperties;

public Expression Expand(SelectExpression selectExpression, Expression lambdaBody)
{
Expand All @@ -1564,6 +1565,7 @@ protected override Expression VisitMember(MemberExpression memberExpression)
var innerExpression = Visit(memberExpression.Expression);

return TryExpand(innerExpression, MemberIdentity.Create(memberExpression.Member))
?? TryBindPropertyAccess(innerExpression, memberExpression.Member.Name)
?? memberExpression.Update(innerExpression);
}

Expand All @@ -1574,10 +1576,17 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
source = Visit(source);

return TryExpand(source, MemberIdentity.Create(navigationName))
?? TryBindPrimitiveCollection(source, navigationName)
?? methodCallExpression.Update(null!, new[] { source, methodCallExpression.Arguments[1] });
?? TryBindPropertyAccess(source, navigationName)
?? methodCallExpression.Update(null!, [source, methodCallExpression.Arguments[1]]);
}

// The following is a hack.
// In preprocessing, SubqueryMemberPushdownExpressionVisitor rewrites ElementAt(0).Int to Select(s => s.Int).ElementAt(0),
// as that's apparently needed for NavigationExpandingExpressionVisitor to work correctly;
// see https://github.com/dotnet/efcore/issues/30386#issuecomment-1452643142 for more context.
// Here, we pattern match the resulting Select(s => s.Int).ElementAt(0) over JsonQueryExpression to properly identify the
// indexing operation and translate it.
// Note that this happens for both owned and complex JSON-mapped types.
if (methodCallExpression.Method.IsGenericMethod
&& (methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.ElementAt
|| methodCallExpression.Method.GetGenericMethodDefinition() == QueryableMethods.ElementAtOrDefault))
Expand All @@ -1600,7 +1609,16 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp
source = maybeAsQueryableMethodCall.Arguments[0];
}

// This special visitor normally only handles owned access, and not complex properties; all complex property access
// is handled in RelationalSqlTranslatingExpressionVisitor as usual.
// However, indexing over complex JSON-mapped collection is mangled in preprocessing just like it is for owned
// entities (see comment above). So we very specifically handle complex properties as part of the pattern matching
// hack here, to make sure indexing works properly.
// #36335 tracks removing the preprocessing mangling, at which point this should no longer be needed
var parentBindComplexProperties = _bindComplexProperties;
_bindComplexProperties = true;
source = Visit(source);
_bindComplexProperties = parentBindComplexProperties;

if (source is JsonQueryExpression jsonQueryExpression)
{
Expand Down Expand Up @@ -1666,15 +1684,15 @@ static Expression PrepareFailedTranslationResult(
var result = source;
if (asQueryable != null)
{
result = asQueryable.Update(null, new[] { result });
result = asQueryable.Update(null, [result]);
}

if (select != null)
{
result = select.Update(null, new[] { result, select.Arguments[1] });
result = select.Update(null, [result, select.Arguments[1]]);
}

return elementAt.Update(null, new[] { result, elementAt.Arguments[1] });
return elementAt.Update(null, [result, elementAt.Arguments[1]]);
}

static bool IsValidSelectorForJsonArrayElementAccess(Expression expression, JsonQueryExpression baselineJsonQuery)
Expand Down Expand Up @@ -1907,7 +1925,7 @@ static TableExpressionBase FindRootTableExpressionForColumn(SelectExpression sel
}
}

private Expression? TryBindPrimitiveCollection(Expression? source, string memberName)
private Expression? TryBindPropertyAccess(Expression? source, string memberName)
{
while (source is IncludeExpression includeExpression)
{
Expand Down Expand Up @@ -1937,12 +1955,27 @@ static TableExpressionBase FindRootTableExpressionForColumn(SelectExpression sel
}

var property = type.FindProperty(memberName);
if (property?.IsPrimitiveCollection != true)
if (property?.IsPrimitiveCollection is true)
{
return null;
return source.CreateEFPropertyExpression(property);
}

// See comments on indexing-related hacks in VisitMethodCall above
if (_bindComplexProperties
&& type.FindComplexProperty(memberName) is IComplexProperty { IsCollection: true } complexProperty)
{
Check.DebugAssert(complexProperty.ComplexType.IsMappedToJson());

if (queryableTranslator._sqlTranslator.TryBindMember(
queryableTranslator._sqlTranslator.Visit(source), MemberIdentity.Create(memberName),
out var translatedExpression, out _)
&& translatedExpression is CollectionResultExpression { QueryExpression: JsonQueryExpression jsonQuery })
{
return jsonQuery;
}
}

return source.CreateEFPropertyExpression(property);
return null;
}

private sealed class AnnotationApplyingExpressionVisitor(IReadOnlyList<IAnnotation> annotations) : ExpressionVisitor
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,45 +120,39 @@ public override async Task Distinct_over_projected_filtered_nested_collection()

public override async Task Index_constant()
{
// Complex collection indexing currently fails because SubqueryMemberPushdownExpressionVisitor moves the Int member access to before the
// ElementAt (making a Select()), this interferes with our translation. See #36335.
await Assert.ThrowsAsync<EqualException>(() => base.Index_constant());
await base.Index_constant();

AssertSql(
"""
SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated]
FROM [RootEntity] AS [r]
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0]') AS int) = 8
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0].Int') AS int) = 8
""");
}

public override async Task Index_parameter()
{
// Complex collection indexing currently fails because SubqueryMemberPushdownExpressionVisitor moves the Int member access to before the
// ElementAt (making a Select()), this interferes with our translation. See #36335.
await Assert.ThrowsAsync<EqualException>(() => base.Index_parameter());
await base.Index_parameter();

AssertSql(
"""
@i='?' (DbType = Int32)

SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated]
FROM [RootEntity] AS [r]
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST(@i AS nvarchar(max)) + ']') AS int) = 8
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST(@i AS nvarchar(max)) + '].Int') AS int) = 8
""");
}

public override async Task Index_column()
{
// Complex collection indexing currently fails because SubqueryMemberPushdownExpressionVisitor moves the Int member access to before the
// ElementAt (making a Select()), this interferes with our translation. See #36335.
await Assert.ThrowsAsync<EqualException>(() => base.Index_column());
await base.Index_column();

AssertSql(
"""
SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated]
FROM [RootEntity] AS [r]
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST([r].[Id] - 1 AS nvarchar(max)) + ']') AS int) = 8
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[' + CAST([r].[Id] - 1 AS nvarchar(max)) + '].Int') AS int) = 8
""");
}

Expand All @@ -170,7 +164,7 @@ public override async Task Index_out_of_bounds()
"""
SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated]
FROM [RootEntity] AS [r]
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[9999]') AS int) = 8
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[9999].Int') AS int) = 8
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,8 @@ public override async Task Distinct_over_projected_filtered_nested_collection()

#endregion Distinct

#region Index

public override async Task Index_constant()
{
await base.Index_constant();
Expand Down Expand Up @@ -133,6 +135,8 @@ public override async Task Index_out_of_bounds()
AssertSql();
}

#endregion Index

public override async Task Select_within_Select_within_Select_with_aggregates()
{
await base.Select_within_Select_within_Select_with_aggregates();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,18 +40,6 @@ [String] nvarchar(max) '$.String'
""");
}

public override async Task Index_constant()
{
await base.Index_constant();

AssertSql(
"""
SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated]
FROM [RootEntity] AS [r]
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0].Int') AS int) = 8
""");
}

public override async Task OrderBy_ElementAt()
{
await base.OrderBy_ElementAt();
Expand Down Expand Up @@ -146,6 +134,18 @@ public override async Task Distinct_over_projected_filtered_nested_collection()

#region Index

public override async Task Index_constant()
{
await base.Index_constant();

AssertSql(
"""
SELECT [r].[Id], [r].[Name], [r].[OptionalRelated], [r].[RelatedCollection], [r].[RequiredRelated]
FROM [RootEntity] AS [r]
WHERE CAST(JSON_VALUE([r].[RelatedCollection], '$[0].Int') AS int) = 8
""");
}

public override async Task Index_parameter()
{
await base.Index_parameter();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@ ORDER BY [r0].[Id]
""");
}


#region Distinct

public override async Task Distinct()
Expand Down Expand Up @@ -181,6 +180,8 @@ public override async Task Distinct_over_projected_filtered_nested_collection()

#endregion Distinct

#region Index

public override async Task Index_constant()
{
await base.Index_constant();
Expand Down Expand Up @@ -315,6 +316,8 @@ ORDER BY (SELECT 1)
""");
}

#endregion Index

public override async Task Select_within_Select_within_Select_with_aggregates()
{
await base.Select_within_Select_within_Select_with_aggregates();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.Data.Sqlite;

namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexJson;

public class ComplexJsonCollectionSqliteTest(ComplexJsonSqliteFixture fixture, ITestOutputHelper testOutputHelper)
: ComplexJsonCollectionRelationalTestBase<ComplexJsonSqliteFixture>(fixture, testOutputHelper)
{
// TODO: #36296
public override Task Index_column()
=> Assert.ThrowsAsync<SqliteException>(() => base.Index_column());
}
: ComplexJsonCollectionRelationalTestBase<ComplexJsonSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexJson;

public class ComplexJsonMiscellaneousSqliteTest(ComplexJsonSqliteFixture fixture, ITestOutputHelper testOutputHelper)
: ComplexJsonMiscellaneousRelationalTestBase<ComplexJsonSqliteFixture>(fixture, testOutputHelper)
{
}
: ComplexJsonMiscellaneousRelationalTestBase<ComplexJsonSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,4 @@
namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexJson;

public class ComplexJsonSetOperationsSqliteTest(ComplexJsonSqliteFixture fixture, ITestOutputHelper testOutputHelper)
: ComplexJsonSetOperationsRelationalTestBase<ComplexJsonSqliteFixture>(fixture, testOutputHelper)
{

}
: ComplexJsonSetOperationsRelationalTestBase<ComplexJsonSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.ComplexTableSplittin
public class ComplexTableSplittingMiscellaneousSqliteTest(
ComplexTableSplittingSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
: ComplexTableSplittingMiscellaneousRelationalTestBase<ComplexTableSplittingSqliteFixture>(fixture, testOutputHelper)
{
}
: ComplexTableSplittingMiscellaneousRelationalTestBase<ComplexTableSplittingSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
namespace Microsoft.EntityFrameworkCore.Query.Relationships.Navigations;

public class NavigationsCollectionSqliteTest(NavigationsSqliteFixture fixture, ITestOutputHelper testOutputHelper)
: NavigationsCollectionRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper)
{
}
: NavigationsCollectionRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
namespace Microsoft.EntityFrameworkCore.Query.Relationships.Navigations;

public class NavigationsIncludeSqliteTest(NavigationsSqliteFixture fixture, ITestOutputHelper testOutputHelper)
: NavigationsIncludeRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper)
{
}
: NavigationsIncludeRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.Navigations;
public class NavigationsMiscellaneousSqliteTest(
NavigationsSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
: NavigationsMiscellaneousRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper)
{
}
: NavigationsMiscellaneousRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
namespace Microsoft.EntityFrameworkCore.Query.Relationships.Navigations;

public class NavigationsSetOperationsSqliteTest(NavigationsSqliteFixture fixture, ITestOutputHelper testOutputHelper)
: NavigationsSetOperationsRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper)
{
}
: NavigationsSetOperationsRelationalTestBase<NavigationsSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,4 @@
namespace Microsoft.EntityFrameworkCore.Query.Relationships.OwnedJson;

public class OwnedJsonMiscellaneousSqliteTest(OwnedJsonSqliteFixture fixture, ITestOutputHelper testOutputHelper)
: OwnedJsonMiscellaneousRelationalTestBase<OwnedJsonSqliteFixture>(fixture, testOutputHelper)
{
}
: OwnedJsonMiscellaneousRelationalTestBase<OwnedJsonSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.OwnedNavigations;
public class OwnedNavigationsMiscellaneousSqliteTest(
OwnedNavigationsSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
: OwnedNavigationsMiscellaneousRelationalTestBase<OwnedNavigationsSqliteFixture>(fixture, testOutputHelper)
{
}
: OwnedNavigationsMiscellaneousRelationalTestBase<OwnedNavigationsSqliteFixture>(fixture, testOutputHelper);
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,4 @@ namespace Microsoft.EntityFrameworkCore.Query.Relationships.OwnedTableSplitting;
public class OwnedTableSplittingMiscellaneousSqliteTest(
OwnedTableSplittingSqliteFixture fixture,
ITestOutputHelper testOutputHelper)
: OwnedTableSplittingMiscellaneousRelationalTestBase<OwnedTableSplittingSqliteFixture>(fixture, testOutputHelper)
{
}
: OwnedTableSplittingMiscellaneousRelationalTestBase<OwnedTableSplittingSqliteFixture>(fixture, testOutputHelper);