Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -499,21 +499,20 @@ obj is DictionarySerializerBase<TDictionary, TKey, TValue> other &&
/// <inheritdoc/>
public bool TryGetItemSerializationInfo(out BsonSerializationInfo serializationInfo)
{
if (_dictionaryRepresentation != DictionaryRepresentation.ArrayOfDocuments)
if (_dictionaryRepresentation is DictionaryRepresentation.ArrayOfArrays or DictionaryRepresentation.ArrayOfDocuments)
Copy link
Contributor Author

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.

{
serializationInfo = null;
return false;
var representation = _dictionaryRepresentation == DictionaryRepresentation.ArrayOfArrays
? BsonType.Array
: BsonType.Document;
var keySerializer = _lazyKeySerializer.Value;
var valueSerializer = _lazyValueSerializer.Value;
var keyValuePairSerializer = new KeyValuePairSerializer<TKey, TValue>(representation, keySerializer, valueSerializer);
serializationInfo = new BsonSerializationInfo(null, keyValuePairSerializer, keyValuePairSerializer.ValueType);
return true;
}

var serializer = new KeyValuePairSerializer<TKey, TValue>(
BsonType.Document,
_lazyKeySerializer.Value,
_lazyValueSerializer.Value);
serializationInfo = new BsonSerializationInfo(
null,
serializer,
serializer.ValueType);
return true;
serializationInfo = null;
return false;
}

/// <inheritdoc/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The 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 BsonArray.

The first case corresponds to IGrouping<TKey, TElement> where the elements are wrapped one level deeper in an _elements field.

The second is new in this PR, and corresponds to a Dictionary<TKey, TValue> with the Document representation. In order to use this dictionary as an enumerable of KeyValuePair<TKey, TValue> we have to insert a call to $objectToArray.

}

return aggregateExpression;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Need to call TranslateEnumerable instead of Translate.


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;
}
Expand Down
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 } }");
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would have liked to use kvp.Key or kvp.Value in the test test but that is not (yet) supported when the KeyValuePair is represented as an array. So I just used the entire KeyValuePair.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have getting the Key or Value of a KeyValuePair represented as an array supported in my work for full support of dictionary representations. So I'll probably update this test when I rebase on this PR.


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>();


Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove extra spacing here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought you meant the extra spaces after ( and before ).

Now I've removed the extra blank line also.

[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" } },
}
];
}
}
Loading