Skip to content

Commit cfb662f

Browse files
committed
CSHARP-4876: Sort OfType in nested expressions.
1 parent 8ac275a commit cfb662f

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ public static AggregationExpression Translate(TranslationContext context, Method
6363
case "IsNullOrWhiteSpace": return IsNullOrWhiteSpaceMethodToAggregationExpressionTranslator.Translate(context, expression);
6464
case "IsSubsetOf": return IsSubsetOfMethodToAggregationExpressionTranslator.Translate(context, expression);
6565
case "Locf": return LocfMethodToAggregationExpressionTranslator.Translate(context, expression);
66+
case "OfType": return OfTypeMethodToAggregationExpressionTranslator.Translate(context, expression);
6667
case "Parse": return ParseMethodToAggregationExpressionTranslator.Translate(context, expression);
6768
case "Pow": return PowMethodToAggregationExpressionTranslator.Translate(context, expression);
6869
case "Push": return PushMethodToAggregationExpressionTranslator.Translate(context, expression);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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.Linq.Expressions;
17+
using System.Reflection;
18+
using MongoDB.Bson.Serialization;
19+
using MongoDB.Bson.Serialization.Conventions;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
23+
using MongoDB.Driver.Linq.Linq3Implementation.Serializers;
24+
25+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
26+
{
27+
internal static class OfTypeMethodToAggregationExpressionTranslator
28+
{
29+
private static readonly MethodInfo[] __ofTypeMethods =
30+
{
31+
EnumerableMethod.OfType,
32+
QueryableMethod.OfType
33+
};
34+
35+
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
36+
{
37+
var method = expression.Method;
38+
var arguments = expression.Arguments;
39+
40+
if (method.IsOneOf(__ofTypeMethods))
41+
{
42+
var sourceExpression = arguments[0];
43+
var sourceTranslation = ExpressionToAggregationExpressionTranslator.TranslateEnumerable(context, sourceExpression);
44+
NestedAsQueryableHelper.EnsureQueryableMethodHasNestedAsQueryableSource(expression, sourceTranslation);
45+
var itemSerializer = ArraySerializerHelper.GetItemSerializer(sourceTranslation.Serializer);
46+
47+
var nominalType = sourceTranslation.Serializer.ValueType;
48+
var actualType = method.GetGenericArguments()[0];
49+
50+
if (nominalType == actualType)
51+
{
52+
return sourceTranslation;
53+
}
54+
55+
var discriminatorConvention = itemSerializer.GetDiscriminatorConvention();
56+
var discriminatorElementName = discriminatorConvention.ElementName;
57+
58+
var itemVar = AstExpression.Var("this");
59+
var discriminatorField = AstExpression.GetField(itemVar, discriminatorElementName);
60+
var ofTypePredicate = discriminatorConvention switch
61+
{
62+
IHierarchicalDiscriminatorConvention hierarchicalDiscriminatorConvention => DiscriminatorAstExpression.TypeIs(discriminatorField, hierarchicalDiscriminatorConvention, nominalType, actualType),
63+
IScalarDiscriminatorConvention scalarDiscriminatorConvention => DiscriminatorAstExpression.TypeIs(discriminatorField, scalarDiscriminatorConvention, nominalType, actualType),
64+
_ => throw new ExpressionNotSupportedException(expression, because: "is operator is not supported with the configured discriminator convention")
65+
};
66+
67+
var ast = AstExpression.Filter(
68+
input: sourceTranslation.Ast,
69+
@as: itemVar.Name,
70+
cond: ofTypePredicate);
71+
var actualTypeSerializer = BsonSerializer.LookupSerializer(actualType);
72+
var resultSerializer = NestedAsQueryableSerializer.CreateIEnumerableOrNestedAsQueryableSerializer(expression.Type, actualTypeSerializer);
73+
74+
return new AggregationExpression(expression, ast, resultSerializer);
75+
}
76+
77+
throw new ExpressionNotSupportedException(expression);
78+
}
79+
}
80+
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
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.Linq;
17+
using FluentAssertions;
18+
using MongoDB.Bson.Serialization.Attributes;
19+
using MongoDB.TestHelpers.XunitExtensions;
20+
using Xunit;
21+
22+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
23+
{
24+
public class CSharp4876Tests : Linq3IntegrationTest
25+
{
26+
[Theory]
27+
[ParameterAttributeData]
28+
public void OfType_should_work(
29+
[Values(false, true)] bool withNestedAsQueryable)
30+
{
31+
var collection = GetCollection();
32+
33+
var queryable = withNestedAsQueryable ?
34+
collection.AsQueryable().Select(x => x.A.AsQueryable().OfType<B2>().ToArray()) :
35+
collection.AsQueryable().Select(x => x.A.OfType<B2>().ToArray());
36+
37+
var stages = Translate(collection, queryable);
38+
AssertStages(stages, "{ $project : { _v : { $filter : { input : '$A', as : 'this', cond : { $cond : { if : { $eq : [{ $type : '$$this._t' }, 'array'] }, then : { $in : ['B2', '$$this._t'] }, else : { $eq : ['$$this._t', 'B2'] } } } } }, _id : 0 } }");
39+
40+
var result = queryable.Single();
41+
result.Select(x => x.Id).Should().Equal(2);
42+
}
43+
44+
private IMongoCollection<C> GetCollection()
45+
{
46+
var collection = GetCollection<C>("test");
47+
CreateCollection(
48+
collection,
49+
new C
50+
{
51+
Id = 1, A =
52+
[
53+
new B1 { Id = 1 },
54+
new B2 { Id = 2 }
55+
]
56+
});
57+
return collection;
58+
}
59+
60+
private class C
61+
{
62+
public int Id { get; set; }
63+
public B[] A { get; set; }
64+
}
65+
66+
[BsonDiscriminator(RootClass = true)]
67+
public class B
68+
{
69+
public int Id { get; set; }
70+
}
71+
72+
public class B1 : B
73+
{
74+
}
75+
76+
public class B2 : B
77+
{
78+
}
79+
}
80+
}

0 commit comments

Comments
 (0)