diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/KeyValuePairMethod.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/KeyValuePairMethod.cs new file mode 100644 index 00000000000..39bbd7e86ce --- /dev/null +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/KeyValuePairMethod.cs @@ -0,0 +1,39 @@ +/* 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.Reflection; + +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + +namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection +{ + internal static class KeyValuePairMethod + { + // private static fields + private static readonly MethodInfo __create; + + // static constructor + static KeyValuePairMethod() + { + __create = ReflectionInfo.Method((object key, object value) => KeyValuePair.Create(key, value)); + } + + // public properties + public static MethodInfo Create => __create; + } +} + +#endif diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CreateMethodToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CreateMethodToAggregationExpressionTranslator.cs index 67bace3f0c5..30ab3c04fbe 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CreateMethodToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MethodTranslators/CreateMethodToAggregationExpressionTranslator.cs @@ -19,6 +19,7 @@ using System.Reflection; using MongoDB.Bson.Serialization; using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Linq.Linq3Implementation.Ast; using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; using MongoDB.Driver.Linq.Linq3Implementation.Misc; using MongoDB.Driver.Linq.Linq3Implementation.Reflection; @@ -87,6 +88,14 @@ public static TranslatedExpression Translate(TranslationContext context, MethodC return new TranslatedExpression(expression, ast, tupleSerializer); } +#if NET6_0_OR_GREATER || NETSTANDARD2_1_OR_GREATER + if (method.Is(KeyValuePairMethod.Create)) + { + var keyExpression = arguments[0]; + var valueExpression = arguments[1]; + return NewKeyValuePairExpressionToAggregationExpressionTranslator.Translate(context, expression, keyExpression, valueExpression); + } +#endif throw new ExpressionNotSupportedException(expression); } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewExpressionToAggregationExpressionTranslator.cs index 81637463fb7..b54f431e516 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewExpressionToAggregationExpressionTranslator.cs @@ -46,6 +46,10 @@ public static TranslatedExpression Translate(TranslationContext context, NewExpr { return NewTupleExpressionToAggregationExpressionTranslator.Translate(context, expression); } + if (NewKeyValuePairExpressionToAggregationExpressionTranslator.CanTranslate(expression)) + { + return NewKeyValuePairExpressionToAggregationExpressionTranslator.Translate(context, expression); + } return MemberInitExpressionToAggregationExpressionTranslator.Translate(context, expression, expression, Array.Empty()); } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewKeyValuePairExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewKeyValuePairExpressionToAggregationExpressionTranslator.cs new file mode 100644 index 00000000000..8116c5b55b9 --- /dev/null +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewKeyValuePairExpressionToAggregationExpressionTranslator.cs @@ -0,0 +1,71 @@ +/* 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.Expressions; +using MongoDB.Bson.Serialization; +using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; + +namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators +{ + internal static class NewKeyValuePairExpressionToAggregationExpressionTranslator + { + public static bool CanTranslate(NewExpression expression) + => expression.Type.IsConstructedGenericType && expression.Type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>); + + public static TranslatedExpression Translate(TranslationContext context, NewExpression expression) + { + var arguments = expression.Arguments; + var keyExpression = arguments[0]; + var valueExpression = arguments[1]; + return Translate(context, expression, keyExpression, valueExpression); + } + + public static TranslatedExpression Translate( + TranslationContext context, + Expression expression, + Expression keyExpression, + Expression valueExpression) + { + var keyTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, keyExpression); + var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression); + + var ast = AstExpression.ComputedDocument([ + AstExpression.ComputedField("Key", keyTranslation.Ast), + AstExpression.ComputedField("Value", valueTranslation.Ast) + ]); + + var serializer = CreateResultSerializer(expression.Type, keyTranslation.Serializer, valueTranslation.Serializer); + + return new TranslatedExpression(expression, ast, serializer); + } + + private static IBsonSerializer CreateResultSerializer(Type resultType, IBsonSerializer keySerializer, IBsonSerializer valueSerializer) + { + var constructorInfo = resultType.GetConstructor([keySerializer.ValueType, valueSerializer.ValueType]); + var classMap = new BsonClassMap(resultType); + classMap.MapConstructor(constructorInfo); + classMap.AutoMap(); + classMap.GetMemberMap("Key").SetSerializer(keySerializer); + classMap.GetMemberMap("Value").SetSerializer(valueSerializer); + classMap.Freeze(); + + // have to use BsonClassMapSerializer here to mimic the MemberInitExpressionToAggregationExpressionTranslator to avoid risking a behavioral breaking change + var serializerType = typeof(BsonClassMapSerializer<>).MakeGenericType(resultType); + return (IBsonSerializer)Activator.CreateInstance(serializerType, classMap); + } + } +} diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewKeyValuePairExpressionToAggregationExpressionTranslatorTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewKeyValuePairExpressionToAggregationExpressionTranslatorTests.cs new file mode 100644 index 00000000000..765f031469f --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/NewKeyValuePairExpressionToAggregationExpressionTranslatorTests.cs @@ -0,0 +1,78 @@ +/* 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.Translators.ExpressionToAggregationExpressionTranslators; + +#if NET6_0_OR_GREATER || NETCOREAPP3_1_OR_GREATER +public class NewKeyValuePairExpressionToAggregationExpressionTranslatorTests : LinqIntegrationTest +{ + public NewKeyValuePairExpressionToAggregationExpressionTranslatorTests(ClassFixture fixture) + : base(fixture) + { + } + + [Fact] + public void NewKeyValuePair_should_translate() + { + var collection = Fixture.Collection; + + var queryable = collection.AsQueryable() + .Select(d => new KeyValuePair("X", d.X)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $project : { Key : 'X', Value : '$X', _id : 0 } }"); + + var result = queryable.Single(); + result.Key.Should().Be("X"); + result.Value.Should().Be(42); + } + + [Fact] + public void KeyValuePair_Create_should_translate() + { + var collection = Fixture.Collection; + + var queryable = collection.AsQueryable() + .Select(d => KeyValuePair.Create("X", d.X)); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $project : { Key : 'X', Value : '$X', _id : 0 } }"); + + var result = queryable.Single(); + result.Key.Should().Be("X"); + result.Value.Should().Be(42); + } + + public class C + { + public int X { get; set; } + } + + public sealed class ClassFixture : MongoCollectionFixture + { + protected override IEnumerable InitialData => + [ + new C { X = 42 } + ]; + } +} + +#endif