From ffe1cbe9a7100c5ee82233f0153893af937222ee Mon Sep 17 00:00:00 2001 From: rstam Date: Wed, 16 Aug 2023 11:49:24 -0700 Subject: [PATCH 1/2] Proof of concept for supporting compound primary keys in LINQ with the EF Core Provider. --- .../Serialization/BsonSerializationInfo.cs | 54 +++- .../Misc/DocumentSerializerHelper.cs | 2 +- .../Misc/MemberSerializationInfo.cs | 9 +- ...essionToAggregationExpressionTranslator.cs | 8 +- ...MemberExpressionToFilterFieldTranslator.cs | 8 +- .../Jira/CSharpxxxxTests.cs | 241 ++++++++++++++++++ 6 files changed, 301 insertions(+), 21 deletions(-) create mode 100644 tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharpxxxxTests.cs diff --git a/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs b/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs index 3b9dcf977cd..21073653343 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs @@ -15,6 +15,8 @@ using System; using System.Collections; +using System.Collections.Generic; +using System.Linq; using MongoDB.Bson.IO; namespace MongoDB.Bson.Serialization @@ -24,10 +26,24 @@ namespace MongoDB.Bson.Serialization /// public class BsonSerializationInfo { + #region static + /// + /// Creates anew instance of the BsonSerializationinfo class with an element path instead of an element name. + /// + /// The element path. + /// The serializer. + /// The nominal type. + /// A BsonSerializationInfo. + public static BsonSerializationInfo CreateWithPath(IEnumerable elementPath, IBsonSerializer serializer, Type nominalType) + { + return new BsonSerializationInfo(elementPath.ToList(), serializer, nominalType); + } + #endregion + // private fields - private string _elementName; - private IBsonSerializer _serializer; - private Type _nominalType; + private readonly IReadOnlyList _elementPath; + private readonly IBsonSerializer _serializer; + private readonly Type _nominalType; // constructors /// @@ -37,19 +53,32 @@ public class BsonSerializationInfo /// The serializer. /// The nominal type. public BsonSerializationInfo(string elementName, IBsonSerializer serializer, Type nominalType) + : this(elementName == null ? null : new[] { elementName }, serializer, nominalType) { - _elementName = elementName; + } + + private BsonSerializationInfo(IReadOnlyList elementPath, IBsonSerializer serializer, Type nominalType) + { + _elementPath = elementPath; _serializer = serializer; _nominalType = nominalType; } // public properties /// - /// Gets or sets the dotted element name. + /// Gets the element name. /// public string ElementName { - get { return _elementName; } + get { return _elementPath?.Single(); } + } + + /// + /// Gets element path. + /// + public IReadOnlyList ElementPath + { + get { return _elementPath; } } /// @@ -92,20 +121,21 @@ public object DeserializeValue(BsonValue value) /// /// The new info. /// A new BsonSerializationInfo. + [Obsolete("This method is no longer relevant because field names are now allowed to contain dots.")] public BsonSerializationInfo Merge(BsonSerializationInfo newSerializationInfo) { string elementName = null; - if (_elementName != null && newSerializationInfo._elementName != null) + if (ElementName != null && newSerializationInfo.ElementName != null) { - elementName = _elementName + "." + newSerializationInfo._elementName; + elementName = ElementName + "." + newSerializationInfo.ElementName; } - else if (_elementName != null) + else if (ElementName != null) { - elementName = _elementName; + elementName = ElementName; } - else if (newSerializationInfo._elementName != null) + else if (newSerializationInfo.ElementName != null) { - elementName = newSerializationInfo._elementName; + elementName = newSerializationInfo.ElementName; } return new BsonSerializationInfo( diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs index 37692e69e8c..753ea6a4361 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs @@ -32,7 +32,7 @@ public static MemberSerializationInfo GetMemberSerializationInfo(IBsonSerializer throw new InvalidOperationException($"Serializer for {serializer.ValueType} does not have a member named {memberName}."); } - return new MemberSerializationInfo(serializationInfo.ElementName, serializationInfo.Serializer); + return new MemberSerializationInfo(serializationInfo.ElementPath, serializationInfo.Serializer); } public static bool HasMemberSerializationInfo(IBsonSerializer serializer, string memberName) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs index 637a7b4e0ab..a1b04e56af1 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System.Collections.Generic; using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; @@ -21,18 +22,18 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Misc internal class MemberSerializationInfo { // private fields - private readonly string _elementName; + private readonly IReadOnlyList _elementPath; private readonly IBsonSerializer _serializer; // constructors - public MemberSerializationInfo(string elementName, IBsonSerializer serializer) + public MemberSerializationInfo(IReadOnlyList elementPath, IBsonSerializer serializer) { - _elementName = Ensure.IsNotNullOrEmpty(elementName, nameof(elementName)); + _elementPath = Ensure.IsNotNull(elementPath, nameof(elementPath)); _serializer = Ensure.IsNotNull(serializer, nameof(serializer)); } // public properties - public string ElementName => _elementName; + public IReadOnlyList ElementPath => _elementPath; public IBsonSerializer Serializer => _serializer; } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs index 9bf0390dd1f..742afdc7b68 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs @@ -77,8 +77,12 @@ public static AggregationExpression Translate(TranslationContext context, Member } var serializationInfo = DocumentSerializerHelper.GetMemberSerializationInfo(containerTranslation.Serializer, member.Name); - var ast = AstExpression.GetField(containerTranslation.Ast, serializationInfo.ElementName); - return new AggregationExpression(expression, ast, serializationInfo.Serializer); + var subField = containerTranslation.Ast; + foreach (var subFieldName in serializationInfo.ElementPath) + { + subField = AstExpression.GetField(subField, subFieldName); + } + return new AggregationExpression(expression, subField, serializationInfo.Serializer); } private static AggregationExpression TranslateTupleItemProperty(MemberExpression expression, AggregationExpression containerTranslation) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs index 31e5d2d73a3..0761b244f47 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs @@ -89,9 +89,13 @@ public static AstFilterField Translate(TranslationContext context, MemberExpress if (fieldSerializer is IBsonDocumentSerializer documentSerializer && documentSerializer.TryGetMemberSerializationInfo(memberExpression.Member.Name, out BsonSerializationInfo memberSerializationInfo)) { - var subFieldName = memberSerializationInfo.ElementName; var subFieldSerializer = memberSerializationInfo.Serializer; - return field.SubField(subFieldName, subFieldSerializer); + var subField = field; + foreach (var subFieldName in memberSerializationInfo.ElementPath) + { + subField = subField.SubField(subFieldName, subFieldSerializer); + } + return subField; } if (memberExpression.Expression.Type.IsConstructedGenericType && diff --git a/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharpxxxxTests.cs b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharpxxxxTests.cs new file mode 100644 index 00000000000..15357b022bd --- /dev/null +++ b/tests/MongoDB.Driver.Tests/Linq/Linq3Implementation/Jira/CSharpxxxxTests.cs @@ -0,0 +1,241 @@ +/* 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; +using FluentAssertions; +using MongoDB.Bson; +using MongoDB.Bson.IO; +using MongoDB.Bson.Serialization; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Bson.Serialization.Serializers; +using MongoDB.Driver.Linq; +using MongoDB.TestHelpers.XunitExtensions; +using Xunit; + +namespace MongoDB.Driver.Tests.Linq.Linq3Implementation.Jira +{ + public class CSharpxxxTests : Linq3IntegrationTest + { + [Fact] + public void Deserialize_should_ungroup_members() + { + var serializedOrderItem = "{ _id : { OrderId : 1, ProductId : 2 }, Quantity : 3 }"; + + var orderItem = BsonSerializer.Deserialize(serializedOrderItem); + + orderItem.OrderId.Should().Be(1); + orderItem.ProductId.Should().Be(2); + orderItem.Quantity.Should().Be(3); + } + + [Fact] + public void Serialize_should_group_members() + { + var orderItem = new OrderItem { OrderId = 1, ProductId = 2, Quantity = 3 }; + + var serializedOrderItem = orderItem.ToBsonDocument(); + + serializedOrderItem.Should().Be("{ _id : { OrderId : 1, ProductId : 2 }, Quantity : 3 }"); + } + + [Theory] + [ParameterAttributeData] + public void Select_with_grouped_member_should_work( + [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) + { + var collection = GetCollection(linqProvider); + + var queryable = collection.AsQueryable() + .Select(x => x.OrderId); + + if (linqProvider == LinqProvider.V2) + { + var exception = Record.Exception(() => Translate(collection, queryable)); + exception.Should().BeOfType(); // LINQ2 doesn't understand grouped members + } + else + { + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $project : { _v : '$_id.OrderId', _id : 0 } }"); + + var results = queryable.ToList(); + results.Should().Equal(1, 4); + } + } + + [Theory] + [ParameterAttributeData] + public void Select_with_ungrouped_member_should_work( + [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) + { + var collection = GetCollection(linqProvider); + + var queryable = collection.AsQueryable() + .Select(x => x.Quantity); + + var stages = Translate(collection, queryable); + if (linqProvider == LinqProvider.V2) + { + AssertStages(stages, "{ $project : { Quantity : '$Quantity', _id : 0 } }"); + } + else + { + AssertStages(stages, "{ $project : { _v : '$Quantity', _id : 0 } }"); + } + + var results = queryable.ToList(); + results.Should().Equal(3, 6); + } + + [Theory] + [ParameterAttributeData] + public void Where_with_grouped_member_should_work( + [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) + { + var collection = GetCollection(linqProvider); + + var queryable = collection.AsQueryable() + .Where(x => x.OrderId == 1); + + if (linqProvider == LinqProvider.V2) + { + var exception = Record.Exception(() => Translate(collection, queryable)); + exception.Should().BeOfType(); // LINQ2 doesn't understand grouped members + } + else + { + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match: { '_id.OrderId' : 1 } }"); + + var result = queryable.Single(); + result.OrderId.Should().Be(1); + } + } + + [Theory] + [ParameterAttributeData] + public void Where_with_ungrouped_member_should_work( + [Values(LinqProvider.V2, LinqProvider.V3)] LinqProvider linqProvider) + { + var collection = GetCollection(linqProvider); + + var queryable = collection.AsQueryable() + .Where(x => x.Quantity == 3); + + var stages = Translate(collection, queryable); + AssertStages(stages, "{ $match: { Quantity : 3 } }"); + + var result = queryable.Single(); + result.OrderId.Should().Be(1); + } + + private IMongoCollection GetCollection(LinqProvider linqProvider) + { + var collection = GetCollection("test", linqProvider); + CreateCollection( + collection, + new OrderItem { OrderId = 1, ProductId = 2, Quantity = 3 }, + new OrderItem { OrderId = 4, ProductId = 5, Quantity = 6 }); + return collection; + } + + [BsonSerializer(typeof(OrderItemsSerializer))] + private class OrderItem + { + public int OrderId { get; set; } + public int ProductId { get; set; } + public int Quantity { get; set; } + } + + private class OrderItemsSerializer : ClassSerializerBase, IBsonDocumentSerializer + { + // public methods + public bool IsGroupedMember(string name, out string groupElementName) + { + switch (name) + { + case "OrderId": + groupElementName = "_id"; + return true; + + case "ProductId": + groupElementName = "_id"; + return true; + + default: + groupElementName = null; + return false; + } + } + + public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializationInfo serializationInfo) + { + switch (memberName) + { + case "OrderId": + serializationInfo = BsonSerializationInfo.CreateWithPath(new[] { "_id", "OrderId" }, Int32Serializer.Instance, typeof(int)); + return true; + + case "ProductId": + serializationInfo = BsonSerializationInfo.CreateWithPath(new[] { "_id", "ProductId" }, Int32Serializer.Instance, typeof(int)); + return true; + + case "Quantity": + serializationInfo = new BsonSerializationInfo("Quantity", Int32Serializer.Instance, typeof(int)); + return true; + + default: + serializationInfo = null; + return false; + } + } + + // protected methods + protected override OrderItem DeserializeValue(BsonDeserializationContext context, BsonDeserializationArgs args) + { + var reader = context.Reader; + reader.ReadStartDocument(); + reader.ReadName("_id"); + reader.ReadStartDocument(); + reader.ReadName("OrderId"); + var orderId = reader.ReadInt32(); + reader.ReadName("ProductId"); + var productId = reader.ReadInt32(); + reader.ReadEndDocument(); + reader.ReadName("Quantity"); + var quantity = reader.ReadInt32(); + reader.ReadEndDocument(); + + return new OrderItem { OrderId = orderId, ProductId = productId, Quantity = quantity }; + } + + protected override void SerializeValue(BsonSerializationContext context, BsonSerializationArgs args, OrderItem value) + { + var writer = context.Writer; + writer.WriteStartDocument(); + writer.WriteName("_id"); + writer.WriteStartDocument(); + writer.WriteName("OrderId"); + writer.WriteInt32(value.OrderId); + writer.WriteName("ProductId"); + writer.WriteInt32(value.ProductId); + writer.WriteEndDocument(); + writer.WriteName("Quantity"); + writer.WriteInt32(value.Quantity); + writer.WriteEndDocument(); + } + } + } +} From d4a1570b685792e447a06751a4259a441b6f5084 Mon Sep 17 00:00:00 2001 From: rstam Date: Wed, 16 Aug 2023 13:57:19 -0700 Subject: [PATCH 2/2] Reduce allocations by using mutually exclusive ElementName and ElementPath properties. --- .../Serialization/BsonSerializationInfo.cs | 14 +++++++++++-- .../Misc/DocumentSerializerHelper.cs | 9 +++++++- .../Misc/MemberSerializationInfo.cs | 21 +++++++++++++++++++ ...essionToAggregationExpressionTranslator.cs | 16 ++++++++++---- ...MemberExpressionToFilterFieldTranslator.cs | 16 ++++++++++---- 5 files changed, 65 insertions(+), 11 deletions(-) diff --git a/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs b/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs index 21073653343..ac0e28842b7 100644 --- a/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs +++ b/src/MongoDB.Bson/Serialization/BsonSerializationInfo.cs @@ -41,6 +41,7 @@ public static BsonSerializationInfo CreateWithPath(IEnumerable elementPa #endregion // private fields + private readonly string _elementName; private readonly IReadOnlyList _elementPath; private readonly IBsonSerializer _serializer; private readonly Type _nominalType; @@ -53,8 +54,10 @@ public static BsonSerializationInfo CreateWithPath(IEnumerable elementPa /// The serializer. /// The nominal type. public BsonSerializationInfo(string elementName, IBsonSerializer serializer, Type nominalType) - : this(elementName == null ? null : new[] { elementName }, serializer, nominalType) { + _elementName = elementName; + _serializer = serializer; + _nominalType = nominalType; } private BsonSerializationInfo(IReadOnlyList elementPath, IBsonSerializer serializer, Type nominalType) @@ -70,7 +73,14 @@ private BsonSerializationInfo(IReadOnlyList elementPath, IBsonSerializer /// public string ElementName { - get { return _elementPath?.Single(); } + get + { + if (_elementPath != null) + { + throw new InvalidOperationException("When ElementPath is not null you must use it instead."); + } + return _elementName; + } } /// diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs index 753ea6a4361..329a8b477e5 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/DocumentSerializerHelper.cs @@ -32,7 +32,14 @@ public static MemberSerializationInfo GetMemberSerializationInfo(IBsonSerializer throw new InvalidOperationException($"Serializer for {serializer.ValueType} does not have a member named {memberName}."); } - return new MemberSerializationInfo(serializationInfo.ElementPath, serializationInfo.Serializer); + if (serializationInfo.ElementPath == null) + { + return new MemberSerializationInfo(serializationInfo.ElementName, serializationInfo.Serializer); + } + else + { + return new MemberSerializationInfo(serializationInfo.ElementPath, serializationInfo.Serializer); + } } public static bool HasMemberSerializationInfo(IBsonSerializer serializer, string memberName) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs index a1b04e56af1..4fa1fea88ac 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Misc/MemberSerializationInfo.cs @@ -13,6 +13,7 @@ * limitations under the License. */ +using System; using System.Collections.Generic; using MongoDB.Bson.Serialization; using MongoDB.Driver.Core.Misc; @@ -22,10 +23,17 @@ namespace MongoDB.Driver.Linq.Linq3Implementation.Misc internal class MemberSerializationInfo { // private fields + private readonly string _elementName; private readonly IReadOnlyList _elementPath; private readonly IBsonSerializer _serializer; // constructors + public MemberSerializationInfo(string elementName, IBsonSerializer serializer) + { + _elementName = Ensure.IsNotNullOrEmpty(elementName, nameof(elementName)); + _serializer = Ensure.IsNotNull(serializer, nameof(serializer)); + } + public MemberSerializationInfo(IReadOnlyList elementPath, IBsonSerializer serializer) { _elementPath = Ensure.IsNotNull(elementPath, nameof(elementPath)); @@ -33,7 +41,20 @@ public MemberSerializationInfo(IReadOnlyList elementPath, IBsonSerialize } // public properties + public string ElementName + { + get + { + if (_elementPath != null) + { + throw new InvalidOperationException("When ElementPath is not null you must use it instead."); + } + return _elementName; + } + } + public IReadOnlyList ElementPath => _elementPath; + public IBsonSerializer Serializer => _serializer; } } diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs index 742afdc7b68..db47c8bb5a5 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToAggregationExpressionTranslators/MemberExpressionToAggregationExpressionTranslator.cs @@ -77,12 +77,20 @@ public static AggregationExpression Translate(TranslationContext context, Member } var serializationInfo = DocumentSerializerHelper.GetMemberSerializationInfo(containerTranslation.Serializer, member.Name); - var subField = containerTranslation.Ast; - foreach (var subFieldName in serializationInfo.ElementPath) + AstExpression ast; + if (serializationInfo.ElementPath == null) { - subField = AstExpression.GetField(subField, subFieldName); + ast = AstExpression.GetField(containerTranslation.Ast, serializationInfo.ElementName); } - return new AggregationExpression(expression, subField, serializationInfo.Serializer); + else + { + ast = containerTranslation.Ast; + foreach (var subFieldName in serializationInfo.ElementPath) + { + ast = AstExpression.GetField(ast, subFieldName); + } + } + return new AggregationExpression(expression, ast, serializationInfo.Serializer); } private static AggregationExpression TranslateTupleItemProperty(MemberExpression expression, AggregationExpression containerTranslation) diff --git a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs index 0761b244f47..29cd8dc9030 100644 --- a/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs +++ b/src/MongoDB.Driver/Linq/Linq3Implementation/Translators/ExpressionToFilterTranslators/ToFilterFieldTranslators/MemberExpressionToFilterFieldTranslator.cs @@ -90,12 +90,20 @@ public static AstFilterField Translate(TranslationContext context, MemberExpress documentSerializer.TryGetMemberSerializationInfo(memberExpression.Member.Name, out BsonSerializationInfo memberSerializationInfo)) { var subFieldSerializer = memberSerializationInfo.Serializer; - var subField = field; - foreach (var subFieldName in memberSerializationInfo.ElementPath) + if (memberSerializationInfo.ElementPath == null) { - subField = subField.SubField(subFieldName, subFieldSerializer); + var subFieldName = memberSerializationInfo.ElementName; + return field.SubField(subFieldName, subFieldSerializer); + } + else + { + var subField = field; + foreach (var subFieldName in memberSerializationInfo.ElementPath) + { + subField = subField.SubField(subFieldName, subFieldSerializer); + } + return subField; } - return subField; } if (memberExpression.Expression.Type.IsConstructedGenericType &&