diff --git a/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs b/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs
index 1ff39685b2a..43591d7e92b 100644
--- a/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs
+++ b/src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs
@@ -29,6 +29,22 @@ public interface IKeyValuePairSerializer
BsonType Representation { get; }
}
+ ///
+ /// An extended interface for KeyValuePairSerializer that provides access to key and value serializers.
+ ///
+ public interface IKeyValuePairSerializerV2 : IKeyValuePairSerializer
+ {
+ ///
+ /// Gets the key serializer.
+ ///
+ IBsonSerializer KeySerializer { get; }
+
+ ///
+ /// Gets the value serializer.
+ ///
+ IBsonSerializer ValueSerializer { get; }
+ }
+
///
/// Static factory class for KeyValuePairSerializers.
///
@@ -61,7 +77,7 @@ public static IBsonSerializer Create(
public sealed class KeyValuePairSerializer :
StructSerializerBase>,
IBsonDocumentSerializer,
- IKeyValuePairSerializer
+ IKeyValuePairSerializerV2
{
// private constants
private static class Flags
@@ -191,6 +207,16 @@ public IBsonSerializer ValueSerializer
get { return _lazyValueSerializer.Value; }
}
+ ///
+ /// Gets the key serializer.
+ ///
+ IBsonSerializer IKeyValuePairSerializerV2.KeySerializer => KeySerializer;
+
+ ///
+ /// Gets the value serializer.
+ ///
+ IBsonSerializer IKeyValuePairSerializerV2.ValueSerializer => ValueSerializer;
+
// public methods
///
/// Deserializes a value.
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs
index bf729df32a9..13c9fc2a157 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Ast/Expressions/AstExpression.cs
@@ -134,6 +134,11 @@ public static AstExpression ArrayElemAt(AstExpression array, AstExpression index
return new AstBinaryExpression(AstBinaryOperator.ArrayElemAt, array, index);
}
+ public static AstExpression ArrayToObject(AstExpression arg)
+ {
+ return new AstUnaryExpression(AstUnaryOperator.ArrayToObject, arg);
+ }
+
public static AstExpression Avg(AstExpression array)
{
return new AstUnaryExpression(AstUnaryOperator.Avg, array);
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs
index 58cf7f6ab61..6b1251cb5dc 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs
@@ -14,7 +14,6 @@
*/
using System;
-using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Reflection;
@@ -66,11 +65,21 @@ public static TranslatedExpression Translate(TranslationContext context, MemberE
if (!DocumentSerializerHelper.AreMembersRepresentedAsFields(containerTranslation.Serializer, out _))
{
- if (member is PropertyInfo propertyInfo && propertyInfo.Name == "Length")
+ if (member is PropertyInfo propertyInfo && propertyInfo.Name == "Length")
{
return LengthPropertyToAggregationExpressionTranslator.Translate(context, expression);
}
+ if (TryTranslateDictionaryProperty(expression, containerTranslation, member, out var translatedDictionaryProperty))
+ {
+ return translatedDictionaryProperty;
+ }
+
+ if (TryTranslateKeyValuePairProperty(expression, containerTranslation, member, out var translatedKeyValuePairProperty))
+ {
+ return translatedKeyValuePairProperty;
+ }
+
if (TryTranslateCollectionCountProperty(expression, containerTranslation, member, out var translatedCount))
{
return translatedCount;
@@ -121,11 +130,20 @@ private static bool TryTranslateCollectionCountProperty(MemberExpression express
{
if (EnumerableProperty.IsCountProperty(expression))
{
- SerializationHelper.EnsureRepresentationIsArray(expression, container.Serializer);
+ AstExpression ast;
- var ast = AstExpression.Size(container.Ast);
- var serializer = Int32Serializer.Instance;
+ if (container.Serializer is IBsonDictionarySerializer dictionarySerializer &&
+ dictionarySerializer.DictionaryRepresentation == DictionaryRepresentation.Document)
+ {
+ ast = AstExpression.Size(AstExpression.ObjectToArray(container.Ast));
+ }
+ else
+ {
+ SerializationHelper.EnsureRepresentationIsArray(expression, container.Serializer);
+ ast = AstExpression.Size(container.Ast);
+ }
+ var serializer = Int32Serializer.Instance;
result = new TranslatedExpression(expression, ast, serializer);
return true;
}
@@ -187,5 +205,142 @@ private static bool TryTranslateDateTimeProperty(MemberExpression expression, Tr
return false;
}
+
+ private static bool TryTranslateKeyValuePairProperty(MemberExpression expression, TranslatedExpression container, MemberInfo memberInfo, out TranslatedExpression result)
+ {
+ result = null;
+
+ if (container.Expression.Type.IsGenericType &&
+ container.Expression.Type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>) &&
+ container.Serializer is IKeyValuePairSerializerV2 { Representation: BsonType.Array } kvpSerializer)
+ {
+ AstExpression ast;
+ IBsonSerializer serializer;
+
+ switch (memberInfo.Name)
+ {
+ case "Key":
+ ast = AstExpression.ArrayElemAt(container.Ast, 0);
+ serializer = kvpSerializer.KeySerializer;
+ break;
+ case "Value":
+ ast = AstExpression.ArrayElemAt(container.Ast, 1);
+ serializer = kvpSerializer.ValueSerializer;
+ break;
+ default:
+ throw new ExpressionNotSupportedException(expression);
+ }
+ result = new TranslatedExpression(expression, ast, serializer);
+ return true;
+ }
+
+ return false;
+ }
+
+ private static bool TryTranslateDictionaryProperty(MemberExpression expression, TranslatedExpression container, MemberInfo memberInfo, out TranslatedExpression result)
+ {
+ result = null;
+
+ if (memberInfo is PropertyInfo propertyInfo &&
+ propertyInfo.DeclaringType.IsGenericType &&
+ (propertyInfo.DeclaringType.GetGenericTypeDefinition() == typeof(Dictionary<,>) ||
+ propertyInfo.DeclaringType.GetGenericTypeDefinition() == typeof(IDictionary<,>)))
+ {
+ if (container.Serializer is IBsonDictionarySerializer dictionarySerializer)
+ {
+ switch (propertyInfo.Name)
+ {
+ case "Count":
+ {
+ AstExpression countAst;
+ switch (dictionarySerializer.DictionaryRepresentation)
+ {
+ case DictionaryRepresentation.Document:
+ countAst = AstExpression.Size(AstExpression.ObjectToArray(container.Ast));
+ break;
+ case DictionaryRepresentation.ArrayOfArrays:
+ case DictionaryRepresentation.ArrayOfDocuments:
+ countAst = AstExpression.Size(container.Ast);
+ break;
+ default:
+ throw new ExpressionNotSupportedException(expression);
+ }
+
+ var serializer = Int32Serializer.Instance;
+ result = new TranslatedExpression(expression, countAst, serializer);
+ return true;
+ }
+
+ case "Keys":
+ {
+ AstExpression keysAst;
+ switch (dictionarySerializer.DictionaryRepresentation)
+ {
+ case DictionaryRepresentation.Document:
+ keysAst = AstExpression.GetField(AstExpression.ObjectToArray(container.Ast), "k");
+ break;
+ case DictionaryRepresentation.ArrayOfArrays:
+ {
+ var kvp = AstExpression.Var("kvp");
+ keysAst = AstExpression.Map(
+ input: container.Ast,
+ @as: kvp,
+ @in: AstExpression.ArrayElemAt(kvp, 0));
+ break;
+ }
+ case DictionaryRepresentation.ArrayOfDocuments:
+ keysAst = AstExpression.GetField(container.Ast, "k");
+ break;
+
+ default:
+ throw new ExpressionNotSupportedException(expression);
+ }
+
+ var serializer = ArraySerializerHelper.CreateSerializer(dictionarySerializer.KeySerializer);
+ result = new TranslatedExpression(expression, keysAst, serializer);
+ return true;
+ }
+
+ case "Values":
+ {
+ AstExpression valuesAst;
+ switch (dictionarySerializer.DictionaryRepresentation)
+ {
+ case DictionaryRepresentation.Document:
+ valuesAst = AstExpression.GetField(AstExpression.ObjectToArray(container.Ast), "v");
+ break;
+ case DictionaryRepresentation.ArrayOfArrays:
+ {
+ var kvp = AstExpression.Var("kvp");
+ valuesAst = AstExpression.Map(
+ input: container.Ast,
+ @as: kvp,
+ @in: AstExpression.ArrayElemAt(kvp, 1));
+ break;
+ }
+ case DictionaryRepresentation.ArrayOfDocuments:
+ valuesAst = AstExpression.GetField(container.Ast, "v");
+ break;
+
+ default:
+ throw new ExpressionNotSupportedException(expression);
+ }
+
+ var serializer = ArraySerializerHelper.CreateSerializer(dictionarySerializer.ValueSerializer);
+ result = new TranslatedExpression(expression, valuesAst, serializer);
+ return true;
+ }
+
+ default:
+ throw new ExpressionNotSupportedException(expression);
+ }
+ }
+
+ throw new ExpressionNotSupportedException(expression, because: "serializer does not implement IBsonDictionarySerializer");
+ }
+
+ return false;
+ }
+
}
}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsKeyMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsKeyMethodToAggregationExpressionTranslator.cs
index 452a3839fca..07c7135670f 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsKeyMethodToAggregationExpressionTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsKeyMethodToAggregationExpressionTranslator.cs
@@ -36,27 +36,65 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC
{
var dictionaryExpression = expression.Object;
var keyExpression = arguments[0];
+ return TranslateContainsKey(context, expression, dictionaryExpression, keyExpression);
+ }
+
+ throw new ExpressionNotSupportedException(expression);
+ }
- var dictionaryTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, dictionaryExpression);
- var dictionarySerializer = GetDictionarySerializer(expression, dictionaryTranslation);
- var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
+ public static TranslatedExpression TranslateContainsKey(TranslationContext context, Expression expression, Expression dictionaryExpression, Expression keyExpression)
+ {
+ var dictionaryTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, dictionaryExpression);
+ var dictionarySerializer = GetDictionarySerializer(expression, dictionaryTranslation);
+ var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
- AstExpression ast;
- switch (dictionaryRepresentation)
- {
- case DictionaryRepresentation.Document:
+ AstExpression ast;
+ switch (dictionaryRepresentation)
+ {
+ case DictionaryRepresentation.Document:
+ {
var keyFieldName = GetKeyFieldName(context, expression, keyExpression, dictionarySerializer.KeySerializer);
ast = AstExpression.IsNotMissing(AstExpression.GetField(dictionaryTranslation.Ast, keyFieldName));
break;
+ }
- default:
- throw new ExpressionNotSupportedException(expression, because: $"ContainsKey is not supported when DictionaryRepresentation is: {dictionaryRepresentation}");
- }
+ case DictionaryRepresentation.ArrayOfDocuments:
+ {
+ var keyFieldName = GetKeyFieldName(context, expression, keyExpression, dictionarySerializer.KeySerializer);
+ var (valueBinding, valueAst) = AstExpression.UseVarIfNotSimple("value", keyFieldName);
+ ast = AstExpression.Let(
+ var: valueBinding,
+ @in: AstExpression.Reduce(
+ input: dictionaryTranslation.Ast,
+ initialValue: false,
+ @in: AstExpression.Cond(
+ @if: AstExpression.Var("value"),
+ @then: true,
+ @else: AstExpression.Eq(AstExpression.GetField(AstExpression.Var("this"), "k"), valueAst))));
+ break;
+ }
+
+ case DictionaryRepresentation.ArrayOfArrays:
+ {
+ var keyFieldName = GetKeyFieldName(context, expression, keyExpression, dictionarySerializer.KeySerializer);
+ var (valueBinding, valueAst) = AstExpression.UseVarIfNotSimple("value", keyFieldName);
+ ast = AstExpression.Let(
+ var: valueBinding,
+ @in: AstExpression.Reduce(
+ input: dictionaryTranslation.Ast,
+ initialValue: false,
+ @in: AstExpression.Cond(
+ @if: AstExpression.Var("value"),
+ @then: true,
+ @else: AstExpression.Eq(AstExpression.ArrayElemAt(AstExpression.Var("this"), 0), valueAst))));
+ break;
+ }
- return new TranslatedExpression(expression, ast, BooleanSerializer.Instance);
+ default:
+ throw new ExpressionNotSupportedException(expression, because: $"DictionaryRepresentation: {dictionaryRepresentation} is not supported.");
}
- throw new ExpressionNotSupportedException(expression);
+ return new TranslatedExpression(expression, ast, BooleanSerializer.Instance);
}
private static AstExpression GetKeyFieldName(TranslationContext context, Expression expression, Expression keyExpression, IBsonSerializer keySerializer)
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsMethodToAggregationExpressionTranslator.cs
index 7a4d64a3ff0..31b9255a606 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsMethodToAggregationExpressionTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsMethodToAggregationExpressionTranslator.cs
@@ -13,6 +13,7 @@
* limitations under the License.
*/
+using System.Collections.Generic;
using System.Linq.Expressions;
using MongoDB.Bson.Serialization.Serializers;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
@@ -33,6 +34,11 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC
if (IsEnumerableContainsMethod(expression, out var sourceExpression, out var valueExpression))
{
+ if (TryTranslateDictionaryKeysOrValuesContains(context, expression, sourceExpression, valueExpression, out var dictionaryTranslation))
+ {
+ return dictionaryTranslation;
+ }
+
return TranslateEnumerableContains(context, expression, sourceExpression, valueExpression);
}
@@ -83,5 +89,48 @@ private static TranslatedExpression TranslateEnumerableContains(TranslationConte
return new TranslatedExpression(expression, ast, BooleanSerializer.Instance);
}
+
+ private static bool TryTranslateDictionaryKeysOrValuesContains(
+ TranslationContext context,
+ Expression expression,
+ Expression sourceExpression,
+ Expression valueExpression,
+ out TranslatedExpression translation)
+ {
+ translation = null;
+
+ if (sourceExpression is not MemberExpression memberExpression)
+ {
+ return false;
+ }
+
+ var memberName = memberExpression.Member.Name;
+ var declaringType = memberExpression.Member.DeclaringType;
+
+ if (!declaringType.IsGenericType ||
+ (declaringType.GetGenericTypeDefinition() != typeof(Dictionary<,>) &&
+ declaringType.GetGenericTypeDefinition() != typeof(IDictionary<,>)))
+ {
+ return false;
+ }
+
+ switch (memberName)
+ {
+ case "Keys":
+ {
+ var dictionaryExpression = memberExpression.Expression;
+ translation = ContainsKeyMethodToAggregationExpressionTranslator.TranslateContainsKey(context, expression, dictionaryExpression, valueExpression);
+ return true;
+ }
+ case "Values":
+ {
+ var dictionaryExpression = memberExpression.Expression;
+ translation = ContainsValueMethodToAggregationExpressionTranslator.TranslateContainsValue(context, expression, dictionaryExpression, valueExpression);
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
}
}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsValueMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsValueMethodToAggregationExpressionTranslator.cs
index d3545e44266..407413c8ff3 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsValueMethodToAggregationExpressionTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/ContainsValueMethodToAggregationExpressionTranslator.cs
@@ -34,61 +34,65 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC
{
var dictionaryExpression = expression.Object;
var valueExpression = arguments[0];
+ return TranslateContainsValue(context, expression, dictionaryExpression, valueExpression);
+ }
- var dictionaryTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, dictionaryExpression);
- var dictionarySerializer = GetDictionarySerializer(expression, dictionaryTranslation);
- var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
+ throw new ExpressionNotSupportedException(expression);
+ }
- var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
- var (valueBinding, valueAst) = AstExpression.UseVarIfNotSimple("value", valueTranslation.Ast);
+ public static TranslatedExpression TranslateContainsValue(TranslationContext context, Expression expression, Expression dictionaryExpression, Expression valueExpression)
+ {
+ var dictionaryTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, dictionaryExpression);
+ var dictionarySerializer = GetDictionarySerializer(expression, dictionaryTranslation);
+ var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
- AstExpression ast;
- switch (dictionaryRepresentation)
- {
- case DictionaryRepresentation.Document:
- ast = AstExpression.Let(
- var: valueBinding,
- @in: AstExpression.Reduce(
- input: AstExpression.ObjectToArray(dictionaryTranslation.Ast),
- initialValue: false,
- @in: AstExpression.Cond(
- @if: AstExpression.Var("value"),
- @then: true,
- @else: AstExpression.Eq(AstExpression.GetField(AstExpression.Var("this"), "v"), valueAst))));
- break;
+ var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
+ var (valueBinding, valueAst) = AstExpression.UseVarIfNotSimple("value", valueTranslation.Ast);
- case DictionaryRepresentation.ArrayOfArrays:
- ast = AstExpression.Let(
- var: valueBinding,
- @in: AstExpression.Reduce(
- input: dictionaryTranslation.Ast,
- initialValue: false,
- @in: AstExpression.Cond(
- @if: AstExpression.Var("value"),
- @then: true,
- @else: AstExpression.Eq(AstExpression.ArrayElemAt(AstExpression.Var("this"), 1), valueAst))));
- break;
+ AstExpression ast;
+ switch (dictionaryRepresentation)
+ {
+ case DictionaryRepresentation.Document:
+ ast = AstExpression.Let(
+ var: valueBinding,
+ @in: AstExpression.Reduce(
+ input: AstExpression.ObjectToArray(dictionaryTranslation.Ast),
+ initialValue: false,
+ @in: AstExpression.Cond(
+ @if: AstExpression.Var("value"),
+ @then: true,
+ @else: AstExpression.Eq(AstExpression.GetField(AstExpression.Var("this"), "v"), valueAst))));
+ break;
- case DictionaryRepresentation.ArrayOfDocuments:
- ast = AstExpression.Let(
- var: valueBinding,
- @in: AstExpression.Reduce(
- input: dictionaryTranslation.Ast,
- initialValue: false,
- @in: AstExpression.Cond(
- @if: AstExpression.Var("value"),
- @then: true,
- @else: AstExpression.Eq(AstExpression.GetField(AstExpression.Var("this"), "v"), valueAst))));
- break;
+ case DictionaryRepresentation.ArrayOfArrays:
+ ast = AstExpression.Let(
+ var: valueBinding,
+ @in: AstExpression.Reduce(
+ input: dictionaryTranslation.Ast,
+ initialValue: false,
+ @in: AstExpression.Cond(
+ @if: AstExpression.Var("value"),
+ @then: true,
+ @else: AstExpression.Eq(AstExpression.ArrayElemAt(AstExpression.Var("this"), 1), valueAst))));
+ break;
- default:
- throw new ExpressionNotSupportedException(expression, because: $"ContainsValue is not supported when DictionaryRepresentation is: {dictionaryRepresentation}");
- }
+ case DictionaryRepresentation.ArrayOfDocuments:
+ ast = AstExpression.Let(
+ var: valueBinding,
+ @in: AstExpression.Reduce(
+ input: dictionaryTranslation.Ast,
+ initialValue: false,
+ @in: AstExpression.Cond(
+ @if: AstExpression.Var("value"),
+ @then: true,
+ @else: AstExpression.Eq(AstExpression.GetField(AstExpression.Var("this"), "v"), valueAst))));
+ break;
- return new TranslatedExpression(expression, ast, BooleanSerializer.Instance);
+ default:
+ throw new ExpressionNotSupportedException(expression, because: $"DictionaryRepresentation: {dictionaryRepresentation} is not supported.");
}
- throw new ExpressionNotSupportedException(expression);
+ return new TranslatedExpression(expression, ast, BooleanSerializer.Instance);
}
private static IBsonDictionarySerializer GetDictionarySerializer(Expression expression, TranslatedExpression dictionaryTranslation)
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs
index 88bf49554f6..41e75b1620f 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/GetItemMethodToAggregationExpressionTranslator.cs
@@ -119,10 +119,6 @@ private static TranslatedExpression TranslateIDictionaryGetItemWithKey(Translati
{
throw new ExpressionNotSupportedException(expression, because: $"dictionary serializer class {dictionaryTranslation.Serializer.GetType()} does not implement {nameof(IBsonDictionarySerializer)}");
}
- if (dictionarySerializer.DictionaryRepresentation != DictionaryRepresentation.Document)
- {
- throw new ExpressionNotSupportedException(expression, because: "dictionary is not represented as a document");
- }
var keySerializer = dictionarySerializer.KeySerializer;
AstExpression keyFieldNameAst;
@@ -159,7 +155,27 @@ private static TranslatedExpression TranslateIDictionaryGetItemWithKey(Translati
keyFieldNameAst = keyTranslation.Ast;
}
- var ast = AstExpression.GetField(dictionaryTranslation.Ast, keyFieldNameAst);
+ var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
+ AstExpression ast;
+ switch (dictionaryRepresentation)
+ {
+ case DictionaryRepresentation.Document:
+ ast = AstExpression.GetField(dictionaryTranslation.Ast, keyFieldNameAst);
+ break;
+
+ case DictionaryRepresentation.ArrayOfArrays:
+ ast = AstExpression.GetField(AstExpression.ArrayToObject(dictionaryTranslation.Ast), keyFieldNameAst);
+ break;
+
+ case DictionaryRepresentation.ArrayOfDocuments:
+ var filter = AstExpression.Filter(dictionaryTranslation.Ast,
+ AstExpression.Eq(AstExpression.GetField(AstExpression.Var("kvp"), "k"), keyFieldNameAst), "kvp");
+ ast = AstExpression.GetField(AstExpression.ArrayElemAt(filter, 0), "v");
+ break;
+ default:
+ throw new ExpressionNotSupportedException(expression, because: $"Indexer access is not supported when DictionaryRepresentation is: {dictionaryRepresentation}");
+ }
+
return new TranslatedExpression(expression, ast, dictionarySerializer.ValueSerializer);
}
}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/AllOrAnyMethodToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/AllOrAnyMethodToFilterTranslator.cs
index abb59233f3c..74875b1c5e5 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/AllOrAnyMethodToFilterTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/AllOrAnyMethodToFilterTranslator.cs
@@ -13,13 +13,12 @@
* limitations under the License.
*/
-using System;
using System.Linq;
using System.Linq.Expressions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Conventions;
-using MongoDB.Bson.Serialization.Serializers;
+using MongoDB.Bson.Serialization.Options;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
@@ -50,6 +49,11 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
var sourceExpression = method.IsStatic ? arguments[0] : expression.Object;
var (fieldTranslation, filter) = FilteredEnumerableFilterFieldTranslator.Translate(context, sourceExpression);
+ if (fieldTranslation.Serializer is IBsonDictionarySerializer { DictionaryRepresentation: DictionaryRepresentation.Document })
+ {
+ throw new ExpressionNotSupportedException(expression);
+ }
+
if (method.IsOneOf(EnumerableMethod.All, EnumerableMethod.AnyWithPredicate, ArrayMethod.Exists) || ListMethod.IsExistsMethod(method))
{
var predicateLambda = (LambdaExpression)(method.IsStatic ? arguments[1] : arguments[0]);
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsKeyMethodToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsKeyMethodToFilterTranslator.cs
index 48398cec982..12233a70697 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsKeyMethodToFilterTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsKeyMethodToFilterTranslator.cs
@@ -35,24 +35,40 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
{
var fieldExpression = expression.Object;
var keyExpression = arguments[0];
+ return TranslateContainsKey(context, expression, fieldExpression, keyExpression);
+ }
+
+ throw new ExpressionNotSupportedException(expression);
+ }
- var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
- var dictionarySerializer = GetDictionarySerializer(expression, fieldTranslation);
- var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
+ public static AstFilter TranslateContainsKey(TranslationContext context, Expression expression, Expression fieldExpression, Expression keyExpression)
+ {
+ var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
+ var dictionarySerializer = GetDictionarySerializer(expression, fieldTranslation);
+ var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
- switch (dictionaryRepresentation)
- {
- case DictionaryRepresentation.Document:
+ switch (dictionaryRepresentation)
+ {
+ case DictionaryRepresentation.Document:
+ {
var key = GetKeyStringConstant(expression, keyExpression, dictionarySerializer.KeySerializer);
var keyField = fieldTranslation.Ast.SubField(key);
return AstFilter.Exists(keyField);
+ }
- default:
- throw new ExpressionNotSupportedException(expression, because: $"ContainsKey is not supported when DictionaryRepresentation is: {dictionaryRepresentation}");
- }
- }
+ case DictionaryRepresentation.ArrayOfDocuments:
+ case DictionaryRepresentation.ArrayOfArrays:
+ {
+ var key = GetKeyStringConstant(expression, keyExpression, dictionarySerializer.KeySerializer);
+ var fieldName = dictionaryRepresentation == DictionaryRepresentation.ArrayOfDocuments ? "k" : "0";
+ var keyField = AstFilter.Field(fieldName);
+ var keyMatchFilter = AstFilter.Eq(keyField, key);
+ return AstFilter.ElemMatch(fieldTranslation.Ast, keyMatchFilter);
+ }
- throw new ExpressionNotSupportedException(expression);
+ default:
+ throw new ExpressionNotSupportedException(expression, because: $"DictionaryRepresentation: {dictionaryRepresentation} is not supported for ContainsKey method.");
+ }
}
private static IBsonDictionarySerializer GetDictionarySerializer(Expression expression, TranslatedFilterField field)
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsMethodToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsMethodToFilterTranslator.cs
index 574a93809c6..abcdf87c4e6 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsMethodToFilterTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsMethodToFilterTranslator.cs
@@ -16,7 +16,9 @@
using System;
using System.Collections;
using System.Collections.Generic;
+using System.Linq;
using System.Linq.Expressions;
+using MongoDB.Bson.Serialization;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
@@ -45,6 +47,12 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
var fieldType = fieldExpression.Type;
var itemExpression = arguments[1];
var itemType = itemExpression.Type;
+
+ if (TryTranslateDictionaryKeysOrValuesContains(context, expression, fieldExpression, itemExpression, out var dictionaryFilter))
+ {
+ return dictionaryFilter;
+ }
+
if (TypeImplementsIEnumerable(fieldType, itemType))
{
return Translate(context, expression, fieldExpression, itemExpression);
@@ -57,8 +65,15 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
arguments.Count == 1)
{
var fieldExpression = expression.Object;
- var fieldType = fieldExpression.Type;
var itemExpression = arguments[0];
+
+ if (TryTranslateDictionaryKeysOrValuesContains(context, expression, fieldExpression, itemExpression, out var dictionaryFilter))
+ {
+ return dictionaryFilter;
+ }
+
+ // Otherwise, handle as regular Contains on IEnumerable
+ var fieldType = fieldExpression.Type;
var itemType = itemExpression.Type;
if (TypeImplementsIEnumerable(fieldType, itemType))
{
@@ -91,5 +106,48 @@ private static bool TypeImplementsIEnumerable(Type type, Type itemType)
var ienumerableType = typeof(IEnumerable<>).MakeGenericType(itemType);
return ienumerableType.IsAssignableFrom(type);
}
+
+ private static bool TryTranslateDictionaryKeysOrValuesContains(
+ TranslationContext context,
+ Expression expression,
+ Expression fieldExpression,
+ Expression itemExpression,
+ out AstFilter filter)
+ {
+ filter = null;
+
+ if (fieldExpression is not MemberExpression memberExpression)
+ {
+ return false;
+ }
+
+ var memberName = memberExpression.Member.Name;
+ var declaringType = memberExpression.Member.DeclaringType;
+
+ if (!declaringType.IsGenericType ||
+ (declaringType.GetGenericTypeDefinition() != typeof(Dictionary<,>) &&
+ declaringType.GetGenericTypeDefinition() != typeof(IDictionary<,>)))
+ {
+ return false;
+ }
+
+ switch (memberName)
+ {
+ case "Keys":
+ {
+ var dictionaryExpression = memberExpression.Expression;
+ filter = ContainsKeyMethodToFilterTranslator.TranslateContainsKey(context, expression, dictionaryExpression, itemExpression);
+ return true;
+ }
+ case "Values":
+ {
+ var dictionaryExpression = memberExpression.Expression;
+ filter = ContainsValueMethodToFilterTranslator.TranslateContainsValue(context, expression, dictionaryExpression, itemExpression);
+ return true;
+ }
+ default:
+ return false;
+ }
+ }
}
}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsValueMethodToFilterTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsValueMethodToFilterTranslator.cs
index 2066d8f789a..3d863cb91bc 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsValueMethodToFilterTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/MethodTranslators/ContainsValueMethodToFilterTranslator.cs
@@ -34,26 +34,36 @@ public static AstFilter Translate(TranslationContext context, MethodCallExpressi
{
var fieldExpression = expression.Object;
var valueExpression = arguments[0];
+ return TranslateContainsValue(context, expression, fieldExpression, valueExpression);
+ }
- var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
- var dictionarySerializer = GetDictionarySerializer(expression, fieldTranslation);
- var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
- var valueSerializer = dictionarySerializer.ValueSerializer;
+ throw new ExpressionNotSupportedException(expression);
+ }
- if (valueExpression is ConstantExpression constantValueExpression)
- {
- var valueField = AstFilter.Field("v");
- var value = constantValueExpression.Value;
- var serializedValue = SerializationHelper.SerializeValue(valueSerializer, value);
+ public static AstFilter TranslateContainsValue(TranslationContext context, Expression expression, Expression fieldExpression, Expression valueExpression)
+ {
+ var fieldTranslation = ExpressionToFilterFieldTranslator.Translate(context, fieldExpression);
+ var dictionarySerializer = GetDictionarySerializer(expression, fieldTranslation);
+ var dictionaryRepresentation = dictionarySerializer.DictionaryRepresentation;
+ var valueSerializer = dictionarySerializer.ValueSerializer;
+
+ if (valueExpression is ConstantExpression constantValueExpression)
+ {
+ var value = constantValueExpression.Value;
+ var serializedValue = SerializationHelper.SerializeValue(valueSerializer, value);
- switch (dictionaryRepresentation)
- {
- case DictionaryRepresentation.ArrayOfDocuments:
+ switch (dictionaryRepresentation)
+ {
+ case DictionaryRepresentation.ArrayOfDocuments:
+ case DictionaryRepresentation.ArrayOfArrays:
+ {
+ var fieldName = dictionaryRepresentation == DictionaryRepresentation.ArrayOfDocuments ? "v" : "1";
+ var valueField = AstFilter.Field(fieldName);
return AstFilter.ElemMatch(fieldTranslation.Ast, AstFilter.Eq(valueField, serializedValue));
+ }
- default:
- throw new ExpressionNotSupportedException(expression, because: $"ContainsValue is not supported when DictionaryRepresentation is: {dictionaryRepresentation}");
- }
+ default:
+ throw new ExpressionNotSupportedException(expression, because: $"DictionaryRepresentation: {dictionaryRepresentation} is not supported for ContainsValue method.");
}
}
diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs
index bbeba78f6ce..f073f2c3edf 100644
--- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs
+++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs
@@ -14,11 +14,11 @@
*/
using System;
+using System.Collections.Generic;
using System.Linq.Expressions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization;
using MongoDB.Bson.Serialization.Serializers;
-using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators
@@ -42,6 +42,13 @@ public static TranslatedFilterField Translate(TranslationContext context, Member
var fieldSerializer = fieldTranslation.Serializer;
var fieldSerializerType = fieldSerializer.GetType();
+ if (memberExpression.Type.IsGenericType &&
+ (memberExpression.Type.GetGenericTypeDefinition() == typeof(Dictionary<,>.KeyCollection) ||
+ memberExpression.Type.GetGenericTypeDefinition() == typeof(Dictionary<,>.ValueCollection)))
+ {
+ throw new ExpressionNotSupportedException(memberExpression);
+ }
+
if (fieldSerializer.GetType() == typeof(BsonValueSerializer))
{
var field = fieldTranslation.Ast;
@@ -134,6 +141,28 @@ public static TranslatedFilterField Translate(TranslationContext context, Member
throw new ExpressionNotSupportedException(memberExpression, because: $"serializer {fieldTranslation.Serializer.GetType().FullName} does not implement IBsonTupleSerializer");
}
+ if (fieldExpression.Type.IsGenericType &&
+ fieldExpression.Type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>) &&
+ fieldSerializer is IKeyValuePairSerializerV2 keyValuePairSerializer)
+ {
+ switch (memberExpression.Member.Name)
+ {
+ case "Key":
+ if (keyValuePairSerializer.Representation == BsonType.Array)
+ {
+ return fieldTranslation.SubField("0", keyValuePairSerializer.KeySerializer);
+ }
+ return fieldTranslation.SubField("k", keyValuePairSerializer.KeySerializer);
+
+ case "Value":
+ if (keyValuePairSerializer.Representation == BsonType.Array)
+ {
+ return fieldTranslation.SubField("1", keyValuePairSerializer.ValueSerializer);
+ }
+ return fieldTranslation.SubField("v", keyValuePairSerializer.ValueSerializer);
+ }
+ }
+
throw new ExpressionNotSupportedException(memberExpression);
}
}
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp2509Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp2509Tests.cs
deleted file mode 100644
index b64e62ed650..00000000000
--- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp2509Tests.cs
+++ /dev/null
@@ -1,163 +0,0 @@
-/* Copyright 2010-present MongoDB Inc.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using FluentAssertions;
-using MongoDB.Bson.Serialization.Attributes;
-using MongoDB.Bson.Serialization.Options;
-using MongoDB.Driver.TestHelpers;
-using Xunit;
-
-namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
-{
- public class CSharp2509Tests : LinqIntegrationTest
- {
- public CSharp2509Tests(ClassFixture fixture)
- : base(fixture)
- {
- }
-
- [Fact]
- public void Where_ContainsValue_should_work_when_representation_is_Dictionary()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.D1.ContainsValue(1));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { $expr : { $reduce : { input : { $objectToArray : '$D1' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 1] } } } } } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(1, 2);
- }
-
- [Fact]
- public void Where_ContainsValue_should_work_when_representation_is_ArrayOfArrays()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.D2.ContainsValue(1));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { $expr : { $reduce : { input : '$D2', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 1] }, 1] } } } } } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(1, 2);
- }
-
- [Fact]
- public void Where_ContainsValue_should_work_when_representation_is_ArrayOfDocuments()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.D3.ContainsValue(1));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { D3 : { $elemMatch : { v : 1 } } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(1, 2);
- }
-
- [Fact]
- public void Select_ContainsValue_should_work_when_representation_is_Dictionary()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Select(x => x.D1.ContainsValue(1));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $reduce : { input : { $objectToArray : '$D1' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 1] } } } } }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(true, true, false);
- }
-
- [Fact]
- public void Select_ContainsValue_should_work_when_representation_is_ArrayOfArrays()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Select(x => x.D2.ContainsValue(1));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$D2', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 1] }, 1] } } } } }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(true, true, false);
- }
-
- [Fact]
- public void Select_ContainsValue_should_work_when_representation_is_ArrayOfDocuments()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Select(x => x.D3.ContainsValue(1));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$D3', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 1] } } } } }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(true, true, false);
- }
-
- public class User
- {
- public int Id { get; set; }
- [BsonDictionaryOptions(DictionaryRepresentation.Document)]
- public Dictionary D1 { get; set; }
- [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
- public Dictionary D2 { get; set; }
- [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
- public Dictionary D3 { get; set; }
- }
-
- public sealed class ClassFixture : MongoCollectionFixture
- {
- protected override IEnumerable InitialData =>
- [
- new User
- {
- Id = 1,
- D1 = new() { { "A", 1 }, { "B", 2 } },
- D2 = new() { { "A", 1 }, { "B", 2 } },
- D3 = new() { { "A", 1 }, { "B", 2 } }
- },
- new User
- {
- Id = 2,
- D1 = new() { { "A", 2 }, { "B", 1 } },
- D2 = new() { { "A", 2 }, { "B", 1 } },
- D3 = new() { { "A", 2 }, { "B", 1 } }
- },
- new User
- {
- Id = 3,
- D1 = new() { { "A", 2 }, { "B", 3 } },
- D2 = new() { { "A", 2 }, { "B", 3 } },
- D3 = new() { { "A", 2 }, { "B", 3 } }
- }
- ];
- }
- }
-}
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4443Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4443Tests.cs
new file mode 100644
index 00000000000..b5b0e81790b
--- /dev/null
+++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4443Tests.cs
@@ -0,0 +1,2800 @@
+/* Copyright 2010-present MongoDB Inc.
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+
+using System.Collections.Generic;
+using System.Linq;
+using FluentAssertions;
+using MongoDB.Bson;
+using MongoDB.Bson.Serialization.Attributes;
+using MongoDB.Bson.Serialization.Options;
+using MongoDB.Driver.Linq;
+using Xunit;
+
+namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira;
+
+public class CSharp4443Tests : LinqIntegrationTest
+{
+ public CSharp4443Tests(ClassFixture fixture)
+ : base(fixture)
+ {
+ }
+
+ [Fact]
+ public void Projecting_dictionary_keys_with_arrayOfArrays_should_throw()
+ {
+ var exception = Record.Exception(() =>
+ {
+ _ = Fixture.ArrayOfArraysCollection.AsQueryable()
+ .Select(x => x.Dictionary.Keys)
+ .ToList();
+ });
+
+ exception.Should().BeOfType();
+ }
+
+ [Fact]
+ public void Projecting_dictionary_keys_with_arrayOfDocs_should_throw()
+ {
+ var exception = Record.Exception(() =>
+ {
+ _ = Fixture.ArrayOfDocsCollection.AsQueryable()
+ .Select(x => x.Dictionary.Keys)
+ .ToList();
+ });
+
+ exception.Should().BeOfType();
+ }
+
+ [Fact]
+ public void Projecting_dictionary_keys_with_document_should_throw()
+ {
+ var exception = Record.Exception(() =>
+ {
+ _ = Fixture.DocCollection.AsQueryable()
+ .Select(x => x.Dictionary.Keys)
+ .ToList();
+ });
+
+ exception.Should().BeOfType();
+ }
+
+ [Fact]
+ public void Projecting_dictionary_values_with_arrayOfArrays_should_throw()
+ {
+ var exception = Record.Exception(() =>
+ {
+ _ = Fixture.ArrayOfArraysCollection.AsQueryable()
+ .Select(x => x.Dictionary.Values)
+ .ToList();
+ });
+
+ exception.Should().BeOfType();
+ }
+
+ [Fact]
+ public void Projecting_dictionary_values_with_arrayOfDocs_should_throw()
+ {
+ var exception = Record.Exception(() =>
+ {
+ _ = Fixture.ArrayOfDocsCollection.AsQueryable()
+ .Select(x => x.Dictionary.Values)
+ .ToList();
+ });
+
+ exception.Should().BeOfType();
+ }
+
+ [Fact]
+ public void Projecting_dictionary_values_with_document_should_throw()
+ {
+ var exception = Record.Exception(() =>
+ {
+ _ = Fixture.DocCollection.AsQueryable()
+ .Select(x => x.Dictionary.Values)
+ .ToList();
+ });
+
+ exception.Should().BeOfType();
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_All_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $allElementsTrue : { $map : { input : '$Dictionary', as : 'kvp', in : { $gt : [{ $arrayElemAt : ['$$kvp', 1] }, 100] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(false, false, false, true);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $anyElementTrue : { $map : { input : '$Dictionary', as : 'kvp', in : { $gt : [{ $arrayElemAt : ['$$kvp', 1] }, 90] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, true, true);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_Average_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Average());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $avg : { $map : { input : '$Dictionary', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(55.666666666666664, 52.0, 67.5, 165.0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 0] }, 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_ContainsValue_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.ContainsValue(25));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 1] }, 25] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_Count_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Count);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $size : '$Dictionary' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(3, 3, 2, 2);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Count(kvp => kvp.Value < 50));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$Dictionary', as : 'kvp', in : { $cond : { if : { $lt : [{ $arrayElemAt : ['$$kvp', 1] }, 50] }, then : 1, else : 0 } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(2, 2, 1, 0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_First_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.First(kvp => kvp.Key == "age").Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : [{ $arrayElemAt : ['$$kvp', 0] }, 'age'] } } }, 0] }, 1] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_FirstOrDefault_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.FirstOrDefault(kvp => kvp.Key.StartsWith("l")).Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $let : { vars : { values : { $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : [{ $indexOfCP : [{ $arrayElemAt : ['$$kvp', 0] }, 'l'] }, 0] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : [null, 0], else : { $arrayElemAt : ['$$values', 0] } } } } }, 1] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(42, 41, 0, 0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayToObject : '$Dictionary' } }, in : '$$this.age' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 0] }, 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_Max_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Max());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $max : { $map : { input : '$Dictionary', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(100, 85, 100, 200);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_Select_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Select(kvp => kvp.Value).Sum());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$Dictionary', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_Sum_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Sum(kvp => kvp.Value));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$Dictionary', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 1] }, 42] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfArrays_Where_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Where(kvp => kvp.Value == 35).Any());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $gt : [{ $size : { $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : [{ $arrayElemAt : ['$$kvp', 1] }, 35] } } } }, 0] }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().Equal(false, false, true, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_All_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $allElementsTrue : { $map : { input : '$Dictionary', as : 'kvp', in : { $gt : ['$$kvp.v', 100] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(false, false, false, true);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $anyElementTrue : { $map : { input : '$Dictionary', as : 'kvp', in : { $gt : ['$$kvp.v', 90] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, true, true);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_Average_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Average());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $avg : '$Dictionary.v' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(55.666666666666664, 52.0, 67.5, 165.0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.k', 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_ContainsValue_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.ContainsValue(25));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 25] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_Count_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Count);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $size : '$Dictionary' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(3, 3, 2, 2);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Count(kvp => kvp.Value < 50));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$Dictionary', as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(2, 2, 1, 0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_First_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.First(kvp => kvp.Key == "age").Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_FirstOrDefault_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.FirstOrDefault(kvp => kvp.Key.StartsWith("l")).Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $let : { vars : { values : { $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : { k : null, v : 0 }, else : { $arrayElemAt : ['$$values', 0] } } } } } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(42, 41, 0, 0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.k', 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_Max_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Max());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $max : '$Dictionary.v' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(100, 85, 100, 200);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_Select_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Select(kvp => kvp.Value).Sum());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : '$Dictionary.v' }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_Sum_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Sum(kvp => kvp.Value));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : '$Dictionary.v' }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$Dictionary', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 42] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsArrayOfDocuments_Where_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Where(kvp => kvp.Value == 35).Any());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $gt : [{ $size : { $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : ['$$kvp.v', 35] } } } }, 0] }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().Equal(false, false, true, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_All_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $allElementsTrue : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : { $gt : ['$$kvp.v', 100] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(false, false, false, true);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_Any_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $anyElementTrue : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : { $gt : ['$$kvp.v', 90] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, true, true);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_Average_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Average());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $avg : { $let : { vars : { this : { $objectToArray : '$Dictionary' } }, in : '$$this.v' } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(55.666666666666664, 52.0, 67.5, 165.0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_ContainsKey_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $ne : [{ $type : '$Dictionary.life' }, 'missing'] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_ContainsValue_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.ContainsValue(25));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : { $objectToArray : '$Dictionary' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 25] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_Count_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Count);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $size : { $objectToArray : '$Dictionary' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(3, 3, 2, 2);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Count(kvp => kvp.Value < 50));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(2, 2, 1, 0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_First_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.First(kvp => kvp.Key == "age").Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_FirstOrDefault_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.FirstOrDefault(kvp => kvp.Key.StartsWith("l")).Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $let : { vars : { values : { $filter : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : { k : null, v : 0 }, else : { $arrayElemAt : ['$$values', 0] } } } } } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(42, 41, 0, 0);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_IndexerAccess_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : '$Dictionary.age', _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_KeysContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $ne : [{ $type : '$Dictionary.life' }, 'missing'] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_Max_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Max());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $max : { $let : { vars : { this : { $objectToArray : '$Dictionary' } }, in : '$$this.v' } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(100, 85, 100, 200);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_Select_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Select(kvp => kvp.Value).Sum());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : '$$kvp.v' } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_Sum_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Sum(kvp => kvp.Value));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : '$$kvp.v' } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_ValuesContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : { $objectToArray : '$Dictionary' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 42] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_DictionaryAsDocument_Where_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.Dictionary.Where(kvp => kvp.Value == 35).Any());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $gt : [{ $size : { $filter : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', cond : { $eq : ['$$kvp.v', 35] } } } }, 0] }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().Equal(false, false, true, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_All_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $allElementsTrue : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $gt : [{ $arrayElemAt : ['$$kvp', 1] }, 100] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(false, false, false, true);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $anyElementTrue : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $gt : [{ $arrayElemAt : ['$$kvp', 1] }, 90] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, true, true);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_Average_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Average());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $avg : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(55.666666666666664, 52.0, 67.5, 165.0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$DictionaryInterface', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 0] }, 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_Count_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Count);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $size : '$DictionaryInterface' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(3, 3, 2, 2);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Count(kvp => kvp.Value < 50));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $cond : { if : { $lt : [{ $arrayElemAt : ['$$kvp', 1] }, 50] }, then : 1, else : 0 } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(2, 2, 1, 0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_First_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.First(kvp => kvp.Key == "age").Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : [{ $arrayElemAt : ['$$kvp', 0] }, 'age'] } } }, 0] }, 1] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_FirstOrDefault_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.FirstOrDefault(kvp => kvp.Key.StartsWith("l")).Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $arrayElemAt : [{ $let : { vars : { values : { $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : [{ $indexOfCP : [{ $arrayElemAt : ['$$kvp', 0] }, 'l'] }, 0] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : [null, 0], else : { $arrayElemAt : ['$$values', 0] } } } } }, 1] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(42, 41, 0, 0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayToObject : '$DictionaryInterface' } }, in : '$$this.age' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$DictionaryInterface', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 0] }, 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_Max_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Max());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $max : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(100, 85, 100, 200);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_Select_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Select(kvp => kvp.Value).Sum());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_Sum_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Sum(kvp => kvp.Value));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $arrayElemAt : ['$$kvp', 1] } } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$DictionaryInterface', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : [{ $arrayElemAt : ['$$this', 1] }, 42] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfArrays_Where_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Where(kvp => kvp.Value == 35).Any());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $gt : [{ $size : { $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : [{ $arrayElemAt : ['$$kvp', 1] }, 35] } } } }, 0] }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().Equal(false, false, true, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_All_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $allElementsTrue : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $gt : ['$$kvp.v', 100] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(false, false, false, true);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $anyElementTrue : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $gt : ['$$kvp.v', 90] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, true, true);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_Average_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Average());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $avg : '$DictionaryInterface.v' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(55.666666666666664, 52.0, 67.5, 165.0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$DictionaryInterface', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.k', 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_Count_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Count);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $size : '$DictionaryInterface' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(3, 3, 2, 2);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Count(kvp => kvp.Value < 50));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(2, 2, 1, 0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_First_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.First(kvp => kvp.Key == "age").Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_FirstOrDefault_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.FirstOrDefault(kvp => kvp.Key.StartsWith("l")).Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $let : { vars : { values : { $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : { k : null, v : 0 }, else : { $arrayElemAt : ['$$values', 0] } } } } } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(42, 41, 0, 0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$DictionaryInterface', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.k', 'life'] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_Max_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Max());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $max : '$DictionaryInterface.v' }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(100, 85, 100, 200);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_Select_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Select(kvp => kvp.Value).Sum());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : '$DictionaryInterface.v' }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_Sum_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Sum(kvp => kvp.Value));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : '$DictionaryInterface.v' }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : '$DictionaryInterface', initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 42] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsArrayOfDocuments_Where_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Where(kvp => kvp.Value == 35).Any());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $gt : [{ $size : { $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : ['$$kvp.v', 35] } } } }, 0] }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().Equal(false, false, true, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_All_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $allElementsTrue : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : { $gt : ['$$kvp.v', 100] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(false, false, false, true);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_Any_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $anyElementTrue : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : { $gt : ['$$kvp.v', 90] } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, true, true);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_Average_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Average());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $avg : { $let : { vars : { this : { $objectToArray : '$DictionaryInterface' } }, in : '$$this.v' } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(55.666666666666664, 52.0, 67.5, 165.0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_ContainsKey_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $ne : [{ $type : '$DictionaryInterface.life' }, 'missing'] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_Count_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Count);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $size : { $objectToArray : '$DictionaryInterface' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(3, 3, 2, 2);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Count(kvp => kvp.Value < 50));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(2, 2, 1, 0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_First_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.First(kvp => kvp.Key == "age").Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_FirstOrDefault_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.FirstOrDefault(kvp => kvp.Key.StartsWith("l")).Value);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $let : { vars : { this : { $let : { vars : { values : { $filter : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } } }, in : { $cond : { if : { $eq : [{ $size : '$$values' }, 0] }, then : { k : null, v : 0 }, else : { $arrayElemAt : ['$$values', 0] } } } } } }, in : '$$this.v' } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(42, 41, 0, 0);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_IndexerAccess_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : '$DictionaryInterface.age', _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(25, 30, 35, 130);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_KeysContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $ne : [{ $type : '$DictionaryInterface.life' }, 'missing'] }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, true, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_Max_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Max());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $max : { $let : { vars : { this : { $objectToArray : '$DictionaryInterface' } }, in : '$$this.v' } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(100, 85, 100, 200);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_Select_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Select(kvp => kvp.Value).Sum());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : '$$kvp.v' } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_Sum_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Sum(kvp => kvp.Value));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $sum : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : '$$kvp.v' } } }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.Should().Equal(167, 156, 135, 330);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_ValuesContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $reduce : { input : { $objectToArray : '$DictionaryInterface' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 42] } } } } }, _id : 0 } }");
+
+ var results = queryable.ToList();
+ results.Should().Equal(true, false, false, false);
+ }
+
+ [Fact]
+ public void Select_IDictionaryAsDocument_Where_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Select(x => x.DictionaryInterface.Where(kvp => kvp.Value == 35).Any());
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $project : { _v : { $gt : [{ $size : { $filter : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', cond : { $eq : ['$$kvp.v', 35] } } } }, 0] }, _id : 0 } }");
+
+ var result = queryable.ToList();
+ result.Should().Equal(false, false, true, false);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_All_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $not : { $elemMatch : { '1' : { $not : { $gt : 100 } } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle().Which.Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { '1' : { $gt : 90 } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(3);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { '0' : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Should().OnlyContain(doc => doc.Dictionary.ContainsKey("life"));
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_ContainsValue_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsValue(25));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { '1' : 25 } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle()
+ .Which.Name.Should().Be("A");
+ }
+
+ [Theory]
+ [InlineData(2, 2)]
+ [InlineData(3, 0)]
+ public void Where_DictionaryAsArrayOfArrays_Count_should_work(int threshold, int expectedCount)
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Count > threshold);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, $"{{ $match : {{ 'Dictionary.{threshold}' : {{ $exists : true }} }} }}");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(expectedCount);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Count(kvp => kvp.Value < 50) == 2);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $sum : { $map : { input : '$Dictionary', as : 'kvp', in : { $cond : { if : { $lt : [{ $arrayElemAt : ['$$kvp', 1] }, 50] }, then : 1, else : 0 } } } } }, 2] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_First_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.First(kvp => kvp.Key.StartsWith("l")).Value > 40);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $gt : [{ $arrayElemAt : [{ $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : [{ $indexOfCP : [{ $arrayElemAt : ['$$kvp', 0] }, 'l'] }, 0] } } }, 0] }, 1] }, 40] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Select(x => x.Name).Should().BeEquivalentTo("A", "B");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary["life"] == 42);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $let : { vars : { this : { $arrayToObject : '$Dictionary' } }, in : '$$this.life' } }, 42] } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ result.First().Name.Should().Be("A");
+ result.First().Dictionary["life"].Should().Be(42);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { '0' : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_OrderBy_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("age"))
+ .OrderBy(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { Dictionary : { $elemMatch : { '0' : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayToObject : '$Dictionary' } }, in : '$$this.age' } } } }",
+ "{ $sort : { _key1 : 1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("A");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_OrderByDescending_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("age"))
+ .OrderByDescending(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { Dictionary : { $elemMatch : { '0' : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayToObject : '$Dictionary' } }, in : '$$this.age' } } } }",
+ "{ $sort : { _key1 : -1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfArrays_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { '1' : 42 } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_All_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $not : { $elemMatch : { v : { $not : { $gt : 100 } } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle().Which.Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { v : { $gt : 90 } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(3);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { k : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Should().OnlyContain(doc => doc.Dictionary.ContainsKey("life"));
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_ContainsValue_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsValue(25));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { v : 25 } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle()
+ .Which.Name.Should().Be("A");
+ }
+
+ [Theory]
+ [InlineData(2, 2)]
+ [InlineData(3, 0)]
+ public void Where_DictionaryAsArrayOfDocuments_Count_should_work(int threshold, int expectedCount)
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Count > threshold);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, $"{{ $match : {{ 'Dictionary.{threshold}' : {{ $exists : true }} }} }}");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(expectedCount);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Count(kvp => kvp.Value < 50) == 2);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $sum : { $map : { input : '$Dictionary', as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, 2] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_First_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.First(kvp => kvp.Key.StartsWith("l")).Value > 40);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $gt : [{ $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } }, 0] } }, in : '$$this.v' } }, 40] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Select(x => x.Name).Should().BeEquivalentTo("A", "B");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary["life"] == 42);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : ['$$kvp.k', 'life'] } } }, 0] } }, in : '$$this.v' } }, 42] } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ result[0].Name.Should().Be("A");
+ result[0].Dictionary["life"].Should().Be(42);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { k : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_OrderBy_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("age"))
+ .OrderBy(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { Dictionary : { $elemMatch : { k : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } } } }",
+ "{ $sort : { _key1 : 1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("A");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_OrderByDescending_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("age"))
+ .OrderByDescending(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { Dictionary : { $elemMatch : { k : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$Dictionary', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } } } }",
+ "{ $sort : { _key1 : -1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsArrayOfDocuments_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { Dictionary : { $elemMatch : { v : 42 } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_All_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $allElementsTrue : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : { $gt : ['$$kvp.v', 100] } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle().Which.Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_Any_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $anyElementTrue : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : { $gt : ['$$kvp.v', 90] } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(3);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_ContainsKey_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { 'Dictionary.life' : { $exists : true } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Should().OnlyContain(doc => doc.Dictionary.ContainsKey("life"));
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_ContainsValue_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsValue(25));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $reduce : { input : { $objectToArray : '$Dictionary' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 25] } } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle()
+ .Which.Name.Should().Be("A");
+ }
+
+ [Theory]
+ [InlineData(2, 2)]
+ [InlineData(3, 0)]
+ public void Where_DictionaryAsDocument_Count_should_work(int threshold, int expectedCount)
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Count > threshold);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, $"{{ $match : {{ $expr : {{ $gt : [{{ $size : {{ $objectToArray : '$Dictionary' }} }}, {threshold}] }} }} }}");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(expectedCount);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Count(kvp => kvp.Value < 50) == 2);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $sum : { $map : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, 2] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_First_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.First(kvp => kvp.Key.StartsWith("l")).Value > 40);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $gt : [{ $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $objectToArray : '$Dictionary' }, as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } }, 0] } }, in : '$$this.v' } }, 40] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Select(x => x.Name).Should().BeEquivalentTo("A", "B");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_IndexerAccess_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary["life"] == 42);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { 'Dictionary.life' : 42 } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ result[0].Name.Should().Be("A");
+ result[0].Dictionary["life"].Should().Be(42);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_KeysContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { 'Dictionary.life' : { $exists : true } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_OrderBy_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("age"))
+ .OrderBy(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { 'Dictionary.age' : { $exists : true } } }",
+ "{ $sort : { 'Dictionary.age' : 1 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("A");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_OrderByDescending_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.ContainsKey("age"))
+ .OrderByDescending(x => x.Dictionary["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { 'Dictionary.age' : { $exists : true } } }",
+ "{ $sort : { 'Dictionary.age' : -1 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_DictionaryAsDocument_ValuesContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.Dictionary.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $reduce : { input : { $objectToArray : '$Dictionary' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 42] } } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_All_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $not : { $elemMatch : { '1' : { $not : { $gt : 100 } } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle().Which.Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { '1' : { $gt : 90 } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(3);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { '0' : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Should().OnlyContain(doc => doc.DictionaryInterface.ContainsKey("life"));
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_Count_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Count == 3);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $size : 3 } } }");
+
+ var results = queryable.ToList();
+ results.Select(x => x.Name).Should().Equal("A", "B");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Count(kvp => kvp.Value < 50) == 2);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $sum : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $cond : { if : { $lt : [{ $arrayElemAt : ['$$kvp', 1] }, 50] }, then : 1, else : 0 } } } } }, 2] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_First_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.First(kvp => kvp.Key.StartsWith("l")).Value > 40);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $gt : [{ $arrayElemAt : [{ $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : [{ $indexOfCP : [{ $arrayElemAt : ['$$kvp', 0] }, 'l'] }, 0] } } }, 0] }, 1] }, 40] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Select(x => x.Name).Should().BeEquivalentTo("A", "B");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface["life"] == 42);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $let : { vars : { this : { $arrayToObject : '$DictionaryInterface' } }, in : '$$this.life' } }, 42] } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ result.First().Name.Should().Be("A");
+ result.First().DictionaryInterface["life"].Should().Be(42);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { '0' : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_OrderBy_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("age"))
+ .OrderBy(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { DictionaryInterface : { $elemMatch : { '0' : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayToObject : '$DictionaryInterface' } }, in : '$$this.age' } } } }",
+ "{ $sort : { _key1 : 1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("A");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_OrderByDescending_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("age"))
+ .OrderByDescending(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { DictionaryInterface : { $elemMatch : { '0' : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayToObject : '$DictionaryInterface' } }, in : '$$this.age' } } } }",
+ "{ $sort : { _key1 : -1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfArrays_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfArraysCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { '1' : 42 } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_All_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $not : { $elemMatch : { v : { $not : { $gt : 100 } } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle().Which.Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_Any_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { v : { $gt : 90 } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(3);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_ContainsKey_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { k : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Should().OnlyContain(doc => doc.DictionaryInterface.ContainsKey("life"));
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_Count_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Count == 3);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $size : 3 } } }");
+
+ var results = queryable.ToList();
+ results.Select(x => x.Name).Should().Equal("A", "B");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Count(kvp => kvp.Value < 50) == 2);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $sum : { $map : { input : '$DictionaryInterface', as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, 2] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_First_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.First(kvp => kvp.Key.StartsWith("l")).Value > 40);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $gt : [{ $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } }, 0] } }, in : '$$this.v' } }, 40] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Select(x => x.Name).Should().BeEquivalentTo("A", "B");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_IndexerAccess_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface["life"] == 42);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : ['$$kvp.k', 'life'] } } }, 0] } }, in : '$$this.v' } }, 42] } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ result[0].Name.Should().Be("A");
+ result[0].DictionaryInterface["life"].Should().Be(42);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_KeysContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { k : 'life' } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_OrderBy_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("age"))
+ .OrderBy(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { DictionaryInterface : { $elemMatch : { k : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } } } }",
+ "{ $sort : { _key1 : 1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("A");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_OrderByDescending_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("age"))
+ .OrderByDescending(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { DictionaryInterface : { $elemMatch : { k : 'age' } } } }",
+ "{ $project : { _id : 0, _document : '$$ROOT', _key1 : { $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : '$DictionaryInterface', as : 'kvp', cond : { $eq : ['$$kvp.k', 'age'] } } }, 0] } }, in : '$$this.v' } } } }",
+ "{ $sort : { _key1 : -1 } }",
+ "{ $replaceRoot : { newRoot : '$_document' } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsArrayOfDocuments_ValuesContains_should_work()
+ {
+ var collection = Fixture.ArrayOfDocsCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { DictionaryInterface : { $elemMatch : { v : 42 } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_All_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.All(kvp => kvp.Value > 100));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $allElementsTrue : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : { $gt : ['$$kvp.v', 100] } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle().Which.Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_Any_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Any(kvp => kvp.Value > 90));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $anyElementTrue : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : { $gt : ['$$kvp.v', 90] } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(3);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_ContainsKey_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { 'DictionaryInterface.life' : { $exists : true } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Should().OnlyContain(doc => doc.DictionaryInterface.ContainsKey("life"));
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_Count_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Count == 3);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $size : { $objectToArray : '$DictionaryInterface' } }, 3] } } }");
+
+ var results = queryable.ToList();
+ results.Select(x => x.Name).Should().Equal("A", "B");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_CountWithPredicate_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Count(kvp => kvp.Value < 50) == 2);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $eq : [{ $sum : { $map : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', in : { $cond : { if : { $lt : ['$$kvp.v', 50] }, then : 1, else : 0 } } } } }, 2] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_First_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.First(kvp => kvp.Key.StartsWith("l")).Value > 40);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $gt : [{ $let : { vars : { this : { $arrayElemAt : [{ $filter : { input : { $objectToArray : '$DictionaryInterface' }, as : 'kvp', cond : { $eq : [{ $indexOfCP : ['$$kvp.k', 'l'] }, 0] } } }, 0] } }, in : '$$this.v' } }, 40] } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ result.Select(x => x.Name).Should().BeEquivalentTo("A", "B");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_IndexerAccess_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface["life"] == 42);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { 'DictionaryInterface.life' : 42 } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ result[0].Name.Should().Be("A");
+ result[0].DictionaryInterface["life"].Should().Be(42);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_KeysContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Keys.Contains("life"));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { 'DictionaryInterface.life' : { $exists : true } } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(2);
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_OrderBy_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("age"))
+ .OrderBy(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { 'DictionaryInterface.age' : { $exists : true } } }",
+ "{ $sort : { 'DictionaryInterface.age' : 1 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("A");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_OrderByDescending_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.ContainsKey("age"))
+ .OrderByDescending(x => x.DictionaryInterface["age"]);
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages,
+ "{ $match : { 'DictionaryInterface.age' : { $exists : true } } }",
+ "{ $sort : { 'DictionaryInterface.age' : -1 } }");
+
+ var result = queryable.ToList();
+ result.Should().HaveCount(4);
+ result.First().Name.Should().Be("D");
+ }
+
+ [Fact]
+ public void Where_IDictionaryAsDocument_ValuesContains_should_work()
+ {
+ var collection = Fixture.DocCollection;
+
+ var queryable = collection.AsQueryable()
+ .Where(x => x.DictionaryInterface.Values.Contains(42));
+
+ var stages = Translate(collection, queryable);
+ AssertStages(stages, "{ $match : { $expr : { $reduce : { input : { $objectToArray : '$DictionaryInterface' }, initialValue : false, in : { $cond : { if : '$$value', then : true, else : { $eq : ['$$this.v', 42] } } } } } } }");
+
+ var result = queryable.ToList();
+ result.Should().ContainSingle();
+ }
+
+ public class DocumentRepresentation
+ {
+ public ObjectId Id { get; set; }
+ public string Name { get; set; }
+
+ [BsonDictionaryOptions(DictionaryRepresentation.Document)]
+ public Dictionary Dictionary { get; set; }
+
+ [BsonDictionaryOptions(DictionaryRepresentation.Document)]
+ public IDictionary DictionaryInterface { get; set; }
+ }
+
+ public class ArrayOfArraysRepresentation
+ {
+ public ObjectId Id { get; set; }
+ public string Name { get; set; }
+
+ [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
+ public Dictionary Dictionary { get; set; }
+
+ [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)]
+ public IDictionary DictionaryInterface { get; set; }
+ }
+
+ public class ArrayOfDocumentsRepresentation
+ {
+ public ObjectId Id { get; set; }
+ public string Name { get; set; }
+
+ [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
+ public Dictionary Dictionary { get; set; }
+
+ [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)]
+ public IDictionary DictionaryInterface { get; set; }
+ }
+
+ public sealed class ClassFixture : MongoDatabaseFixture
+ {
+ public IMongoCollection DocCollection { get; private set; }
+ public IMongoCollection ArrayOfArraysCollection { get; private set; }
+ public IMongoCollection ArrayOfDocsCollection { get; private set; }
+
+ protected override void InitializeFixture()
+ {
+ DocCollection = Database.GetCollection("test_document");
+ ArrayOfArraysCollection = Database.GetCollection("test_array_of_arrays");
+ ArrayOfDocsCollection = Database.GetCollection("test_array_of_docs");
+
+ SeedTestDictionary();
+ }
+
+ private void SeedTestDictionary()
+ {
+ // Clear existing Dictionary
+ DocCollection.DeleteMany(FilterDefinition.Empty);
+ ArrayOfArraysCollection.DeleteMany(FilterDefinition.Empty);
+ ArrayOfDocsCollection.DeleteMany(FilterDefinition.Empty);
+
+ // Insert test Dictionary for Document representation
+ var docDictionary = new List
+ {
+ new()
+ {
+ Name = "A",
+ Dictionary = new Dictionary { { "life", 42 }, { "age", 25 }, { "score", 100 } },
+ DictionaryInterface = new Dictionary { { "life", 42 }, { "age", 25 }, { "score", 100 } }
+ },
+ new()
+ {
+ Name = "B",
+ Dictionary = new Dictionary { { "life", 41 }, { "age", 30 }, { "score", 85 } },
+ DictionaryInterface = new Dictionary { { "life", 41 }, { "age", 30 }, { "score", 85 } }
+ },
+ new()
+ {
+ Name = "C",
+ Dictionary = new Dictionary { { "health", 100 }, { "age", 35 } },
+ DictionaryInterface = new Dictionary { { "health", 100 }, { "age", 35 } }
+ },
+ new()
+ {
+ Name = "D",
+ Dictionary = new Dictionary { { "health", 200 }, { "age", 130 } },
+ DictionaryInterface = new Dictionary { { "health", 200 }, { "age", 130 } }
+ }
+ };
+ DocCollection.InsertMany(docDictionary);
+
+ // Insert test Dictionary for ArrayOfArrays representation
+ var arrayDictionary = new List
+ {
+ new()
+ {
+ Name = "A",
+ Dictionary = new Dictionary { { "life", 42 }, { "age", 25 }, { "score", 100 } },
+ DictionaryInterface = new Dictionary { { "life", 42 }, { "age", 25 }, { "score", 100 } }
+ },
+ new()
+ {
+ Name = "B",
+ Dictionary = new Dictionary { { "life", 41 }, { "age", 30 }, { "score", 85 } },
+ DictionaryInterface = new Dictionary { { "life", 41 }, { "age", 30 }, { "score", 85 } }
+ },
+ new()
+ {
+ Name = "C",
+ Dictionary = new Dictionary { { "health", 100 }, { "age", 35 } },
+ DictionaryInterface = new Dictionary { { "health", 100 }, { "age", 35 } }
+ },
+ new()
+ {
+ Name = "D",
+ Dictionary = new Dictionary { { "health", 200 }, { "age", 130 } },
+ DictionaryInterface = new Dictionary { { "health", 200 }, { "age", 130 } }
+ }
+ };
+ ArrayOfArraysCollection.InsertMany(arrayDictionary);
+
+ // Insert test Dictionary for ArrayOfDocuments representation
+ var arrayDocDictionary = new List
+ {
+ new()
+ {
+ Name = "A",
+ Dictionary = new Dictionary { { "life", 42 }, { "age", 25 }, { "score", 100 } },
+ DictionaryInterface = new Dictionary { { "life", 42 }, { "age", 25 }, { "score", 100 } }
+ },
+ new()
+ {
+ Name = "B",
+ Dictionary = new Dictionary { { "life", 41 }, { "age", 30 }, { "score", 85 } },
+ DictionaryInterface = new Dictionary { { "life", 41 }, { "age", 30 }, { "score", 85 } }
+ },
+ new()
+ {
+ Name = "C",
+ Dictionary = new Dictionary { { "health", 100 }, { "age", 35 } },
+ DictionaryInterface = new Dictionary { { "health", 100 }, { "age", 35 } }
+ },
+ new()
+ {
+ Name = "D",
+ Dictionary = new Dictionary { { "health", 200 }, { "age", 130 } },
+ DictionaryInterface = new Dictionary { { "health", 200 }, { "age", 130 } }
+ }
+ };
+ ArrayOfDocsCollection.InsertMany(arrayDocDictionary);
+ }
+ }
+}
\ No newline at end of file
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4557Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4557Tests.cs
deleted file mode 100644
index 1c761e8a73e..00000000000
--- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4557Tests.cs
+++ /dev/null
@@ -1,78 +0,0 @@
-/* Copyright 2010-present MongoDB Inc.
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-* http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
-
-using System.Collections.Generic;
-using System.Linq;
-using FluentAssertions;
-using MongoDB.Driver.TestHelpers;
-using Xunit;
-
-namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
-{
- public class CSharp4557Tests : LinqIntegrationTest
- {
- public CSharp4557Tests(ClassFixture fixture)
- : base(fixture)
- {
- }
-
- [Fact]
- public void Where_with_ContainsKey_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection
- .AsQueryable()
- .Where(x => x.Foo.ContainsKey("bar"));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { 'Foo.bar' : { $exists : true } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(2);
- }
-
- [Fact]
- public void Select_with_ContainsKey_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection
- .AsQueryable()
- .Select(x => x.Foo.ContainsKey("bar"));
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $ne : [{ $type : '$Foo.bar' }, 'missing'] }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(false, true);
- }
-
- public class C
- {
- public int Id { get; set; }
- public Dictionary Foo { get; set; }
- }
-
- public sealed class ClassFixture : MongoCollectionFixture
- {
- protected override IEnumerable InitialData =>
- [
- new C { Id = 1, Foo = new Dictionary { { "foo", 100 } } },
- new C { Id = 2, Foo = new Dictionary { { "bar", 100 } } }
- ];
- }
- }
-}
diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4813Tests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4813Tests.cs
index 645237a3a33..cd72c16f41a 100644
--- a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4813Tests.cs
+++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharp4813Tests.cs
@@ -17,8 +17,6 @@
using System.Collections.Generic;
using System.Linq;
using FluentAssertions;
-using MongoDB.Bson.Serialization.Attributes;
-using MongoDB.Bson.Serialization.Options;
using MongoDB.Driver.Core.Misc;
using MongoDB.Driver.Core.TestHelpers.XunitExtensions;
using MongoDB.Driver.Linq;
@@ -62,90 +60,6 @@ public void Where_Count_should_work()
results.Select(x => x.Id).Should().Equal(1);
}
- [Fact]
- public void Where_Dictionary_Count_should_throw()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.Dictionary.Count == 1);
-
- var exception = Record.Exception(() => Translate(collection, queryable));
- exception.Should().BeOfType();
- }
-
- [Fact]
- public void Where_DictionaryAsArrayOfArrays_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.DictionaryAsArrayOfArrays.Count == 1);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { DictionaryAsArrayOfArrays : { $size : 1 } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(1);
- }
-
- [Fact]
- public void Where_DictionaryAsArrayOfDocuments_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.DictionaryAsArrayOfDocuments.Count == 1);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { DictionaryAsArrayOfDocuments : { $size : 1 } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(1);
- }
-
- [Fact]
- public void Where_DictionaryInterface_Count_should_throw()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.DictionaryInterface.Count == 1);
-
- var exception = Record.Exception(() => Translate(collection, queryable));
- exception.Should().BeOfType();
- }
-
- [Fact]
- public void Where_DictionaryInterfaceArrayOfArrays_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.DictionaryInterfaceAsArrayOfArrays.Count == 1);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { DictionaryInterfaceAsArrayOfArrays : { $size : 1 } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(1);
- }
-
- [Fact]
- public void Where_DictionaryInterfaceArrayOfDocuments_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Where(x => x.DictionaryInterfaceAsArrayOfDocuments.Count == 1);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $match : { DictionaryInterfaceAsArrayOfDocuments : { $size : 1 } } }");
-
- var results = queryable.ToList();
- results.Select(x => x.Id).Should().Equal(1);
- }
-
[Fact]
public void Where_List_Count_should_work()
{
@@ -220,124 +134,6 @@ public void Select_Count_should_work()
results.Should().Equal(1, 2);
}
- [Theory]
- [ParameterAttributeData]
- public void Select_Dictionary_Count_should_throw(
- [Values(false, true)] bool enableClientSideProjections)
- {
- RequireServer.Check().Supports(Feature.FindProjectionExpressions);
- var collection = Fixture.Collection;
- var translationOptions = new ExpressionTranslationOptions { EnableClientSideProjections = enableClientSideProjections };
-
- var queryable = collection.AsQueryable(translationOptions)
- .Select(x => x.Dictionary.Count);
-
- if (enableClientSideProjections)
- {
- var stages = Translate(collection, queryable, out var outputSerializer);
- AssertStages(stages, "{ $project : { _snippets : ['$Dictionary'], _id : 0 } }");
- outputSerializer.Should().BeAssignableTo();
-
- var results = queryable.ToList();
- results.Should().Equal(1, 2);
- }
- else
- {
- var exception = Record.Exception(() => Translate(collection, queryable));
- exception.Should().BeOfType();
- exception.Message.Should().Contain("is not represented as an array");
- }
- }
-
- [Fact]
- public void Select_DictionaryAsArrayOfArrays_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Select(x => x.DictionaryAsArrayOfArrays.Count);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $size : '$DictionaryAsArrayOfArrays' }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(1, 2);
- }
-
- [Fact]
- public void Select_DictionaryAsArrayOfDocuments_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Select(x => x.DictionaryAsArrayOfDocuments.Count);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $size : '$DictionaryAsArrayOfDocuments' }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(1, 2);
- }
-
- [Theory]
- [ParameterAttributeData]
- public void Select_DictionaryInterface_Count_should_throw(
- [Values(false, true)] bool enableClientSideProjections)
- {
- RequireServer.Check().Supports(Feature.FindProjectionExpressions);
- var collection = Fixture.Collection;
- var translationOptions = new ExpressionTranslationOptions { EnableClientSideProjections = enableClientSideProjections };
-
- var queryable = collection.AsQueryable(translationOptions)
- .Select(x => x.DictionaryInterface.Count);
-
- if (enableClientSideProjections)
- {
- var stages = Translate(collection, queryable, out var outputSerializer);
- AssertStages(stages, "{ $project : { _snippets : ['$DictionaryInterface'], _id : 0 } }");
- outputSerializer.Should().BeAssignableTo();
-
- var results = queryable.ToList();
- results.Should().Equal(1, 2);
- }
- else
- {
- var exception = Record.Exception(() => Translate(collection, queryable));
- exception.Should().BeOfType();
- exception.Message.Should().Contain("is not represented as an array");
- }
- }
-
- [Fact]
- public void Select_DictionaryInterfaceAsArrayOfArrays_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Select(x => x.DictionaryInterfaceAsArrayOfArrays.Count);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $size : '$DictionaryInterfaceAsArrayOfArrays' }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(1, 2);
- }
-
- [Fact]
- public void Select_DictionaryInterfaceAsArrayOfDocuments_Count_should_work()
- {
- var collection = Fixture.Collection;
-
- var queryable = collection.AsQueryable()
- .Select(x => x.DictionaryInterfaceAsArrayOfDocuments.Count);
-
- var stages = Translate(collection, queryable);
- AssertStages(stages, "{ $project : { _v : { $size : '$DictionaryInterfaceAsArrayOfDocuments' }, _id : 0 } }");
-
- var results = queryable.ToList();
- results.Should().Equal(1, 2);
- }
-
[Fact]
public void Select_List_Count_should_work()
{
@@ -373,12 +169,6 @@ public class C
public int Id { get; set; }
public BitArray BitArray { get; set; }
public int Count { get; set; }
- public Dictionary Dictionary { get; set; }
- [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)] public Dictionary DictionaryAsArrayOfArrays { get; set; }
- [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)] public Dictionary DictionaryAsArrayOfDocuments { get; set; }
- public IDictionary DictionaryInterface { get; set; }
- [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfArrays)] public IDictionary DictionaryInterfaceAsArrayOfArrays { get; set; }
- [BsonDictionaryOptions(DictionaryRepresentation.ArrayOfDocuments)] public IDictionary DictionaryInterfaceAsArrayOfDocuments { get; set; }
public List List { get; set; }
public IList ListInterface { get; set; }
}
@@ -392,12 +182,6 @@ public sealed class ClassFixture : MongoCollectionFixture
Id = 1,
BitArray = new BitArray(length: 1),
Count = 1,
- Dictionary = new() { { "A", 1 } },
- DictionaryAsArrayOfArrays = new() { { "A", 1 } },
- DictionaryAsArrayOfDocuments = new() { { "A", 1 } },
- DictionaryInterface = new Dictionary { { "A", 1 } },
- DictionaryInterfaceAsArrayOfArrays = new Dictionary { { "A", 1 } },
- DictionaryInterfaceAsArrayOfDocuments = new Dictionary { { "A", 1 } },
List = new() { 1 },
ListInterface = new List() { 1 }
},
@@ -406,12 +190,6 @@ public sealed class ClassFixture : MongoCollectionFixture
Id = 2,
BitArray = new BitArray(length: 2),
Count = 2,
- Dictionary = new() { { "A", 1 }, { "B", 2 } },
- DictionaryAsArrayOfArrays = new() { { "A", 1 }, { "B", 2 } },
- DictionaryAsArrayOfDocuments = new() { { "A", 1 }, { "B", 2 } },
- DictionaryInterface = new Dictionary { { "A", 1 }, { "B", 2 } },
- DictionaryInterfaceAsArrayOfArrays = new Dictionary { { "A", 1 }, { "B", 2 } },
- DictionaryInterfaceAsArrayOfDocuments = new Dictionary { { "A", 1 }, { "B", 2 } },
List = new() { 1, 2 },
ListInterface = new List { 1, 2 }
}