Skip to content

Commit 8365a61

Browse files
committed
CSHARP-4813: Verify more carefully that property named Count is a collection method.
1 parent a322f26 commit 8365a61

File tree

17 files changed

+1080
-63
lines changed

17 files changed

+1080
-63
lines changed

src/MongoDB.Bson/Serialization/Serializers/DiscriminatedInterfaceSerializer.cs

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,26 @@
1919

2020
namespace MongoDB.Bson.Serialization.Serializers
2121
{
22+
/// <summary>
23+
/// An interface implemented by DiscriminatedInterfaceSerializer.
24+
/// </summary>
25+
public interface IDiscriminatedInterfaceSerializer
26+
{
27+
/// <summary>
28+
/// Gets the interface serializer.
29+
/// </summary>
30+
IBsonSerializer InterfaceSerializer { get; }
31+
}
32+
2233
/// <summary>
2334
/// Represents a serializer for Interfaces.
2435
/// </summary>
2536
/// <typeparam name="TInterface">The type of the interface.</typeparam>
26-
public class DiscriminatedInterfaceSerializer<TInterface> : SerializerBase<TInterface>, IBsonDocumentSerializer // where TInterface is an interface
37+
public class DiscriminatedInterfaceSerializer<TInterface> :
38+
SerializerBase<TInterface>,
39+
IBsonDocumentSerializer,
40+
IDiscriminatedInterfaceSerializer
41+
// where TInterface is an interface
2742
{
2843
#region static
2944
private static IBsonSerializer<TInterface> CreateInterfaceSerializer()
@@ -97,6 +112,14 @@ public DiscriminatedInterfaceSerializer(IDiscriminatorConvention discriminatorCo
97112
_interfaceSerializer = interfaceSerializer;
98113
}
99114

115+
// public properties
116+
/// <summary>
117+
/// Gets the interface serializer.
118+
/// </summary>
119+
public IBsonSerializer<TInterface> InterfaceSerializer => _interfaceSerializer;
120+
121+
IBsonSerializer IDiscriminatedInterfaceSerializer.InterfaceSerializer => _interfaceSerializer;
122+
100123
// public methods
101124
/// <summary>
102125
/// Deserializes a value.

src/MongoDB.Bson/Serialization/Serializers/ImpliedImplementationInterfaceSerializer.cs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,22 @@
1414
*/
1515

1616
using System;
17-
using System.Collections.Generic;
18-
using System.Linq;
1917
using System.Reflection;
2018
using MongoDB.Bson.Serialization.Options;
2119

2220
namespace MongoDB.Bson.Serialization.Serializers
2321
{
22+
/// <summary>
23+
/// An interface implemented by ImpliedImplementationInterfaceSerializer.
24+
/// </summary>
25+
public interface IImpliedImplementationInterfaceSerializer
26+
{
27+
/// <summary>
28+
/// Gets the serializer for the implied implementation.
29+
/// </summary>
30+
IBsonSerializer ImplementationSerializer { get; }
31+
}
32+
2433
/// <summary>
2534
/// Represents a serializer for Interfaces.
2635
/// </summary>
@@ -31,7 +40,8 @@ public class ImpliedImplementationInterfaceSerializer<TInterface, TImplementatio
3140
IBsonArraySerializer,
3241
IBsonDictionarySerializer,
3342
IBsonDocumentSerializer,
34-
IChildSerializerConfigurable
43+
IChildSerializerConfigurable,
44+
IImpliedImplementationInterfaceSerializer
3545
where TImplementation : class, TInterface
3646
{
3747
// private fields
@@ -144,6 +154,8 @@ public IBsonSerializer<TImplementation> ImplementationSerializer
144154
get { return _lazyImplementationSerializer.Value; }
145155
}
146156

157+
IBsonSerializer IImpliedImplementationInterfaceSerializer.ImplementationSerializer => ImplementationSerializer;
158+
147159
/// <summary>
148160
/// Gets the value serializer.
149161
/// </summary>

src/MongoDB.Bson/Serialization/Serializers/KeyValuePairSerializer.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,21 +15,29 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18-
using System.IO;
19-
using MongoDB.Bson.IO;
20-
using MongoDB.Bson.Serialization.Conventions;
21-
using MongoDB.Bson.Serialization.Options;
2218

2319
namespace MongoDB.Bson.Serialization.Serializers
2420
{
21+
/// <summary>
22+
/// An interface implemented by KeyValuePairSerializer.
23+
/// </summary>
24+
public interface IKeyValuePairSerializer
25+
{
26+
/// <summary>
27+
/// Gets the representation.
28+
/// </summary>
29+
BsonType Representation { get; }
30+
}
31+
2532
/// <summary>
2633
/// Represents a serializer for KeyValuePairs.
2734
/// </summary>
2835
/// <typeparam name="TKey">The type of the keys.</typeparam>
2936
/// <typeparam name="TValue">The type of the values.</typeparam>
3037
public class KeyValuePairSerializer<TKey, TValue> :
3138
StructSerializerBase<KeyValuePair<TKey, TValue>>,
32-
IBsonDocumentSerializer
39+
IBsonDocumentSerializer,
40+
IKeyValuePairSerializer
3341
{
3442
// private constants
3543
private static class Flags

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,67 @@
1414
*/
1515

1616
using System;
17+
using MongoDB.Bson;
1718
using MongoDB.Bson.Serialization;
19+
using MongoDB.Bson.Serialization.Serializers;
1820

1921
namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2022
{
2123
internal static class DocumentSerializerHelper
2224
{
25+
public static bool AreMembersRepresentedAsFields(IBsonSerializer serializer, out IBsonDocumentSerializer documentSerializer)
26+
{
27+
if (serializer is IDiscriminatedInterfaceSerializer discriminatedInterfaceSerializer)
28+
{
29+
return AreMembersRepresentedAsFields(discriminatedInterfaceSerializer.InterfaceSerializer, out documentSerializer);
30+
}
31+
32+
if (serializer is IDowncastingSerializer downcastingSerializer)
33+
{
34+
return AreMembersRepresentedAsFields(downcastingSerializer.DerivedSerializer, out documentSerializer);
35+
}
36+
37+
if (serializer is IImpliedImplementationInterfaceSerializer impliedImplementationSerializer)
38+
{
39+
return AreMembersRepresentedAsFields(impliedImplementationSerializer.ImplementationSerializer, out documentSerializer);
40+
}
41+
42+
if (serializer is IBsonDictionarySerializer)
43+
{
44+
documentSerializer = null;
45+
return false;
46+
}
47+
48+
if (serializer is IKeyValuePairSerializer keyValuePairSerializer)
49+
{
50+
if (keyValuePairSerializer.Representation == BsonType.Document)
51+
{
52+
documentSerializer = (IBsonDocumentSerializer)keyValuePairSerializer;
53+
return true;
54+
}
55+
else
56+
{
57+
documentSerializer = null;
58+
return false;
59+
}
60+
}
61+
62+
// for backward compatibility assume that any remaining implementers of IBsonDocumentSerializer represent members as fields
63+
if (serializer is IBsonDocumentSerializer tempDocumentSerializer)
64+
{
65+
documentSerializer = tempDocumentSerializer;
66+
return true;
67+
}
68+
69+
documentSerializer = null;
70+
return false;
71+
}
72+
2373
public static MemberSerializationInfo GetMemberSerializationInfo(IBsonSerializer serializer, string memberName)
2474
{
25-
if (!(serializer is IBsonDocumentSerializer documentSerializer))
75+
if (!AreMembersRepresentedAsFields(serializer, out var documentSerializer))
2676
{
27-
throw new NotSupportedException($"Serializer for {serializer.ValueType} must implement IBsonDocumentSerializer to be used with LINQ.");
77+
throw new NotSupportedException($"Serializer for {serializer.ValueType} does not represent members as fields.");
2878
}
2979

3080
if (!(documentSerializer.TryGetMemberSerializationInfo(memberName, out BsonSerializationInfo serializationInfo)))
@@ -41,12 +91,5 @@ public static MemberSerializationInfo GetMemberSerializationInfo(IBsonSerializer
4191
return new MemberSerializationInfo(serializationInfo.ElementPath, serializationInfo.Serializer);
4292
}
4393
}
44-
45-
public static bool HasMemberSerializationInfo(IBsonSerializer serializer, string memberName)
46-
{
47-
return
48-
serializer is IBsonDocumentSerializer documentSerializer &&
49-
documentSerializer.TryGetMemberSerializationInfo(memberName, out var _);
50-
}
5194
}
5295
}

src/MongoDB.Driver/Linq/Linq3Implementation/Misc/SerializationHelper.cs

Lines changed: 70 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,31 +13,34 @@
1313
* limitations under the License.
1414
*/
1515

16-
using System;
1716
using System.Collections;
1817
using System.Linq.Expressions;
1918
using MongoDB.Bson;
2019
using MongoDB.Bson.IO;
2120
using MongoDB.Bson.Serialization;
21+
using MongoDB.Bson.Serialization.Options;
22+
using MongoDB.Bson.Serialization.Serializers;
2223
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
2324

2425
namespace MongoDB.Driver.Linq.Linq3Implementation.Misc
2526
{
2627
internal static class SerializationHelper
2728
{
28-
public static void EnsureRepresentationIsNumeric(Expression expression, IBsonSerializer serializer)
29+
public static void EnsureRepresentationIsArray(Expression expression, IBsonSerializer serializer)
2930
{
30-
if (serializer is IRepresentationConfigurable representationConfigurableSerializer)
31+
var representation = GetRepresentation(serializer);
32+
if (representation != BsonType.Array)
3133
{
32-
EnsureRepresentationIsNumeric(expression, serializer.ValueType, representationConfigurableSerializer.Representation);
34+
throw new ExpressionNotSupportedException(expression, because: "the expression is not represented as an array in the database");
3335
}
36+
}
3437

35-
static void EnsureRepresentationIsNumeric(Expression expression, Type valueType, BsonType representation)
38+
public static void EnsureRepresentationIsNumeric(Expression expression, IBsonSerializer serializer)
39+
{
40+
var representation = GetRepresentation(serializer);
41+
if (!IsNumericRepresentation(representation))
3642
{
37-
if (!IsNumericRepresentation(representation))
38-
{
39-
throw new ExpressionNotSupportedException(expression, because: $"serializer for type {valueType} uses a non-numeric representation: {representation}");
40-
}
43+
throw new ExpressionNotSupportedException(expression, because: $"serializer for type {serializer.ValueType} uses a non-numeric representation: {representation}");
4144
}
4245

4346
static bool IsNumericRepresentation(BsonType representation)
@@ -50,6 +53,64 @@ static bool IsNumericRepresentation(BsonType representation)
5053
}
5154
}
5255

56+
public static BsonType GetRepresentation(IBsonSerializer serializer)
57+
{
58+
if (serializer is IDiscriminatedInterfaceSerializer discriminatedInterfaceSerializer)
59+
{
60+
return GetRepresentation(discriminatedInterfaceSerializer.InterfaceSerializer);
61+
}
62+
63+
if (serializer is IDowncastingSerializer downcastingSerializer)
64+
{
65+
return GetRepresentation(downcastingSerializer.DerivedSerializer);
66+
}
67+
68+
if (serializer is IImpliedImplementationInterfaceSerializer impliedImplementationSerializer)
69+
{
70+
return GetRepresentation(impliedImplementationSerializer.ImplementationSerializer);
71+
}
72+
73+
if (serializer is IHasRepresentationSerializer hasRepresentationSerializer)
74+
{
75+
return hasRepresentationSerializer.Representation;
76+
}
77+
78+
if (serializer is IBsonDictionarySerializer dictionarySerializer)
79+
{
80+
return dictionarySerializer.DictionaryRepresentation switch
81+
{
82+
DictionaryRepresentation.ArrayOfArrays => BsonType.Array,
83+
DictionaryRepresentation.ArrayOfDocuments => BsonType.Array,
84+
DictionaryRepresentation.Document => BsonType.Document,
85+
_ => BsonType.Undefined
86+
};
87+
}
88+
89+
if (serializer is IKeyValuePairSerializer keyValuePairSerializer)
90+
{
91+
return keyValuePairSerializer.Representation;
92+
}
93+
94+
// for backward compatibility assume that any remaining implementers of IBsonDocumentSerializer are represented as documents
95+
if (serializer is IBsonDocumentSerializer)
96+
{
97+
return BsonType.Document;
98+
}
99+
100+
// for backward compatibility assume that any remaining implementers of IBsonArraySerializer are represented as documents
101+
if (serializer is IBsonArraySerializer)
102+
{
103+
return BsonType.Array;
104+
}
105+
106+
return BsonType.Undefined;
107+
}
108+
109+
public static bool IsRepresentedAsDocument(IBsonSerializer serializer)
110+
{
111+
return SerializationHelper.GetRepresentation(serializer) == BsonType.Document;
112+
}
113+
53114
public static BsonValue SerializeValue(IBsonSerializer serializer, ConstantExpression constantExpression, Expression containingExpression)
54115
{
55116
var value = constantExpression.Value;
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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;
18+
using System.Collections.Generic;
19+
using System.Linq.Expressions;
20+
using System.Reflection;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
22+
23+
namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
24+
{
25+
internal static class EnumerableProperty
26+
{
27+
// public methods
28+
public static bool IsCountProperty(MemberExpression expression)
29+
{
30+
// Count is not actually a property defined by IEnumerable but rather defined by several sub-interfaces
31+
return
32+
expression.Expression != null &&
33+
expression.Member is PropertyInfo propertyInfo &&
34+
propertyInfo.Name == "Count" &&
35+
propertyInfo.PropertyType == typeof(int) &&
36+
propertyInfo.GetGetMethod().GetParameters().Length == 0 &&
37+
ImplementsCollectionInterface(expression.Expression.Type);
38+
39+
static bool ImplementsCollectionInterface(Type type)
40+
=>
41+
type.Implements(typeof(ICollection)) ||
42+
type.Implements(typeof(ICollection<>)) ||
43+
type.Implements(typeof(IReadOnlyCollection<>));
44+
}
45+
}
46+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Serializers/KnownSerializers/KnownSerializerFinder.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,13 +112,13 @@ protected override Expression VisitMember(MemberExpression node)
112112
var result = base.VisitMember(node);
113113

114114
var containerSerializer = _registry.GetSerializer(node.Expression);
115-
if (containerSerializer is IBsonDocumentSerializer documentSerializer)
115+
if (DocumentSerializerHelper.AreMembersRepresentedAsFields(containerSerializer, out var documentSerializer))
116116
{
117117
if (documentSerializer.TryGetMemberSerializationInfo(node.Member.Name, out var memberSerializationInfo))
118118
{
119119
_currentKnownSerializersNode.AddKnownSerializer(node.Type, memberSerializationInfo.Serializer);
120120

121-
if (memberSerializationInfo.Serializer is IBsonDocumentSerializer bsonDocumentSerializer)
121+
if (DocumentSerializerHelper.AreMembersRepresentedAsFields(memberSerializationInfo.Serializer, out var bsonDocumentSerializer))
122122
{
123123
_currentSerializer = bsonDocumentSerializer;
124124
}
@@ -199,7 +199,7 @@ protected override Expression VisitParameter(ParameterExpression node)
199199
if (_currentSerializer is IBsonArraySerializer arraySerializer &&
200200
arraySerializer.TryGetItemSerializationInfo(out var itemSerializationInfo) &&
201201
node.Type == itemSerializationInfo.NominalType &&
202-
itemSerializationInfo.Serializer is IBsonDocumentSerializer documentSerializer)
202+
DocumentSerializerHelper.AreMembersRepresentedAsFields(itemSerializationInfo.Serializer, out var documentSerializer))
203203
{
204204
_currentSerializer = documentSerializer;
205205
_currentKnownSerializersNode.AddKnownSerializer(node.Type, documentSerializer);

0 commit comments

Comments
 (0)