Skip to content

Commit 0349851

Browse files
committed
CSHARP-4731: Support assigning List<T> value to IList<T> property in Select.
1 parent 10b0a25 commit 0349851

File tree

2 files changed

+145
-2
lines changed

2 files changed

+145
-2
lines changed

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
using MongoDB.Bson.Serialization;
2222
using MongoDB.Driver.Linq.Linq3Implementation.Ast;
2323
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
24+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
2425

2526
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators
2627
{
@@ -57,7 +58,8 @@ public static AggregationExpression Translate(
5758
var constructorArgumentSerializer = constructorArgumentTranslation.Serializer ?? BsonSerializer.LookupSerializer(constructorArgumentType);
5859
var memberMap = EnsureMemberMap(expression, classMap, creatorMapParameter);
5960
EnsureDefaultValue(memberMap);
60-
memberMap.SetSerializer(constructorArgumentSerializer);
61+
var memberSerializer = CoerceSourceSerializerToMemberSerializer(memberMap, constructorArgumentSerializer);
62+
memberMap.SetSerializer(memberSerializer);
6163
computedFields.Add(AstExpression.ComputedField(memberMap.ElementName, constructorArgumentTranslation.Ast));
6264
}
6365
}
@@ -69,7 +71,8 @@ public static AggregationExpression Translate(
6971
var memberMap = FindMemberMap(expression, classMap, member.Name);
7072
var valueExpression = memberAssignment.Expression;
7173
var valueTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, valueExpression);
72-
memberMap.SetSerializer(valueTranslation.Serializer);
74+
var memberSerializer = CoerceSourceSerializerToMemberSerializer(memberMap, valueTranslation.Serializer);
75+
memberMap.SetSerializer(memberSerializer);
7376
computedFields.Add(AstExpression.ComputedField(memberMap.ElementName, valueTranslation.Ast));
7477
}
7578

@@ -107,6 +110,27 @@ private static BsonClassMap CreateClassMap(Type classType, ConstructorInfo const
107110
return classMap;
108111
}
109112

113+
private static IBsonSerializer CoerceSourceSerializerToMemberSerializer(BsonMemberMap memberMap, IBsonSerializer sourceSerializer)
114+
{
115+
var memberType = memberMap.MemberType;
116+
var memberSerializer = memberMap.GetSerializer();
117+
var sourceType = sourceSerializer.ValueType;
118+
119+
if (memberType != sourceType &&
120+
memberType.ImplementsIEnumerable(out var memberItemType) &&
121+
sourceType.ImplementsIEnumerable(out var sourceItemType) &&
122+
sourceItemType == memberItemType &&
123+
sourceSerializer is IBsonArraySerializer sourceArraySerializer &&
124+
sourceArraySerializer.TryGetItemSerializationInfo(out var sourceItemSerializationInfo) &&
125+
memberSerializer is IChildSerializerConfigurable memberChildSerializerConfigurable)
126+
{
127+
var sourceItemSerializer = sourceItemSerializationInfo.Serializer;
128+
return memberChildSerializerConfigurable.WithChildSerializer(sourceItemSerializer);
129+
}
130+
131+
return sourceSerializer;
132+
}
133+
110134
private static BsonMemberMap EnsureMemberMap(Expression expression, BsonClassMap classMap, MemberInfo creatorMapParameter)
111135
{
112136
var declaringClassMap = classMap;
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
/* Copyright 2010-present MongoDB Inc.
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using FluentAssertions;
20+
using MongoDB.Bson;
21+
using MongoDB.Bson.Serialization.Attributes;
22+
using MongoDB.Driver.Linq;
23+
using MongoDB.TestHelpers.XunitExtensions;
24+
using Xunit;
25+
26+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
27+
{
28+
public class CSharp4731Tests : Linq3IntegrationTest
29+
{
30+
[Theory]
31+
[ParameterAttributeData]
32+
public void Select_setting_IList_from_List_should_work(
33+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
34+
{
35+
var collection = GetCollection(linqProvider);
36+
37+
var queryable = collection
38+
.AsQueryable()
39+
.Select(x => new P { IList = x.List })
40+
.Where(x => x.IList.Contains(E.A));
41+
42+
if (linqProvider == LinqProvider.V2)
43+
{
44+
var exception = Record.Exception(() => Translate(collection, queryable));
45+
exception.Should().BeOfType<ArgumentException>();
46+
}
47+
else
48+
{
49+
var stages = Translate(collection, queryable);
50+
AssertStages(
51+
stages,
52+
"{ $project : { IList : '$List', _id : 0 } }",
53+
"{ $match : { IList : 'A' } }");
54+
55+
var result = queryable.Single();
56+
result.IList.Should().Equal(E.A, E.B);
57+
}
58+
}
59+
60+
[Theory]
61+
[ParameterAttributeData]
62+
public void Select_setting_IReadOnlyList_from_List_should_work(
63+
[Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider)
64+
{
65+
var collection = GetCollection(linqProvider);
66+
67+
var queryable = collection
68+
.AsQueryable()
69+
.Select(x => new Q { IReadOnlyList = x.List })
70+
.Where(x => x.IReadOnlyList.Contains(E.A));
71+
72+
if (linqProvider == LinqProvider.V2)
73+
{
74+
var exception = Record.Exception(() => Translate(collection, queryable));
75+
exception.Should().BeOfType<ArgumentException>();
76+
}
77+
else
78+
{
79+
var stages = Translate(collection, queryable);
80+
AssertStages(
81+
stages,
82+
"{ $project : { IReadOnlyList : '$List', _id : 0 } }",
83+
"{ $match : { IReadOnlyList : 'A' } }");
84+
85+
var result = queryable.Single();
86+
result.IReadOnlyList.Should().Equal(E.A, E.B);
87+
}
88+
}
89+
90+
private IMongoCollection<Test> GetCollection(LinqProvider linqProvider)
91+
{
92+
var collection = GetCollection<Test>("test", linqProvider);
93+
CreateCollection(
94+
collection,
95+
new Test { Id = 1, List = new List<E> { E.A, E.B } },
96+
new Test { Id = 2, List = new List<E> { E.C, E.D } });
97+
return collection;
98+
}
99+
100+
private class Test
101+
{
102+
public int Id { get; set; }
103+
[BsonRepresentation(BsonType.String)]
104+
public List<E> List { get; set; }
105+
}
106+
107+
private class P
108+
{
109+
public IList<E> IList { get; set; }
110+
}
111+
112+
private class Q
113+
{
114+
public IReadOnlyList<E> IReadOnlyList { get; set; }
115+
}
116+
117+
private enum E { A, B, C, D };
118+
}
119+
}

0 commit comments

Comments
 (0)