Skip to content

Commit 5861edb

Browse files
committed
CSHARP-4063: Support Nullable HasValue and Value properties in LINQ3.
1 parent e37fd92 commit 5861edb

File tree

5 files changed

+164
-0
lines changed

5 files changed

+164
-0
lines changed

src/MongoDB.Driver/Linq/Linq3Implementation/Reflection/NullableProperty.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@ internal static class NullableProperty
2121
{
2222
// private static fields
2323
private static readonly PropertyInfo __hasValue;
24+
private static readonly PropertyInfo __value;
2425

2526
// static constructor
2627
static NullableProperty()
2728
{
2829
__hasValue = ReflectionInfo.Property((int? n) => n.HasValue);
30+
__value = ReflectionInfo.Property((int? n) => n.Value);
2931
}
3032

3133
// public properties
3234
public static PropertyInfo HasValue => __hasValue;
35+
public static PropertyInfo Value => __value;
3336
}
3437
}

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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,16 @@ public static AggregationExpression Translate(TranslationContext context, Member
3636
var containerExpression = expression.Expression;
3737
var member = expression.Member;
3838

39+
if (member is PropertyInfo property)
40+
{
41+
switch (property.Name)
42+
{
43+
case "HasValue": return HasValuePropertyToAggregationExpressionTranslator.Translate(context, expression);
44+
case "Value": return ValuePropertyToAggregationExpressionTranslator.Translate(context, expression);
45+
default: break;
46+
}
47+
}
48+
3949
var containerTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, containerExpression);
4050
if (containerTranslation.Serializer is IWrappedValueSerializer wrappedValueSerializer)
4151
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
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;
19+
using MongoDB.Bson.Serialization.Serializers;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
22+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
23+
24+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.PropertyTranslators
25+
{
26+
internal static class HasValuePropertyToAggregationExpressionTranslator
27+
{
28+
public static AggregationExpression Translate(TranslationContext context, MemberExpression expression)
29+
{
30+
if (expression.Member is PropertyInfo propertyInfo && propertyInfo.Is(NullableProperty.HasValue))
31+
{
32+
var containerExpression = expression.Expression;
33+
var containerTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, containerExpression);
34+
var ast = AstExpression.Ne(containerTranslation.Ast, BsonNull.Value);
35+
var serializer = new BooleanSerializer();
36+
return new AggregationExpression(expression, ast, serializer);
37+
}
38+
39+
throw new ExpressionNotSupportedException(expression);
40+
}
41+
}
42+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
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.Driver.Linq.Linq3Implementation.Misc;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
21+
22+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.PropertyTranslators
23+
{
24+
internal static class ValuePropertyToAggregationExpressionTranslator
25+
{
26+
public static AggregationExpression Translate(TranslationContext context, MemberExpression expression)
27+
{
28+
if (expression.Member is PropertyInfo propertyInfo && propertyInfo.Is(NullableProperty.Value))
29+
{
30+
var containerExpression = expression.Expression;
31+
var containerTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, containerExpression);
32+
if (containerTranslation.Serializer is IChildSerializerConfigurable containerSerializer)
33+
{
34+
return new AggregationExpression(expression, containerTranslation.Ast, containerSerializer.ChildSerializer);
35+
}
36+
}
37+
38+
throw new ExpressionNotSupportedException(expression);
39+
}
40+
}
41+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
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 Xunit;
18+
19+
namespace MongoDB.Driver.Tests.Linq.Linq3ImplementationTests.Jira
20+
{
21+
public class CSharp4063Tests : Linq3IntegrationTest
22+
{
23+
[Fact]
24+
public void GroupBy_with_bool_should_work()
25+
{
26+
var collection = GetCollection<C>();
27+
var subject = collection.AsQueryable();
28+
29+
var queryable = subject.GroupBy(
30+
x => x.Id,
31+
(k, g) => new { Value = g.Count(x => x.Bool) });
32+
33+
var stages = Translate(collection, queryable);
34+
var expectedStages = new[]
35+
{
36+
"{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }",
37+
"{ $project : { Value : { $size : { $filter : { input : '$_elements', as : 'x', cond : '$$x.Bool' } } }, _id : 0 } }"
38+
};
39+
AssertStages(stages, expectedStages);
40+
}
41+
42+
[Fact]
43+
public void GroupBy_with_nullable_bool_should_work()
44+
{
45+
var collection = GetCollection<C>();
46+
var subject = collection.AsQueryable();
47+
48+
var queryable = subject.GroupBy(
49+
x => x.Id,
50+
(k, g) => new { Value = g.Count(x => x.NullableBool.HasValue && x.NullableBool.Value) });
51+
52+
var stages = Translate(collection, queryable);
53+
var expectedStages = new[]
54+
{
55+
"{ $group : { _id : '$_id', _elements : { $push : '$$ROOT' } } }",
56+
"{ $project : { Value : { $size : { $filter : { input : '$_elements', as : 'x', cond : { $and : [{ $ne : ['$$x.NullableBool', null] }, '$$x.NullableBool'] } } } }, _id : 0 } }"
57+
};
58+
AssertStages(stages, expectedStages);
59+
}
60+
61+
private class C
62+
{
63+
public int Id { get; set; }
64+
public bool Bool { get; set; }
65+
public bool? NullableBool { get; set; }
66+
}
67+
}
68+
}

0 commit comments

Comments
 (0)