Skip to content

Commit 61d2c77

Browse files
committed
CSHARP-2509: Support Dictionary.ContainsValue in LINQ queries.
1 parent fbcf93a commit 61d2c77

File tree

6 files changed

+426
-0
lines changed

6 files changed

+426
-0
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,6 +575,11 @@ public static AstExpression NullaryWindowExpression(AstNullaryWindowOperator @op
575575
return new AstNullaryWindowExpression(@operator, window);
576576
}
577577

578+
public static AstExpression ObjectToArray(AstExpression arg)
579+
{
580+
return new AstUnaryExpression(AstUnaryOperator.ObjectToArray, arg);
581+
}
582+
578583
public static AstExpression Or(params AstExpression[] args)
579584
{
580585
Ensure.IsNotNull(args, nameof(args));

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodCallExpressionToAggregationExpressionTranslator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ public static AggregationExpression Translate(TranslationContext context, Method
3737
case "Concat": return ConcatMethodToAggregationExpressionTranslator.Translate(context, expression);
3838
case "Contains": return ContainsMethodToAggregationExpressionTranslator.Translate(context, expression);
3939
case "ContainsKey": return ContainsKeyMethodToAggregationExpressionTranslator.Translate(context, expression);
40+
case "ContainsValue": return ContainsValueMethodToAggregationExpressionTranslator.Translate(context, expression);
4041
case "CovariancePopulation": return CovariancePopulationMethodToAggregationExpressionTranslator.Translate(context, expression);
4142
case "CovarianceSample": return CovarianceSampleMethodToAggregationExpressionTranslator.Translate(context, expression);
4243
case "Create": return CreateMethodToAggregationExpressionTranslator.Translate(context, expression);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Linq.Expressions;
17+
using System.Reflection;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Bson.Serialization.Options;
20+
using MongoDB.Bson.Serialization.Serializers;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
22+
23+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
24+
{
25+
internal static class ContainsValueMethodToAggregationExpressionTranslator
26+
{
27+
// public methods
28+
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
29+
{
30+
var method = expression.Method;
31+
var arguments = expression.Arguments;
32+
33+
if (IsContainsValueMethod(method))
34+
{
35+
var dictionaryExpression = expression.Object;
36+
var valueExpression = arguments[0];
37+
38+
var dictionaryTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, dictionaryExpression);
39+
var dictionarySerializer = GetDictionarySerializer(expression, dictionaryTranslation);
40+
var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
41+
42+
var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
43+
var (valueBinding, valueAst) = AstExpression.UseVarIfNotSimple("value", valueTranslation.Ast);
44+
45+
AstExpression ast;
46+
switch (dictionaryRepresentation)
47+
{
48+
case DictionaryRepresentation.Document:
49+
ast = AstExpression.Let(
50+
var: valueBinding,
51+
@in: AstExpression.Reduce(
52+
input: AstExpression.ObjectToArray(dictionaryTranslation.Ast),
53+
initialValue: false,
54+
@in: AstExpression.Cond(
55+
@if: AstExpression.Var("value"),
56+
@then: true,
57+
@else: AstExpression.Eq(AstExpression.GetField(AstExpression.Var("this"), "v"), valueAst))));
58+
break;
59+
60+
case DictionaryRepresentation.ArrayOfArrays:
61+
ast = AstExpression.Let(
62+
var: valueBinding,
63+
@in: AstExpression.Reduce(
64+
input: dictionaryTranslation.Ast,
65+
initialValue: false,
66+
@in: AstExpression.Cond(
67+
@if: AstExpression.Var("value"),
68+
@then: true,
69+
@else: AstExpression.Eq(AstExpression.ArrayElemAt(AstExpression.Var("this"), 1), valueAst))));
70+
break;
71+
72+
case DictionaryRepresentation.ArrayOfDocuments:
73+
ast = AstExpression.Let(
74+
var: valueBinding,
75+
@in: AstExpression.Reduce(
76+
input: dictionaryTranslation.Ast,
77+
initialValue: false,
78+
@in: AstExpression.Cond(
79+
@if: AstExpression.Var("value"),
80+
@then: true,
81+
@else: AstExpression.Eq(AstExpression.GetField(AstExpression.Var("this"), "v"), valueAst))));
82+
break;
83+
84+
default:
85+
throw new ExpressionNotSupportedException(expression, because: $"ContainsValue is not supported when DictionaryRepresentation is: {dictionaryRepresentation}");
86+
}
87+
88+
return new AggregationExpression(expression, ast, BooleanSerializer.Instance);
89+
}
90+
91+
throw new ExpressionNotSupportedException(expression);
92+
}
93+
94+
private static IBsonDictionarySerializer GetDictionarySerializer(Expression expression, AggregationExpression dictionaryTranslation)
95+
{
96+
if (dictionaryTranslation.Serializer is IBsonDictionarySerializer dictionarySerializer)
97+
{
98+
return dictionarySerializer;
99+
}
100+
101+
throw new ExpressionNotSupportedException(expression, because: $"class {dictionaryTranslation.Serializer.GetType().FullName} does not implement the IBsonDictionarySerializer interface");
102+
}
103+
104+
private static bool IsContainsValueMethod(MethodInfo method)
105+
{
106+
return
107+
!method.IsStatic &&
108+
method.IsPublic &&
109+
method.ReturnType == typeof(bool) &&
110+
method.Name == "ContainsValue" &&
111+
method.GetParameters() is var parameters &&
112+
parameters.Length == 1;
113+
}
114+
}
115+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System.Linq.Expressions;
17+
using System.Reflection;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Bson.Serialization.Options;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators;
23+
24+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.MethodTranslators
25+
{
26+
internal static class ContainsValueMethodToFilterTranslator
27+
{
28+
public static AstFilter Translate(TranslationContext context, MethodCallExpression expression)
29+
{
30+
var method = expression.Method;
31+
var arguments = expression.Arguments;
32+
33+
if (IsContainsValueMethod(method))
34+
{
35+
var dictionaryExpression = expression.Object;
36+
var valueExpression = arguments[0];
37+
38+
var dictionaryField = ExpressionToFilterFieldTranslator.Translate(context, dictionaryExpression);
39+
var dictionarySerializer = GetDictionarySerializer(expression, dictionaryField);
40+
var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
41+
var valueSerializer = dictionarySerializer.ValueSerializer;
42+
43+
if (valueExpression is ConstantExpression constantValueExpression)
44+
{
45+
var valueField = AstFilter.Field("v", valueSerializer);
46+
var value = constantValueExpression.Value;
47+
var serializedValue = SerializationHelper.SerializeValue(valueSerializer, value);
48+
49+
switch (dictionaryRepresentation)
50+
{
51+
case DictionaryRepresentation.ArrayOfDocuments:
52+
return AstFilter.ElemMatch(dictionaryField, AstFilter.Eq(valueField, serializedValue));
53+
54+
default:
55+
throw new ExpressionNotSupportedException(expression, because: $"ContainsValue is not supported when DictionaryRepresentation is: {dictionaryRepresentation}");
56+
}
57+
}
58+
}
59+
60+
throw new ExpressionNotSupportedException(expression);
61+
}
62+
63+
private static IBsonDictionarySerializer GetDictionarySerializer(Expression expression, AstFilterField field)
64+
{
65+
if (field.Serializer is IBsonDictionarySerializer dictionarySerializer)
66+
{
67+
return dictionarySerializer;
68+
}
69+
70+
throw new ExpressionNotSupportedException(expression, because: $"class {field.Serializer.GetType().FullName} does not implement the IBsonDictionarySerializer interface");
71+
}
72+
73+
private static bool IsContainsValueMethod(MethodInfo method)
74+
{
75+
return
76+
!method.IsStatic &&
77+
method.IsPublic &&
78+
method.ReturnType == typeof(bool) &&
79+
method.Name == "ContainsValue" &&
80+
method.GetParameters() is var parameters &&
81+
parameters.Length == 1;
82+
}
83+
}
84+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/MethodCallExpressionToFilterTranslator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
2626
{
2727
case "Contains": return ContainsMethodToFilterTranslator.Translate(context, expression);
2828
case "ContainsKey": return ContainsKeyMethodToFilterTranslator.Translate(context, expression);
29+
case "ContainsValue": return ContainsValueMethodToFilterTranslator.Translate(context, expression);
2930
case "EndsWith": return EndsWithMethodToFilterTranslator.Translate(context, expression);
3031
case "Equals": return EqualsMethodToFilterTranslator.Translate(context, expression);
3132
case "Exists": return ExistsMethodToFilterTranslator.Translate(context, expression);

0 commit comments

Comments
 (0)