Skip to content

Commit

Permalink
CSHARP-5421: Add Mql.Field so EF Core Provider can access shadow prop…
Browse files Browse the repository at this point in the history
…erties.
  • Loading branch information
rstam committed Nov 25, 2024
1 parent 42e08b5 commit e93dd86
Show file tree
Hide file tree
Showing 8 changed files with 225 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ internal static class PartialEvaluator
typeof(DateTimeExtensions),
typeof(LinqExtensions),
typeof(MongoEnumerable),
typeof(Mql),
typeof(StringExtensions)
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

using System;
using System.Reflection;
using MongoDB.Bson.Serialization;

namespace MongoDB.Driver.Linq.Linq3Implementation.Reflection
{
Expand All @@ -26,6 +27,7 @@ internal static class MqlMethod
private static readonly MethodInfo __dateFromStringWithFormatAndTimezone;
private static readonly MethodInfo __dateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull;
private static readonly MethodInfo __exists;
private static readonly MethodInfo __field;
private static readonly MethodInfo __isMissing;
private static readonly MethodInfo __isNullOrMissing;

Expand All @@ -37,6 +39,7 @@ static MqlMethod()
__dateFromStringWithFormatAndTimezone = ReflectionInfo.Method((string dateString, string format, string timezone) => Mql.DateFromString(dateString, format, timezone));
__dateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull = ReflectionInfo.Method((string dateString, string format, string timezone, DateTime? onError, DateTime? onNull) => Mql.DateFromString(dateString, format, timezone, onError, onNull));
__exists = ReflectionInfo.Method((object field) => Mql.Exists(field));
__field = ReflectionInfo.Method((object container, string fieldName, IBsonSerializer<object> serializer) => Mql.Field<object, object>(container, fieldName, serializer));
__isMissing = ReflectionInfo.Method((object field) => Mql.IsMissing(field));
__isNullOrMissing = ReflectionInfo.Method((object field) => Mql.IsNullOrMissing(field));
}
Expand All @@ -47,6 +50,7 @@ static MqlMethod()
public static MethodInfo DateFromStringWithFormatAndTimezone => __dateFromStringWithFormatAndTimezone;
public static MethodInfo DateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull => __dateFromStringWithFormatAndTimezoneAndOnErrorAndOnNull;
public static MethodInfo Exists => __exists;
public static MethodInfo Field => __field;
public static MethodInfo IsMissing => __isMissing;
public static MethodInfo IsNullOrMissing => __isNullOrMissing;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ public static AggregationExpression Translate(TranslationContext context, Method
case "Exists": return ExistsMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Exp": return ExpMethodToAggregationExpressionTranslator.Translate(context, expression);
case "ExponentialMovingAverage": return ExponentialMovingAverageMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Field": return FieldMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Floor": return FloorMethodToAggregationExpressionTranslator.Translate(context, expression);
case "get_Item": return GetItemMethodToAggregationExpressionTranslator.Translate(context, expression);
case "Integral": return IntegralMethodToAggregationExpressionTranslator.Translate(context, expression);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System.Linq.Expressions;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;

namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToAggregationExpressionTranslators.MethodTranslators
{
internal static class FieldMethodToAggregationExpressionTranslator
{
public static AggregationExpression Translate(TranslationContext context, MethodCallExpression expression)
{
var method = expression.Method;
var arguments = expression.Arguments;

if (method.Is(MqlMethod.Field))
{
var documentExpression = arguments[0];
var fieldNameExpression = arguments[1];
var fieldSerializerExpression = arguments[2];

var documentTranslation = ExpressionToAggregationExpressionTranslator.Translate(context, documentExpression);
var fieldName = fieldNameExpression.GetConstantValue<string>(expression);
var fieldSerializer = fieldSerializerExpression.GetConstantValue<IBsonSerializer>(expression);

var ast = AstExpression.GetField(documentTranslation.Ast, fieldName);
return new AggregationExpression(expression, ast, fieldSerializer);
}

throw new ExpressionNotSupportedException(expression);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System.Linq.Expressions;
using MongoDB.Bson.Serialization;
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Filters;
using MongoDB.Driver.Linq.Linq3Implementation.ExtensionMethods;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
using MongoDB.Driver.Linq.Linq3Implementation.Reflection;

namespace MongoDB.Driver.Linq.Linq3Implementation.Translators.ExpressionToFilterTranslators.ToFilterFieldTranslators
{
internal static class FieldMethodToFilterFieldTranslator
{
public static AstFilterField Translate(TranslationContext context, MethodCallExpression expression)
{
var method = expression.Method;
var arguments = expression.Arguments;

if (method.Is(MqlMethod.Field))
{
var documentExpression = arguments[0];
var fieldNameExpression = arguments[1];
var fieldSerializerExpression = arguments[2];

var documentField = ExpressionToFilterFieldTranslator.Translate(context, documentExpression);
var fieldName = fieldNameExpression.GetConstantValue<string>(expression);
var fieldSerializer = fieldSerializerExpression.GetConstantValue<IBsonSerializer>(expression);

return documentField.SubField(fieldName, fieldSerializer);
}

throw new ExpressionNotSupportedException(expression);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public static AstFilterField Translate(TranslationContext context, MethodCallExp
case "AllElements": return AllElementsMethodToFilterFieldTranslator.Translate(context, expression);
case "AllMatchingElements": return AllMatchingElementsMethodToFilterFieldTranslator.Translate(context, expression);
case "ElementAt": return ElementAtMethodToFilterFieldTranslator.Translate(context, expression);
case "Field": return FieldMethodToFilterFieldTranslator.Translate(context, expression);
case "First": return FirstMethodToFilterFieldTranslator.Translate(context, expression);
case "FirstMatchingElement": return FirstMatchingElementMethodToFilterFieldTranslator.Translate(context, expression);
case "get_Item": return GetItemMethodToFilterFieldTranslator.Translate(context, expression);
Expand Down
16 changes: 15 additions & 1 deletion src/MongoDB.Driver/Mql.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
*/

using System;
using System.Runtime.CompilerServices;
using MongoDB.Driver.Linq.Linq3Implementation.Misc;
using MongoDB.Bson.Serialization;

namespace MongoDB.Driver
{
Expand Down Expand Up @@ -92,6 +92,20 @@ public static bool Exists<TField>(TField field)
throw CustomLinqExtensionMethodHelper.CreateNotSupportedException();
}

/// <summary>
/// Gets the value of a field in a document.
/// </summary>
/// <typeparam name="TDocument">The type of the document.</typeparam>
/// <typeparam name="TField">The type of the field.</typeparam>
/// <param name="document">The document.</param>
/// <param name="fieldName">The field name.</param>
/// <param name="fieldSerializer">The field serializer.</param>
/// <returns>The value of the field.</returns>
public static TField Field<TDocument, TField>(TDocument document, string fieldName, IBsonSerializer<TField> fieldSerializer)
{
throw CustomLinqExtensionMethodHelper.CreateNotSupportedException();
}

/// <summary>
/// Tests whether a field is missing.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
/* Copyright 2010-present MongoDB Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

using System.Linq;
using FluentAssertions;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using MongoDB.Bson.Serialization.Serializers;
using Xunit;

namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira
{
public class MqlFieldTests : Linq3IntegrationTest
{
[Fact]
public void Select_Mql_Field_should_work_with_BsonDocument()
{
var collection = GetCollection<BsonDocument>();

var queryable = collection.AsQueryable()
.Select(root => Mql.Field(root, "X", Int32Serializer.Instance) + 1); // like root.X except BsonDocument does not have a property called X

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }");

var results = queryable.ToList();
results.Should().Equal(2, 3);
}

[Fact]
public void Select_Mql_Field_should_work_with_POCO()
{
var collection = GetCollection<C>();

var queryable = collection.AsQueryable()
.Select(root => Mql.Field(root, "X", Int32Serializer.Instance) + 1); // like root.X except C does not have a property called X

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : { $add : ['$X', 1] }, _id : 0 } }");

var results = queryable.ToList();
results.Should().Equal(2, 3);
}

[Fact]
public void Where_Mql_Field_should_work_with_BsonDocument()
{
var collection = GetCollection<BsonDocument>();

var queryable = collection.AsQueryable()
.Where(root => Mql.Field(root, "X", Int32Serializer.Instance) == 1); // like root.X except BsonDocument does not have a property called X

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $match : { X : 1 } }");

var results = queryable.ToList();
results.Select(x => x["_id"].AsInt32).Should().Equal(1);
}

[Fact]
public void Where_Mql_Field_should_work_with_POCO()
{
var collection = GetCollection<C>();

var queryable = collection.AsQueryable()
.Where(root => Mql.Field(root, "X", Int32Serializer.Instance) == 1); // like root.X except C does not have a property called X

var stages = Translate(collection, queryable);
AssertStages(stages, "{ $match : { X : 1 } }");

var results = queryable.ToList();
results.Select(x => x.Id).Should().Equal(1);
}

private IMongoCollection<TDocument> GetCollection<TDocument>()
{
var collection = GetCollection<BsonDocument>("test");
CreateCollection(
collection,
new BsonDocument { { "_id", 1 }, { "X", 1 } },
new BsonDocument { { "_id", 2 }, { "X", 2 } });

var database = collection.Database;
var collectionName = collection.CollectionNamespace.CollectionName;
return database.GetCollection<TDocument>(collectionName);
}

[BsonIgnoreExtraElements]
private class C
{
public int Id { get; set; }
}
}
}

0 comments on commit e93dd86

Please sign in to comment.