Skip to content

Commit 3fc9ac3

Browse files
kirankumarkolliCopilot Autofix powered by GitHub Advanced Security
andcommitted
Fix #5547: LINQ Dictionary.Any() generates correct SQL with OBJECTTOARRAY
Co-authored-by: Copilot Autofix powered by GitHub Advanced Security <copilot-autofix-noreply@github.com>
1 parent d6a69c8 commit 3fc9ac3

3 files changed

Lines changed: 98 additions & 1 deletion

File tree

Microsoft.Azure.Cosmos/src/Linq/ExpressionToSQL.cs

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1133,7 +1133,26 @@ private static Collection VisitMemberAccessCollectionExpression(Expression input
11331133
SqlScalarExpression body = ExpressionToSql.VisitNonSubqueryScalarExpression(inputExpression, context);
11341134
Type type = inputExpression.Type;
11351135

1136-
Collection collection = ExpressionToSql.ConvertToCollection(body);
1136+
Collection collection;
1137+
if (type.IsDictionary())
1138+
{
1139+
// For Dictionary types, wrap with OBJECTTOARRAY to convert the object to an iterable array
1140+
SqlScalarExpression objectToArrayCall = SqlFunctionCallScalarExpression.CreateBuiltin(
1141+
SqlFunctionCallScalarExpression.Names.ObjectToArray,
1142+
body);
1143+
1144+
// Create a subquery: SELECT VALUE OBJECTTOARRAY(...)
1145+
SqlSelectSpec selectSpec = SqlSelectValueSpec.Create(objectToArrayCall);
1146+
SqlSelectClause selectClause = SqlSelectClause.Create(selectSpec);
1147+
SqlQuery subquery = SqlQuery.Create(selectClause, fromClause: null, whereClause: null, groupByClause: null, orderByClause: null, offsetLimitClause: null);
1148+
SqlCollection subqueryCollection = SqlSubqueryCollection.Create(subquery);
1149+
collection = new Collection(subqueryCollection);
1150+
}
1151+
else
1152+
{
1153+
collection = ExpressionToSql.ConvertToCollection(body);
1154+
}
1155+
11371156
context.PushCollection(collection);
11381157
ParameterExpression parameter = context.GenerateFreshParameter(type, parameterName);
11391158
context.PushParameter(parameter, context.CurrentSubqueryBinding.ShouldBeOnNewQuery);

Microsoft.Azure.Cosmos/src/Linq/TypeSystem.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,22 @@ public static bool IsNullable(this Type type)
123123
return type.IsGenericType() && type.GetGenericTypeDefinition() == typeof(Nullable<>);
124124
}
125125

126+
public static bool IsDictionary(this Type type)
127+
{
128+
if (type.IsGenericType())
129+
{
130+
Type genericTypeDefinition = type.GetGenericTypeDefinition();
131+
if (genericTypeDefinition == typeof(Dictionary<,>) || genericTypeDefinition == typeof(IDictionary<,>))
132+
{
133+
return true;
134+
}
135+
}
136+
137+
return type.GetInterfaces().Any(interfaceType =>
138+
interfaceType.IsGenericType() &&
139+
interfaceType.GetGenericTypeDefinition() == typeof(IDictionary<,>));
140+
}
141+
126142
public static Type NullableUnderlyingType(this Type type)
127143
{
128144
if (type.IsNullable())
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
//------------------------------------------------------------
2+
// Copyright (c) Microsoft Corporation. All rights reserved.
3+
//------------------------------------------------------------
4+
5+
namespace Microsoft.Azure.Cosmos.Linq
6+
{
7+
using System;
8+
using System.Collections.Generic;
9+
using System.Linq;
10+
using Microsoft.VisualStudio.TestTools.UnitTesting;
11+
12+
/// <summary>
13+
/// Tests for GitHub Issue #5547:
14+
/// LINQ queries using .Any() on Dictionary&lt;string, object&gt; properties should generate SQL using OBJECTTOARRAY.
15+
/// </summary>
16+
[TestClass]
17+
public class LinqDictionaryQueryTests
18+
{
19+
/// <summary>
20+
/// Test that IsDictionary correctly identifies Dictionary types.
21+
/// </summary>
22+
[TestMethod]
23+
public void IsDictionary_ShouldReturnTrueForDictionaryTypes()
24+
{
25+
// Test Dictionary<,>
26+
Assert.IsTrue(typeof(Dictionary<string, object>).IsDictionary());
27+
Assert.IsTrue(typeof(Dictionary<int, string>).IsDictionary());
28+
29+
// Test IDictionary<,>
30+
Assert.IsTrue(typeof(IDictionary<string, object>).IsDictionary());
31+
Assert.IsTrue(typeof(IDictionary<int, string>).IsDictionary());
32+
33+
// Test types that implement IDictionary<,>
34+
Assert.IsTrue(typeof(SortedDictionary<string, object>).IsDictionary());
35+
}
36+
37+
/// <summary>
38+
/// Test that IsDictionary correctly rejects non-Dictionary types.
39+
/// </summary>
40+
[TestMethod]
41+
public void IsDictionary_ShouldReturnFalseForNonDictionaryTypes()
42+
{
43+
// Test arrays
44+
Assert.IsFalse(typeof(string[]).IsDictionary());
45+
Assert.IsFalse(typeof(int[]).IsDictionary());
46+
47+
// Test lists
48+
Assert.IsFalse(typeof(List<string>).IsDictionary());
49+
Assert.IsFalse(typeof(List<object>).IsDictionary());
50+
51+
// Test IEnumerable
52+
Assert.IsFalse(typeof(IEnumerable<string>).IsDictionary());
53+
54+
// Test primitives
55+
Assert.IsFalse(typeof(string).IsDictionary());
56+
Assert.IsFalse(typeof(int).IsDictionary());
57+
58+
// Test other collections
59+
Assert.IsFalse(typeof(HashSet<string>).IsDictionary());
60+
}
61+
}
62+
}

0 commit comments

Comments
 (0)