Skip to content

Commit e93dd86

Browse files
committed
CSHARP-5421: Add Mql.Field so EF Core Provider can access shadow properties.
1 parent 42e08b5 commit e93dd86

File tree

8 files changed

+225
-1
lines changed

8 files changed

+225
-1
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ internal static class PartialEvaluator
3232
typeof(DateTimeExtensions),
3333
typeof(LinqExtensions),
3434
typeof(MongoEnumerable),
35+
typeof(Mql),
3536
typeof(StringExtensions)
3637
};
3738

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using System.Reflection;
18+
using MongoDB.Bson.Serialization;
1819

1920
namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
2021
{
@@ -26,6 +27,7 @@ internal static class MqlMethod
2627
private static readonly MethodInfo __dateFromStringWithFormatAndTimezone;
2728
private static readonly MethodInfo __dateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull;
2829
private static readonly MethodInfo __exists;
30+
private static readonly MethodInfo __field;
2931
private static readonly MethodInfo __isMissing;
3032
private static readonly MethodInfo __isNullOrMissing;
3133

@@ -37,6 +39,7 @@ static MqlMethod()
3739
__dateFromStringWithFormatAndTimezone = ReflectionInfo.Method((string dateString, string format, string timezone) => Mql.DateFromString(dateString, format, timezone));
3840
__dateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull = ReflectionInfo.Method((string dateString, string format, string timezone, DateTime? onError, DateTime? onNull) => Mql.DateFromString(dateString, format, timezone, onError, onNull));
3941
__exists = ReflectionInfo.Method((object field) => Mql.Exists(field));
42+
__field = ReflectionInfo.Method((object container, string fieldName, IBsonSerializer<object> serializer) => Mql.Field<object, object>(container, fieldName, serializer));
4043
__isMissing = ReflectionInfo.Method((object field) => Mql.IsMissing(field));
4144
__isNullOrMissing = ReflectionInfo.Method((object field) => Mql.IsNullOrMissing(field));
4245
}
@@ -47,6 +50,7 @@ static MqlMethod()
4750
public static MethodInfo DateFromStringWithFormatAndTimezone => __dateFromStringWithFormatAndTimezone;
4851
public static MethodInfo DateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull => __dateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull;
4952
public static MethodInfo Exists => __exists;
53+
public static MethodInfo Field => __field;
5054
public static MethodInfo IsMissing => __isMissing;
5155
public static MethodInfo IsNullOrMissing => __isNullOrMissing;
5256
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ public static AggregationExpression Translate(TranslationContext context, Method
5252
case "Exists": return ExistsMethodToAggregationExpressionTranslator.Translate(context, expression);
5353
case "Exp": return ExpMethodToAggregationExpressionTranslator.Translate(context, expression);
5454
case "ExponentialMovingAverage": return ExponentialMovingAverageMethodToAggregationExpressionTranslator.Translate(context, expression);
55+
case "Field": return FieldMethodToAggregationExpressionTranslator.Translate(context, expression);
5556
case "Floor": return FloorMethodToAggregationExpressionTranslator.Translate(context, expression);
5657
case "get_Item": return GetItemMethodToAggregationExpressionTranslator.Translate(context, expression);
5758
case "Integral": return IntegralMethodToAggregationExpressionTranslator.Translate(context, expression);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
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 MongoDB.Bson.Serialization;
18+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
19+
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
22+
23+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
24+
{
25+
internal static class FieldMethodToAggregationExpressionTranslator
26+
{
27+
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
28+
{
29+
var method = expression.Method;
30+
var arguments = expression.Arguments;
31+
32+
if (method.Is(MqlMethod.Field))
33+
{
34+
var documentExpression = arguments[0];
35+
var fieldNameExpression = arguments[1];
36+
var fieldSerializerExpression = arguments[2];
37+
38+
var documentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, documentExpression);
39+
var fieldName = fieldNameExpression.GetConstantValue<string>(expression);
40+
var fieldSerializer = fieldSerializerExpression.GetConstantValue<IBsonSerializer>(expression);
41+
42+
var ast = AstExpression.GetField(documentTranslation.Ast, fieldName);
43+
return new AggregationExpression(expression, ast, fieldSerializer);
44+
}
45+
46+
throw new ExpressionNotSupportedException(expression);
47+
}
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
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 MongoDB.Bson.Serialization;
18+
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
19+
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
20+
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
21+
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;
22+
23+
namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators
24+
{
25+
internal static class FieldMethodToFilterFieldTranslator
26+
{
27+
public static AstFilterField Translate(TranslationContext context, MethodCallExpression expression)
28+
{
29+
var method = expression.Method;
30+
var arguments = expression.Arguments;
31+
32+
if (method.Is(MqlMethod.Field))
33+
{
34+
var documentExpression = arguments[0];
35+
var fieldNameExpression = arguments[1];
36+
var fieldSerializerExpression = arguments[2];
37+
38+
var documentField = ExpressionToFilterFieldTranslator.Translate(context, documentExpression);
39+
var fieldName = fieldNameExpression.GetConstantValue<string>(expression);
40+
var fieldSerializer = fieldSerializerExpression.GetConstantValue<IBsonSerializer>(expression);
41+
42+
return documentField.SubField(fieldName, fieldSerializer);
43+
}
44+
45+
throw new ExpressionNotSupportedException(expression);
46+
}
47+
}
48+
}

src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MethodCallExpressionToFilterFieldTranslator.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ public static AstFilterField Translate(TranslationContext context, MethodCallExp
2727
case "AllElements": return AllElementsMethodToFilterFieldTranslator.Translate(context, expression);
2828
case "AllMatchingElements": return AllMatchingElementsMethodToFilterFieldTranslator.Translate(context, expression);
2929
case "ElementAt": return ElementAtMethodToFilterFieldTranslator.Translate(context, expression);
30+
case "Field": return FieldMethodToFilterFieldTranslator.Translate(context, expression);
3031
case "First": return FirstMethodToFilterFieldTranslator.Translate(context, expression);
3132
case "FirstMatchingElement": return FirstMatchingElementMethodToFilterFieldTranslator.Translate(context, expression);
3233
case "get_Item": return GetItemMethodToFilterFieldTranslator.Translate(context, expression);

src/MongoDB.Driver/Mql.cs

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@
1414
*/
1515

1616
using System;
17-
using System.Runtime.CompilerServices;
1817
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
18+
using MongoDB.Bson.Serialization;
1919

2020
namespace MongoDB.Driver
2121
{
@@ -92,6 +92,20 @@ public static bool Exists<TField>(TField field)
9292
throw CustomLinqExtensionMethodHelper.CreateNotSupportedException();
9393
}
9494

95+
/// <summary>
96+
/// Gets the value of a field in a document.
97+
/// </summary>
98+
/// <typeparam name="TDocument">The type of the document.</typeparam>
99+
/// <typeparam name="TField">The type of the field.</typeparam>
100+
/// <param name="document">The document.</param>
101+
/// <param name="fieldName">The field name.</param>
102+
/// <param name="fieldSerializer">The field serializer.</param>
103+
/// <returns>The value of the field.</returns>
104+
public static TField Field<TDocument, TField>(TDocument document, string fieldName, IBsonSerializer<TField> fieldSerializer)
105+
{
106+
throw CustomLinqExtensionMethodHelper.CreateNotSupportedException();
107+
}
108+
95109
/// <summary>
96110
/// Tests whether a field is missing.
97111
/// </summary>
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
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;
19+
using MongoDB.Bson.Serialization.Attributes;
20+
using MongoDB.Bson.Serialization.Serializers;
21+
using Xunit;
22+
23+
namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
24+
{
25+
public class MqlFieldTests : Linq3IntegrationTest
26+
{
27+
[Fact]
28+
public void Select_Mql_Field_should_work_with_BsonDocument()
29+
{
30+
var collection = GetCollection<BsonDocument>();
31+
32+
var queryable = collection.AsQueryable()
33+
.Select(root => Mql.Field(root, "X", Int32Serializer.Instance) + 1); // like root.X except BsonDocument does not have a property called X
34+
35+
var stages = Translate(collection, queryable);
36+
AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }");
37+
38+
var results = queryable.ToList();
39+
results.Should().Equal(2, 3);
40+
}
41+
42+
[Fact]
43+
public void Select_Mql_Field_should_work_with_POCO()
44+
{
45+
var collection = GetCollection<C>();
46+
47+
var queryable = collection.AsQueryable()
48+
.Select(root => Mql.Field(root, "X", Int32Serializer.Instance) + 1); // like root.X except C does not have a property called X
49+
50+
var stages = Translate(collection, queryable);
51+
AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }");
52+
53+
var results = queryable.ToList();
54+
results.Should().Equal(2, 3);
55+
}
56+
57+
[Fact]
58+
public void Where_Mql_Field_should_work_with_BsonDocument()
59+
{
60+
var collection = GetCollection<BsonDocument>();
61+
62+
var queryable = collection.AsQueryable()
63+
.Where(root => Mql.Field(root, "X", Int32Serializer.Instance) == 1); // like root.X except BsonDocument does not have a property called X
64+
65+
var stages = Translate(collection, queryable);
66+
AssertStages(stages, "{ $match : { X : 1 } }");
67+
68+
var results = queryable.ToList();
69+
results.Select(x => x["_id"].AsInt32).Should().Equal(1);
70+
}
71+
72+
[Fact]
73+
public void Where_Mql_Field_should_work_with_POCO()
74+
{
75+
var collection = GetCollection<C>();
76+
77+
var queryable = collection.AsQueryable()
78+
.Where(root => Mql.Field(root, "X", Int32Serializer.Instance) == 1); // like root.X except C does not have a property called X
79+
80+
var stages = Translate(collection, queryable);
81+
AssertStages(stages, "{ $match : { X : 1 } }");
82+
83+
var results = queryable.ToList();
84+
results.Select(x => x.Id).Should().Equal(1);
85+
}
86+
87+
private IMongoCollection<TDocument> GetCollection<TDocument>()
88+
{
89+
var collection = GetCollection<BsonDocument>("test");
90+
CreateCollection(
91+
collection,
92+
new BsonDocument { { "_id", 1 }, { "X", 1 } },
93+
new BsonDocument { { "_id", 2 }, { "X", 2 } });
94+
95+
var database = collection.Database;
96+
var collectionName = collection.CollectionNamespace.CollectionName;
97+
return database.GetCollection<TDocument>(collectionName);
98+
}
99+
100+
[BsonIgnoreExtraElements]
101+
private class C
102+
{
103+
public int Id { get; set; }
104+
}
105+
}
106+
}

0 commit comments

Comments
 (0)