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
20 changes: 20 additions & 0 deletions src/EFCore.Relational/Extensions/TableExpressionExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

// ReSharper disable once CheckNamespace
namespace Microsoft.EntityFrameworkCore;

/// <summary>
/// Type extension methods for <see cref="TableExpressionBase" /> and related types.
/// </summary>
public static class TableExpressionExtensions
{
/// <summary>
/// If the given <paramref name="table" /> is a <see cref="JoinExpressionBase" />, returns the table it joins to. Otherwise, returns
/// <paramref name="table" />.
/// </summary>
public static TableExpressionBase UnwrapJoin(this TableExpressionBase table)
=> table is JoinExpressionBase join ? join.Table : table;
}
23 changes: 14 additions & 9 deletions src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,11 @@
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;

/// <summary>
/// Converts <see cref="SqlServerOpenJsonExpression" /> expressions with WITH (the default) to OPENJSON without WITH when an
/// ordering still exists on the [key] column, i.e. when the ordering of the original JSON array needs to be preserved
/// (e.g. limit/offset).
/// Converts <see cref="SqlServerOpenJsonExpression" /> expressions with WITH (the default) to OPENJSON without WITH under the following
/// conditions:
/// * When an ordering still exists on the [key] column, i.e. when the ordering of the original JSON array needs to be preserved
/// (e.g. limit/offset).
/// * When the column type in the WITH clause is a SQL Server "CLR type" - these are incompatible with WITH (e.g. hierarchy id).
/// </summary>
/// <remarks>
/// This is an internal API that supports the Entity Framework Core infrastructure and not subject to
Expand Down Expand Up @@ -79,14 +81,17 @@ public virtual Expression Process(Expression expression)
{
var table = selectExpression.Tables[i];

if ((table is SqlServerOpenJsonExpression { ColumnInfos: not null }
or JoinExpressionBase { Table: SqlServerOpenJsonExpression { ColumnInfos: not null } })
&& selectExpression.Orderings.Select(o => o.Expression)
.Concat(selectExpression.Projection.Select(p => p.Expression))
.Any(x => IsKeyColumn(x, table)))
if (table.UnwrapJoin() is SqlServerOpenJsonExpression { ColumnInfos: not null } openJsonExpression
&& (
// Condition 1: an ordering still refers to the OPENJSON's [key] column - ordering needs to be preserved.
selectExpression.Orderings.Select(o => o.Expression)
.Concat(selectExpression.Projection.Select(p => p.Expression))
.Any(x => IsKeyColumn(x, table))
||
// Condition 2: a column type in the WITH clause is a SQL Server "CLR type" (e.g. hierarchy id).
openJsonExpression.ColumnInfos.Any(c => c.TypeMapping.StoreType is "hierarchyid")))
{
// Remove the WITH clause from the OPENJSON expression
var openJsonExpression = (SqlServerOpenJsonExpression)((table as JoinExpressionBase)?.Table ?? table);
var newOpenJsonExpression = openJsonExpression.Update(
openJsonExpression.JsonExpression,
openJsonExpression.Path,
Expand Down
25 changes: 25 additions & 0 deletions test/EFCore.SqlServer.HierarchyId.Tests/QueryTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,31 @@ public void Parse_can_translate()
Assert.Equal(new[] { HierarchyId.Parse("/") }, results);
}

[ConditionalFact]
public void Contains_with_parameter_list_can_translate()
{
var ids = new[] { HierarchyId.Parse("/1/1/7/"), HierarchyId.Parse("/1/1/99/") };
var result = (from p in _db.Patriarchy
where ids.Contains(p.Id)
select p.Name).Single();

Assert.Equal(
"""
@__ids_0='?' (Size = 4000)

SELECT TOP(2) [p].[Name]
FROM [Patriarchy] AS [p]
WHERE [p].[Id] IN (
SELECT CAST([i].[value] AS hierarchyid) AS [value]
FROM OPENJSON(@__ids_0) AS [i]
)
""",
_db.Sql,
ignoreLineEndingDifferences: true);

Assert.Equal("Dan", result);
}

public void Dispose()
=> _db.Dispose();

Expand Down