Skip to content

Commit 2b22b71

Browse files
committed
Make Contains work again over hierarchyid
Fixes #31912
1 parent c7cd3aa commit 2b22b71

File tree

3 files changed

+59
-9
lines changed

3 files changed

+59
-9
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;
5+
6+
// ReSharper disable once CheckNamespace
7+
namespace Microsoft.EntityFrameworkCore;
8+
9+
/// <summary>
10+
/// Type extension methods for <see cref="TableExpressionBase" /> and related types.
11+
/// </summary>
12+
public static class TableExpressionExtensions
13+
{
14+
/// <summary>
15+
/// If the given <paramref name="table" /> is a <see cref="JoinExpressionBase" />, returns the table it joins to. Otherwise, returns
16+
/// <paramref name="table" />.
17+
/// </summary>
18+
public static TableExpressionBase UnwrapJoin(this TableExpressionBase table)
19+
=> table is JoinExpressionBase join ? join.Table : table;
20+
}

src/EFCore.SqlServer/Query/Internal/SqlServerJsonPostprocessor.cs

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99
namespace Microsoft.EntityFrameworkCore.SqlServer.Query.Internal;
1010

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

82-
if ((table is SqlServerOpenJsonExpression { ColumnInfos: not null }
83-
or JoinExpressionBase { Table: SqlServerOpenJsonExpression { ColumnInfos: not null } })
84-
&& selectExpression.Orderings.Select(o => o.Expression)
85-
.Concat(selectExpression.Projection.Select(p => p.Expression))
86-
.Any(x => IsKeyColumn(x, table)))
84+
if (table.UnwrapJoin() is SqlServerOpenJsonExpression { ColumnInfos: not null } openJsonExpression
85+
&& (
86+
// Condition 1: an ordering still refers to the OPENJSON's [key] column - ordering needs to be preserved.
87+
selectExpression.Orderings.Select(o => o.Expression)
88+
.Concat(selectExpression.Projection.Select(p => p.Expression))
89+
.Any(x => IsKeyColumn(x, table))
90+
||
91+
// Condition 2: a column type in the WITH clause is a SQL Server "CLR type" (e.g. hierarchy id).
92+
openJsonExpression.ColumnInfos.Any(c => c.TypeMapping.StoreType is "hierarchyid")))
8793
{
8894
// Remove the WITH clause from the OPENJSON expression
89-
var openJsonExpression = (SqlServerOpenJsonExpression)((table as JoinExpressionBase)?.Table ?? table);
9095
var newOpenJsonExpression = openJsonExpression.Update(
9196
openJsonExpression.JsonExpression,
9297
openJsonExpression.Path,

test/EFCore.SqlServer.HierarchyId.Tests/QueryTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -356,6 +356,31 @@ public void Parse_can_translate()
356356
Assert.Equal(new[] { HierarchyId.Parse("/") }, results);
357357
}
358358

359+
[ConditionalFact]
360+
public void Contains_with_parameter_list_can_translate()
361+
{
362+
var ids = new[] { HierarchyId.Parse("/1/1/7/"), HierarchyId.Parse("/1/1/99/") };
363+
var result = (from p in _db.Patriarchy
364+
where ids.Contains(p.Id)
365+
select p.Name).Single();
366+
367+
Assert.Equal(
368+
"""
369+
@__ids_0='?' (Size = 4000)
370+
371+
SELECT TOP(2) [p].[Name]
372+
FROM [Patriarchy] AS [p]
373+
WHERE [p].[Id] IN (
374+
SELECT CAST([i].[value] AS hierarchyid) AS [value]
375+
FROM OPENJSON(@__ids_0) AS [i]
376+
)
377+
""",
378+
_db.Sql,
379+
ignoreLineEndingDifferences: true);
380+
381+
Assert.Equal("Dan", result);
382+
}
383+
359384
public void Dispose()
360385
=> _db.Dispose();
361386

0 commit comments

Comments
 (0)