-
Couldn't load subscription status.
- Fork 1.3k
CSHARP-1913: Support using Dictionary fields as IEnumerable<KeyValuePair<TKey, TValue>> #1800
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 3 commits
eafd5ab
2cf31c2
27d21ba
60558b7
867ab5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,7 +16,9 @@ | |
| using System; | ||
| using System.Linq; | ||
| using System.Linq.Expressions; | ||
| using MongoDB.Bson; | ||
| using MongoDB.Bson.Serialization; | ||
| using MongoDB.Bson.Serialization.Options; | ||
| using MongoDB.Bson.Serialization.Serializers; | ||
| using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; | ||
| using MongoDB.Driver.Linq.Linq3Implementation.Misc; | ||
|
|
@@ -95,15 +97,28 @@ public static TranslatedExpression TranslateEnumerable(TranslationContext contex | |
| { | ||
| var aggregateExpression = Translate(context, expression); | ||
|
|
||
| var serializer = aggregateExpression.Serializer; | ||
| if (serializer is IWrappedEnumerableSerializer wrappedEnumerableSerializer) | ||
| if (aggregateExpression.Serializer is IWrappedEnumerableSerializer wrappedEnumerableSerializer) | ||
| { | ||
| var enumerableFieldName = wrappedEnumerableSerializer.EnumerableFieldName; | ||
| var enumerableElementSerializer = wrappedEnumerableSerializer.EnumerableElementSerializer; | ||
| var enumerableSerializer = IEnumerableSerializer.Create(enumerableElementSerializer); | ||
|
|
||
| var ast = AstExpression.GetField(aggregateExpression.Ast, enumerableFieldName); | ||
| var ienumerableSerializer = IEnumerableSerializer.Create(enumerableElementSerializer); | ||
|
|
||
| aggregateExpression = new TranslatedExpression(expression, ast, ienumerableSerializer); | ||
| } | ||
|
|
||
| if (aggregateExpression.Serializer is IBsonDictionarySerializer dictionarySerializer && | ||
| dictionarySerializer.DictionaryRepresentation == DictionaryRepresentation.Document) | ||
| { | ||
| var keySerializer = dictionarySerializer.KeySerializer; | ||
| var valueSerializer = dictionarySerializer.ValueSerializer; | ||
| var keyValuePairSerializer = KeyValuePairSerializer.Create(BsonType.Document, keySerializer, valueSerializer); | ||
|
|
||
| var ast = AstExpression.ObjectToArray(aggregateExpression.Ast); | ||
| var ienumerableSerializer = ArraySerializerHelper.CreateSerializer(keyValuePairSerializer); | ||
|
|
||
| return new TranslatedExpression(aggregateExpression.Expression, ast, enumerableSerializer); | ||
| aggregateExpression = new TranslatedExpression(expression, ast, ienumerableSerializer); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Both of the above cases share this in common: they are both cases where an enumerable value has the values represented in some unusual way other than as a The first case corresponds to The second is new in this PR, and corresponds to a |
||
| } | ||
|
|
||
| return aggregateExpression; | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,8 +14,13 @@ | |
| */ | ||
|
|
||
| using System.Collections.ObjectModel; | ||
| using System.Linq; | ||
| using System.Linq.Expressions; | ||
| using System.Reflection; | ||
| using MongoDB.Bson; | ||
| using MongoDB.Bson.Serialization; | ||
| using MongoDB.Bson.Serialization.Options; | ||
| using MongoDB.Bson.Serialization.Serializers; | ||
| using MongoDB.Driver.Linq.Linq3Implementation.Ast; | ||
| using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions; | ||
| using MongoDB.Driver.Linq.Linq3Implementation.Ast.Stages; | ||
|
|
@@ -74,16 +79,23 @@ private static TranslatedPipeline TranslateSelectMany( | |
| { | ||
| var sourceSerializer = pipeline.OutputSerializer; | ||
| var selectorLambda = ExpressionHelper.UnquoteLambda(arguments[1]); | ||
| var selectorTranslation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, selectorLambda, sourceSerializer, asRoot: true); | ||
| var resultValueSerializer = ArraySerializerHelper.GetItemSerializer(selectorTranslation.Serializer); | ||
| var resultWrappedValueSerializer = WrappedValueSerializer.Create("_v", resultValueSerializer); | ||
| var selectorParameter = selectorLambda.Parameters.Single(); | ||
| var selectorParameterSymbol = context.CreateSymbol(selectorParameter, sourceSerializer, isCurrent: true); | ||
| var selectorContext = context.WithSymbol(selectorParameterSymbol); | ||
| var selectorTranslation = ExpressionToAggregationExpressionTranslator.TranslateEnumerable(selectorContext, selectorLambda.Body); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Need to call |
||
|
|
||
| var valuesAst = selectorTranslation.Ast; | ||
| var valuesSerializer = selectorTranslation.Serializer; | ||
|
|
||
| var valuesItemSerializer = ArraySerializerHelper.GetItemSerializer(valuesSerializer); | ||
| var wrappedValueSerializer = WrappedValueSerializer.Create("_v", valuesItemSerializer); | ||
|
|
||
| pipeline = pipeline.AddStages( | ||
| AstStage.Project( | ||
| AstProject.Set("_v", selectorTranslation.Ast), | ||
| AstProject.Set("_v", valuesAst), | ||
| AstProject.ExcludeId()), | ||
| AstStage.Unwind("_v"), | ||
| resultWrappedValueSerializer); | ||
| wrappedValueSerializer); | ||
|
|
||
| return pipeline; | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,204 @@ | ||
| /* 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 MongoDB.Driver.TestHelpers; | ||
| using FluentAssertions; | ||
| using MongoDB.Bson.Serialization.Attributes; | ||
| using MongoDB.Bson.Serialization.Options; | ||
| using Xunit; | ||
|
|
||
| namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira; | ||
|
|
||
| public class CSharp1913Tests : LinqIntegrationTest<CSharp1913Tests.ClassFixture> | ||
| { | ||
| public CSharp1913Tests(ClassFixture fixture) | ||
| : base(fixture) | ||
| { | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Nested__SelectMany_with_ArrayOfArrays_representation_should_work() | ||
| { | ||
| var collection = Fixture.Collection; | ||
|
|
||
| var queryable = collection.AsQueryable() | ||
| .OfType<C>() | ||
| .Where( to => to.Name == "TestName" ) | ||
| .Select( to => to.DictionaryWithArrayOfArraysRepresentation.SelectMany(kvp => new KeyValuePair<string, string>[] { kvp }) ); | ||
|
|
||
| var stages = Translate(collection, queryable); | ||
| AssertStages( | ||
| stages, | ||
| "{ $match : { Name : 'TestName' } }", | ||
| "{ $project : { _v : { $reduce : { input : { $map : { input : '$DictionaryWithArrayOfArraysRepresentation', as : 'kvp', in : ['$$kvp'] } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }"); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would have liked to use There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I have getting the |
||
|
|
||
| var result = queryable.Single(); | ||
| result.Select(kvp => kvp.Key).Should().Equal("A", "B", "C"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Nested__SelectMany_with_ArrayOfDocuments_representation_should_work() | ||
| { | ||
| var collection = Fixture.Collection; | ||
|
|
||
| var queryable = collection.AsQueryable() | ||
| .OfType<C>() | ||
| .Where( to => to.Name == "TestName" ) | ||
| .Select( to => to.DictionaryWithArrayOfDocumentsRepresentation.SelectMany(kvp => new KeyValuePair<string, string>[] { kvp }) ); | ||
|
|
||
| var stages = Translate(collection, queryable); | ||
| AssertStages( | ||
| stages, | ||
| "{ $match : { Name : 'TestName' } }", | ||
| "{ $project : { _v : { $reduce : { input : { $map : { input : '$DictionaryWithArrayOfDocumentsRepresentation', as : 'kvp', in : ['$$kvp'] } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }"); | ||
|
|
||
| var result = queryable.Single(); | ||
| result.Select(kvp => kvp.Key).Should().Equal("A", "B", "C"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Nested_SelectMany_with_Document_representation_should_work() | ||
| { | ||
| var collection = Fixture.Collection; | ||
|
|
||
| var queryable = collection.AsQueryable() | ||
| .OfType<C>() | ||
| .Where( to => to.Name == "TestName" ) | ||
| .Select( to => to.DictionaryWithDocumentRepresentation.SelectMany(kvp => new KeyValuePair<string, string>[] { kvp }) ); | ||
|
|
||
| var stages = Translate(collection, queryable); | ||
| AssertStages( | ||
| stages, | ||
| "{ $match : { Name : 'TestName' } }", | ||
| "{ $project : { _v : { $reduce : { input : { $map : { input : { $objectToArray : '$DictionaryWithDocumentRepresentation' }, as : 'kvp', in : ['$$kvp'] } }, initialValue : [], in : { $concatArrays : ['$$value', '$$this'] } } }, _id : 0 } }"); | ||
|
|
||
| var result = queryable.Single(); | ||
| result.Select(kvp => kvp.Key).Should().Equal("A", "B", "C"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Top_level_SelectMany_with_ArrayOfArrays_representation_should_work() | ||
| { | ||
| var collection = Fixture.Collection; | ||
|
|
||
| var queryable = collection.AsQueryable() | ||
| .OfType<C>() | ||
| .Where( to => to.Name == "TestName" ) | ||
| .SelectMany( to => to.DictionaryWithArrayOfArraysRepresentation ); | ||
|
|
||
| var stages = Translate(collection, queryable); | ||
| AssertStages( | ||
| stages, | ||
| "{ $match : { Name : 'TestName' } }", | ||
| "{ $project : { _v : '$DictionaryWithArrayOfArraysRepresentation', _id : 0 } }", | ||
| "{ $unwind : '$_v' }"); | ||
|
|
||
| var results = queryable.ToList(); | ||
| results.Count.Should().Be(3); | ||
| results[0].Key.Should().Be("A"); | ||
| results[0].Value.Should().Be("a"); | ||
| results[1].Key.Should().Be("B"); | ||
| results[1].Value.Should().Be("b"); | ||
| results[2].Key.Should().Be("C"); | ||
| results[2].Value.Should().Be("c"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Top_level_SelectMany_with_ArrayOfDocuments_representation_should_work() | ||
| { | ||
| var collection = Fixture.Collection; | ||
|
|
||
| var queryable = collection.AsQueryable() | ||
| .OfType<C>() | ||
| .Where( to => to.Name == "TestName" ) | ||
| .SelectMany( to => to.DictionaryWithArrayOfDocumentsRepresentation ); | ||
|
|
||
| var stages = Translate(collection, queryable); | ||
| AssertStages( | ||
| stages, | ||
| "{ $match : { Name : 'TestName' } }", | ||
| "{ $project : { _v : '$DictionaryWithArrayOfDocumentsRepresentation', _id : 0 } }", | ||
| "{ $unwind : '$_v' }"); | ||
|
|
||
| var results = queryable.ToList(); | ||
| results.Count.Should().Be(3); | ||
| results[0].Key.Should().Be("A"); | ||
| results[0].Value.Should().Be("a"); | ||
| results[1].Key.Should().Be("B"); | ||
| results[1].Value.Should().Be("b"); | ||
| results[2].Key.Should().Be("C"); | ||
| results[2].Value.Should().Be("c"); | ||
| } | ||
|
|
||
| [Fact] | ||
| public void Top_level_SelectMany_with_Document_representation_should_work() | ||
| { | ||
| var collection = Fixture.Collection; | ||
|
|
||
| var queryable = collection.AsQueryable() | ||
| .OfType<C>() | ||
| .Where( to => to.Name == "TestName" ) | ||
| .SelectMany( to => to.DictionaryWithDocumentRepresentation); | ||
|
|
||
| var stages = Translate(collection, queryable); | ||
| AssertStages( | ||
| stages, | ||
| "{ $match : { Name : 'TestName' } }", | ||
| "{ $project : { _v : { $objectToArray : '$DictionaryWithDocumentRepresentation' }, _id : 0 } }", | ||
| "{ $unwind : '$_v' }"); | ||
|
|
||
| var results = queryable.ToList(); | ||
| results.Count.Should().Be(3); | ||
| results[0].Key.Should().Be("A"); | ||
| results[0].Value.Should().Be("a"); | ||
| results[1].Key.Should().Be("B"); | ||
| results[1].Value.Should().Be("b"); | ||
| results[2].Key.Should().Be("C"); | ||
| results[2].Value.Should().Be("c"); | ||
| } | ||
|
|
||
| public class C | ||
| { | ||
| public int Id { get; set; } | ||
| public string Name {get;set;} | ||
|
|
||
| [BsonDictionaryOptions( DictionaryRepresentation.ArrayOfArrays )] | ||
| public Dictionary<string,string> DictionaryWithArrayOfArraysRepresentation { get; set; } = new Dictionary<string, string>(); | ||
|
|
||
|
|
||
|
||
| [BsonDictionaryOptions( DictionaryRepresentation.ArrayOfDocuments )] | ||
| public Dictionary<string,string> DictionaryWithArrayOfDocumentsRepresentation { get; set; } = new Dictionary<string, string>(); | ||
|
|
||
| [BsonDictionaryOptions( DictionaryRepresentation.Document )] | ||
| public Dictionary<string,string> DictionaryWithDocumentRepresentation { get; set; } = new Dictionary<string, string>(); | ||
| } | ||
|
|
||
| public sealed class ClassFixture : MongoCollectionFixture<C> | ||
| { | ||
| protected override IEnumerable<C> InitialData => | ||
| [ | ||
| new C | ||
| { | ||
| Id = 1, | ||
| Name = "TestName", | ||
| DictionaryWithArrayOfArraysRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } }, | ||
| DictionaryWithArrayOfDocumentsRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } }, | ||
| DictionaryWithDocumentRepresentation = new Dictionary<string, string> { { "A", "a" }, { "B", "b" }, { "C", "c" } }, | ||
| } | ||
| ]; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We were sort of working with ArrayOfDocuments representation, but this adds support for ArrayOfArrays.