diff --git a/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoProjectionBindingRemovingExpressionVisitor.cs b/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoProjectionBindingRemovingExpressionVisitor.cs index 582b2206..3c1e6172 100644 --- a/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoProjectionBindingRemovingExpressionVisitor.cs +++ b/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoProjectionBindingRemovingExpressionVisitor.cs @@ -39,11 +39,10 @@ namespace MongoDB.EntityFrameworkCore.Query.Visitors; internal class MongoProjectionBindingRemovingExpressionVisitor : ExpressionVisitor { private readonly MongoQueryExpression _queryExpression; - private readonly IEntityType _rootEntityType; - private readonly ParameterExpression DocParameter; + private readonly ParameterExpression _docParameter; private readonly bool _trackQueryResults; private readonly Dictionary _materializationContextBindings = new(); - private readonly Dictionary ProjectionBindings = new(); + private readonly Dictionary _projectionBindings = new(); private readonly Dictionary _ownerMappings = new(); private readonly Dictionary _ordinalParameterBindings = new(); private List _pendingIncludes = []; @@ -51,7 +50,6 @@ internal class MongoProjectionBindingRemovingExpressionVisitor : ExpressionVisit /// /// Create a . /// - /// The this projection relates to. /// The this visitor should use. /// The parameter that will hold the input parameter to the shaper. /// @@ -59,14 +57,12 @@ internal class MongoProjectionBindingRemovingExpressionVisitor : ExpressionVisit /// if they are not. /// public MongoProjectionBindingRemovingExpressionVisitor( - IEntityType rootEntityType, MongoQueryExpression queryExpression, ParameterExpression docParameter, bool trackQueryResults) { _queryExpression = queryExpression; - _rootEntityType = rootEntityType; - DocParameter = docParameter; + _docParameter = docParameter; _trackQueryResults = trackQueryResults; } @@ -78,11 +74,16 @@ protected override Expression VisitExtension(Expression extensionExpression) { var projection = GetProjection(projectionBindingExpression); - return CreateGetValueExpression( - DocParameter, - projection.Alias, - !projectionBindingExpression.Type.IsNullableType(), - projectionBindingExpression.Type); + var memberExpression = (MemberExpression)projection.Expression; + while (memberExpression.Expression is MemberExpression nestedMemberExpression) + { + memberExpression = nestedMemberExpression; + } + + var typeBase = ((StructuralTypeShaperExpression)memberExpression.Expression!).StructuralType; + var propertyBase = typeBase.FindMember(memberExpression.Member.Name); + + return CreateGetValueExpression(_docParameter, projection.Alias, propertyBase); } case CollectionShaperExpression collectionShaperExpression: @@ -101,12 +102,12 @@ protected override Expression VisitExtension(Expression extensionExpression) throw new InvalidOperationException(CoreStrings.TranslationFailed(extensionExpression.Print())); } - var bsonArray = ProjectionBindings[objectArrayProjection]; + var bsonArray = _projectionBindings[objectArrayProjection]; var jObjectParameter = Expression.Parameter(typeof(BsonDocument), bsonArray.Name + "Object"); var ordinalParameter = Expression.Parameter(typeof(int), bsonArray.Name + "Ordinal"); var accessExpression = objectArrayProjection.InnerProjection.ParentAccessExpression; - ProjectionBindings[accessExpression] = jObjectParameter; + _projectionBindings[accessExpression] = jObjectParameter; _ownerMappings[accessExpression] = (objectArrayProjection.Navigation.DeclaringEntityType, objectArrayProjection.AccessExpression); _ordinalParameterBindings[accessExpression] = Expression.Add( @@ -183,15 +184,16 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) { if (parameterExpression.Type == typeof(BsonDocument) || parameterExpression.Type == typeof(BsonArray)) { - string? fieldName = null; - var fieldRequired = true; + // "alias" will be different from the property/navigation name when mapped to a different name in the document. + string? alias = null; + IPropertyBase? propertyBase = null; var projectionExpression = ((UnaryExpression)binaryExpression.Right).Operand; if (projectionExpression is ProjectionBindingExpression projectionBindingExpression) { var projection = GetProjection(projectionBindingExpression); projectionExpression = projection.Expression; - fieldName = projection.Alias; + alias = projection.Alias; } else if (projectionExpression is UnaryExpression convertExpression && convertExpression.NodeType == ExpressionType.Convert) @@ -203,15 +205,16 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) if (projectionExpression is ObjectArrayProjectionExpression objectArrayProjectionExpression) { innerAccessExpression = objectArrayProjectionExpression.AccessExpression; - ProjectionBindings[objectArrayProjectionExpression] = parameterExpression; - fieldName ??= objectArrayProjectionExpression.Name; + _projectionBindings[objectArrayProjectionExpression] = parameterExpression; + alias ??= objectArrayProjectionExpression.Name; + propertyBase = objectArrayProjectionExpression.Navigation; } else { var entityProjectionExpression = (EntityProjectionExpression)projectionExpression; var accessExpression = entityProjectionExpression.ParentAccessExpression; - ProjectionBindings[accessExpression] = parameterExpression; - fieldName ??= entityProjectionExpression.Name; + _projectionBindings[accessExpression] = parameterExpression; + alias ??= entityProjectionExpression.Name; switch (accessExpression) { @@ -219,10 +222,10 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) innerAccessExpression = innerObjectAccessExpression.AccessExpression; _ownerMappings[accessExpression] = (innerObjectAccessExpression.Navigation.DeclaringEntityType, innerAccessExpression); - fieldRequired = innerObjectAccessExpression.Required; + propertyBase = innerObjectAccessExpression.Navigation; break; case RootReferenceExpression: - innerAccessExpression = DocParameter; + innerAccessExpression = _docParameter; break; default: throw new InvalidOperationException( @@ -230,8 +233,7 @@ protected override Expression VisitBinary(BinaryExpression binaryExpression) } } - var valueExpression = - CreateGetValueExpression(innerAccessExpression, fieldName, fieldRequired, parameterExpression.Type); + var valueExpression = CreateGetValueExpression(innerAccessExpression, alias, propertyBase); return Expression.MakeBinary(ExpressionType.Assign, binaryExpression.Left, valueExpression); } @@ -290,8 +292,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp if (methodCallExpression.Arguments[0] is ProjectionBindingExpression projectionBindingExpression) { var projection = GetProjection(projectionBindingExpression); - innerExpression = - CreateGetValueExpression(DocParameter, projection.Alias, projection.Required, typeof(BsonDocument)); + innerExpression = CreateGetValueExpression(_docParameter, projection.Alias, property); } else { @@ -331,7 +332,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } private Expression CreateGetValueExpression( - Expression docExpression, + Expression documentExpression, IProperty property, Type type) { @@ -343,7 +344,7 @@ private Expression CreateGetValueExpression( var ownership = entityType.FindOwnership(); if (ownership?.IsUnique == false && property.IsOwnedTypeOrdinalKey()) { - var readExpression = _ordinalParameterBindings[docExpression]; + var readExpression = _ordinalParameterBindings[documentExpression]; if (readExpression.Type != type) { readExpression = Expression.Convert(readExpression, type); @@ -356,16 +357,16 @@ private Expression CreateGetValueExpression( if (principalProperty != null) { Expression? ownerBsonDocExpression = null; - if (_ownerMappings.TryGetValue(docExpression, + if (_ownerMappings.TryGetValue(documentExpression, out var ownerInfo)) { ownerBsonDocExpression = ownerInfo.BsonDocExpression; } - else if (docExpression is RootReferenceExpression rootReferenceExpression) + else if (documentExpression is RootReferenceExpression rootReferenceExpression) { ownerBsonDocExpression = rootReferenceExpression; } - else if (docExpression is ObjectAccessExpression objectAccessExpression) + else if (documentExpression is ObjectAccessExpression objectAccessExpression) { ownerBsonDocExpression = objectAccessExpression.AccessExpression; } @@ -381,8 +382,7 @@ private Expression CreateGetValueExpression( } return Expression.Convert( - CreateGetValueExpression(docExpression, property.Name, !type.IsNullableType(), type, property.DeclaringType, - property.GetTypeMapping()), + CreateGetValueExpression(documentExpression, null, property), type); } @@ -391,52 +391,43 @@ private Expression CreateGetValueExpression( /// /// The to look-up. /// The registered this relates to. - protected ProjectionExpression GetProjection(ProjectionBindingExpression projectionBindingExpression) + private ProjectionExpression GetProjection(ProjectionBindingExpression projectionBindingExpression) => _queryExpression.Projection[GetProjectionIndex(projectionBindingExpression)]; /// /// Create a new compilable the shaper can use to obtain the value from the . /// - /// The used to access the . - /// The name of the property. - /// if the field is required, if it is optional. - /// The of the value as it is within the document. - /// The optional this element comes from. - /// Any associated to be used in mapping the value. + /// The used to access the . + /// The name of the property. + /// The or associated with the value. /// A compilable to obtain the desired value as the correct type. - protected Expression CreateGetValueExpression( - Expression docExpression, - string? propertyName, - bool required, - Type type, - ITypeBase? declaredType = null, - CoreTypeMapping? typeMapping = null) + private Expression CreateGetValueExpression( + Expression documentExpression, + string? alias, + IPropertyBase? propertyBase) { - var entityType = declaredType ?? docExpression switch + if (propertyBase is null && alias is null) { - RootReferenceExpression rootReferenceExpression => rootReferenceExpression.EntityType, - ObjectAccessExpression docAccessExpression => docAccessExpression.Navigation.TargetEntityType, - _ => _rootEntityType - }; + return documentExpression; + } - var innerExpression = docExpression; - if (ProjectionBindings.TryGetValue(docExpression, out var innerVariable)) + var innerExpression = documentExpression; + if (_projectionBindings.TryGetValue(documentExpression, out var innerVariable)) { innerExpression = innerVariable; } else { - innerExpression = docExpression switch + innerExpression = documentExpression switch { - RootReferenceExpression => CreateGetValueExpression(DocParameter, null, required, typeof(BsonDocument)), - ObjectAccessExpression docAccessExpression => CreateGetValueExpression(docAccessExpression.AccessExpression, - docAccessExpression.Name, required, typeof(BsonDocument)), + // TODO: handle more nesting; not currently used. + //RootReferenceExpression => CreateGetValueExpression(DocParameter, null, required, typeof(BsonDocument), propertyBase: propertyBase, declaredType: propertyBase!.DeclaringType), + //ObjectAccessExpression docAccessExpression => CreateGetValueExpression(docAccessExpression.AccessExpression, docAccessExpression.Name, required, typeof(BsonDocument), propertyBase: propertyBase, declaredType: propertyBase!.DeclaringType), _ => innerExpression }; } - var elementType = typeMapping?.ClrType ?? type; - return BsonBinding.CreateGetValueExpression(innerExpression, propertyName, required, elementType, entityType); + return BsonBinding.CreateGetValueExpression(innerExpression, alias, propertyBase); } private BlockExpression AddIncludes(BlockExpression shaperBlock) diff --git a/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoShapedQueryCompilingExpressionVisitor.cs b/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoShapedQueryCompilingExpressionVisitor.cs index 2956e390..a927a957 100644 --- a/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoShapedQueryCompilingExpressionVisitor.cs +++ b/src/MongoDB.EntityFrameworkCore/Query/Visitors/MongoShapedQueryCompilingExpressionVisitor.cs @@ -90,8 +90,7 @@ protected override Expression VisitShapedQuery(ShapedQueryExpression shapedQuery var shaperBody = shapedQueryExpression.ShaperExpression; shaperBody = new BsonDocumentInjectingExpressionVisitor().Visit(shaperBody); shaperBody = InjectEntityMaterializers(shaperBody); - shaperBody = new MongoProjectionBindingRemovingExpressionVisitor( - rootEntityType, mongoQueryExpression, bsonDocParameter, trackQueryResults) + shaperBody = new MongoProjectionBindingRemovingExpressionVisitor(mongoQueryExpression, bsonDocParameter, trackQueryResults) .Visit(shaperBody); var shaperLambda = Expression.Lambda( diff --git a/src/MongoDB.EntityFrameworkCore/Serializers/BsonSerializerFactory.cs b/src/MongoDB.EntityFrameworkCore/Serializers/BsonSerializerFactory.cs index 41cfeb46..06eb6ce9 100644 --- a/src/MongoDB.EntityFrameworkCore/Serializers/BsonSerializerFactory.cs +++ b/src/MongoDB.EntityFrameworkCore/Serializers/BsonSerializerFactory.cs @@ -282,7 +282,7 @@ private static IBsonSerializer GetCollectionSerializer(Type type, IBsonSerialize return CreateGenericSerializer(typeof(EnumerableInterfaceImplementerSerializer<,>), [type, itemType], childSerializer); } - internal static BsonSerializationInfo GetPropertySerializationInfo(IReadOnlyProperty property) + internal static BsonSerializationInfo GetPropertySerializationInfo(string? alias, IReadOnlyProperty property) { var serializer = CreateTypeSerializer(property); @@ -298,12 +298,12 @@ internal static BsonSerializationInfo GetPropertySerializationInfo(IReadOnlyProp if (binaryVectorType != null) { return new BsonSerializationInfo( - property.GetElementName(), + alias ?? property.GetElementName(), CreateBinaryVectorSerializer(type, binaryVectorType.Value), serializer.ValueType); } - return new BsonSerializationInfo(property.GetElementName(), serializer, serializer.ValueType); + return new BsonSerializationInfo(alias ?? property.GetElementName(), serializer, serializer.ValueType); } private static IBsonSerializer CreateBinaryVectorSerializer(Type type, BinaryVectorDataType binaryVectorDataType) diff --git a/src/MongoDB.EntityFrameworkCore/Serializers/EntitySerializer.cs b/src/MongoDB.EntityFrameworkCore/Serializers/EntitySerializer.cs index 695c86ee..de855e59 100644 --- a/src/MongoDB.EntityFrameworkCore/Serializers/EntitySerializer.cs +++ b/src/MongoDB.EntityFrameworkCore/Serializers/EntitySerializer.cs @@ -98,7 +98,7 @@ public bool TryGetMemberSerializationInfo(string memberName, out BsonSerializati var property = _entityType.FindProperty(memberName); if (property != null) { - serializationInfo = BsonSerializerFactory.GetPropertySerializationInfo(property); + serializationInfo = BsonSerializerFactory.GetPropertySerializationInfo(null, property); return true; } diff --git a/src/MongoDB.EntityFrameworkCore/Storage/BsonBinding.cs b/src/MongoDB.EntityFrameworkCore/Storage/BsonBinding.cs index 5392c684..0cafcaf0 100644 --- a/src/MongoDB.EntityFrameworkCore/Storage/BsonBinding.cs +++ b/src/MongoDB.EntityFrameworkCore/Storage/BsonBinding.cs @@ -14,14 +14,15 @@ */ using System; +using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Metadata; using MongoDB.Bson; using MongoDB.Bson.Serialization; -using MongoDB.EntityFrameworkCore.Extensions; using MongoDB.EntityFrameworkCore.Serializers; namespace MongoDB.EntityFrameworkCore.Storage; @@ -34,60 +35,40 @@ internal static class BsonBinding /// /// Create the expression which will obtain the value or intermediate value required by the shaper. /// - /// The expression to obtain the current . - /// The name of the field in the document that contains the desired value. - /// - /// if the field is required to be present in the document, - /// if it is optional. - /// - /// What to the value is to be treated as. - /// The the value will belong to in order to obtaining additional metadata. + /// The expression to obtain the current . + /// The name of the field in the document that contains the desired value. + /// The or mapping to the field. /// A compilable expression the shaper can use to obtain this value from a . /// If we can't find anything mapped to this name. public static Expression CreateGetValueExpression( - Expression bsonDocExpression, - string? name, - bool required, - Type mappedType, - ITypeBase declaredType) + Expression documentExpression, + string? alias, + IPropertyBase? propertyBase = null) { - if (name is null) + if (propertyBase is null && alias is null) { - return bsonDocExpression; + return documentExpression; } - if (mappedType == typeof(BsonArray)) + if (propertyBase is IProperty property) { - return CreateGetBsonArray(bsonDocExpression, name); + return CreateGetPropertyValue(documentExpression, alias, property); } - if (mappedType == typeof(BsonDocument)) - { - return CreateGetBsonDocument(bsonDocExpression, name, required, declaredType); - } + Debug.Assert(propertyBase is INavigationBase, + $"Not a property and not a navigation, but a {propertyBase.GetType().ShortDisplayName()}"); - var targetProperty = declaredType.FindProperty(name); - if (targetProperty != null) - { - return CreateGetPropertyValue(bsonDocExpression, Expression.Constant(targetProperty), - targetProperty.IsNullable ? mappedType.MakeNullable() : mappedType); - } - - if (declaredType is IEntityType entityType) - { - var navigationProperty = entityType.FindNavigation(name); - if (navigationProperty != null) - { - var fieldName = navigationProperty.TargetEntityType.GetContainingElementName()!; - return CreateGetElementValue(bsonDocExpression, fieldName, mappedType); - } - } - - throw new InvalidOperationException(CoreStrings.PropertyNotFound(name, declaredType.DisplayName())); + var navigationBase = (INavigationBase)propertyBase!; + return navigationBase.IsCollection + ? CreateGetBsonArray(documentExpression, alias, navigationBase) + : CreateGetBsonDocument(documentExpression, alias, navigationBase); } - private static MethodCallExpression CreateGetBsonArray(Expression bsonDocExpression, string name) - => Expression.Call(null, GetBsonArrayMethodInfo, bsonDocExpression, Expression.Constant(name)); + private static MethodCallExpression CreateGetBsonArray(Expression documentExpression, string? alias, INavigationBase navigation) + => Expression.Call( + GetBsonArrayMethodInfo, + documentExpression, + Expression.Constant(alias ?? navigation.Name, typeof(string))); private static readonly MethodInfo GetBsonArrayMethodInfo = typeof(BsonBinding).GetMethods(BindingFlags.Static | BindingFlags.NonPublic) @@ -107,10 +88,10 @@ private static readonly MethodInfo GetBsonArrayMethodInfo } private static MethodCallExpression CreateGetBsonDocument( - Expression bsonDocExpression, string name, bool required, ITypeBase declaredType) - => Expression.Call(null, GetBsonDocumentMethodInfo, bsonDocExpression, Expression.Constant(name), - Expression.Constant(required), - Expression.Constant(declaredType)); + Expression documentExpression, string? alias, INavigationBase navigationBase) + => Expression.Call(null, GetBsonDocumentMethodInfo, documentExpression, Expression.Constant(alias ?? navigationBase.Name), + Expression.Constant(navigationBase is INavigation { ForeignKey.IsRequiredDependent: true }), + Expression.Constant(navigationBase.DeclaringEntityType)); private static readonly MethodInfo GetBsonDocumentMethodInfo = typeof(BsonBinding).GetMethods(BindingFlags.Static | BindingFlags.NonPublic) @@ -129,23 +110,20 @@ private static readonly MethodInfo GetBsonDocumentMethodInfo } private static MethodCallExpression - CreateGetPropertyValue(Expression bsonDocExpression, Expression propertyExpression, Type resultType) => - Expression.Call(null, GetPropertyValueMethodInfo.MakeGenericMethod(resultType), bsonDocExpression, propertyExpression); - - private static MethodCallExpression CreateGetElementValue(Expression bsonDocExpression, string name, Type type) => - Expression.Call(null, GetElementValueMethodInfo.MakeGenericMethod(type), bsonDocExpression, Expression.Constant(name)); + CreateGetPropertyValue(Expression documentExpression, string? alias, IProperty property) + => Expression.Call( + GetPropertyValueMethodInfo.MakeGenericMethod(property.IsNullable ? property.ClrType.MakeNullable() : property.ClrType), + documentExpression, + Expression.Constant(alias ?? property.GetElementName(), typeof(string)), + Expression.Constant(property)); private static readonly MethodInfo GetPropertyValueMethodInfo = typeof(BsonBinding).GetMethods(BindingFlags.Static | BindingFlags.NonPublic) .Single(mi => mi.Name == nameof(GetPropertyValue)); - private static readonly MethodInfo GetElementValueMethodInfo - = typeof(BsonBinding).GetMethods(BindingFlags.Static | BindingFlags.NonPublic) - .Single(mi => mi.Name == nameof(GetElementValue)); - - internal static T? GetPropertyValue(BsonDocument document, IReadOnlyProperty property) + internal static T? GetPropertyValue(BsonDocument document, string? alias, IReadOnlyProperty property) { - var serializationInfo = BsonSerializerFactory.GetPropertySerializationInfo(property); + var serializationInfo = BsonSerializerFactory.GetPropertySerializationInfo(alias, property); if (TryReadElementValue(document, serializationInfo, out T? value)) { if (value == null && !property.IsNullable) @@ -159,7 +137,7 @@ private static readonly MethodInfo GetElementValueMethodInfo if (property.IsNullable) return default; - throw new InvalidOperationException($"Document element is missing for required non-nullable property '{property.Name}'."); + throw new InvalidOperationException($"Document element is missing for required non-nullable property '{alias ?? property.Name}'."); } internal static T? GetElementValue(BsonDocument document, string elementName) diff --git a/src/MongoDB.EntityFrameworkCore/Storage/MongoUpdate.cs b/src/MongoDB.EntityFrameworkCore/Storage/MongoUpdate.cs index b7b39f62..2f37e18b 100644 --- a/src/MongoDB.EntityFrameworkCore/Storage/MongoUpdate.cs +++ b/src/MongoDB.EntityFrameworkCore/Storage/MongoUpdate.cs @@ -203,7 +203,7 @@ internal static void WriteNonKeyProperties(IBsonWriter writer, IUpdateEntry entr private static void WriteProperty(IBsonWriter writer, object? value, IProperty property) { - var serializationInfo = BsonSerializerFactory.GetPropertySerializationInfo(property); + var serializationInfo = BsonSerializerFactory.GetPropertySerializationInfo(null, property); writer.WriteName(serializationInfo.ElementPath?.Last() ?? serializationInfo.ElementName); var root = BsonSerializationContext.CreateRoot(writer); serializationInfo.Serializer.Serialize(root, value); diff --git a/tests/MongoDB.EntityFrameworkCore.SpecificationTests/Query/NorthwindMiscellaneousQueryMongoTest.cs b/tests/MongoDB.EntityFrameworkCore.SpecificationTests/Query/NorthwindMiscellaneousQueryMongoTest.cs index 265a604a..c3f2c353 100644 --- a/tests/MongoDB.EntityFrameworkCore.SpecificationTests/Query/NorthwindMiscellaneousQueryMongoTest.cs +++ b/tests/MongoDB.EntityFrameworkCore.SpecificationTests/Query/NorthwindMiscellaneousQueryMongoTest.cs @@ -2384,43 +2384,46 @@ public override async Task Query_expression_with_to_string_and_contains(bool asy public override async Task Select_expression_long_to_string(bool async) { - // Fails: Client eval in final projection EF-250 - Assert.Contains( - "The property 'Order.ShipName' could not be found.", - (await Assert.ThrowsAsync(() => base.Select_expression_long_to_string(async))).Message); + await base.Select_expression_long_to_string(async); AssertMql( + """ + Orders.{ "$match" : { "OrderDate" : { "$ne" : null } } }, { "$project" : { "ShipName" : { "$toString" : { "$toLong" : "$_id" } }, "_id" : 0 } } + """ ); } public override async Task Select_expression_int_to_string(bool async) { - // Fails: Client eval in final projection EF-250 - Assert.Contains( - "The property 'Order.ShipName' could not be found.", - (await Assert.ThrowsAsync(() => base.Select_expression_int_to_string(async))).Message); + await base.Select_expression_int_to_string(async); AssertMql( -); + """ + Orders.{ "$match" : { "OrderDate" : { "$ne" : null } } }, { "$project" : { "ShipName" : { "$toString" : "$_id" }, "_id" : 0 } } + """ + ); } public override async Task ToString_with_formatter_is_evaluated_on_the_client(bool async) { // Fails: Client eval in final projection EF-250 Assert.Contains( - "The property 'Order.ShipName' could not be found.", - (await Assert.ThrowsAsync(() => base.ToString_with_formatter_is_evaluated_on_the_client(async))).Message); + "Expression not supported: o.OrderID.ToString(\"X\")", + (await Assert.ThrowsAsync(() => base.ToString_with_formatter_is_evaluated_on_the_client(async))).Message); AssertMql( -); + """ + Orders. + """ + ); } public override async Task Select_expression_other_to_string(bool async) { // Fails: Client eval in final projection EF-250 Assert.Contains( - "The property 'Order.ShipName' could not be found.", - (await Assert.ThrowsAsync(() => base.Select_expression_other_to_string(async))).Message); + "cannot be called with instance of type", + (await Assert.ThrowsAsync(() => base.Select_expression_other_to_string(async))).Message); AssertMql( ); @@ -2507,7 +2510,7 @@ public override async Task Select_expression_date_add_milliseconds_large_number_ { // Fails: Projections issue EF-76 Assert.Contains( - "Rewriting child expression", + "No coercion operator is defined", (await Assert.ThrowsAsync(() => base.Select_expression_date_add_milliseconds_large_number_divided(async))).Message); AssertMql( diff --git a/tests/MongoDB.EntityFrameworkCore.UnitTests/Storage/BsonBindingTests.cs b/tests/MongoDB.EntityFrameworkCore.UnitTests/Storage/BsonBindingTests.cs index 8b03a817..208ed865 100644 --- a/tests/MongoDB.EntityFrameworkCore.UnitTests/Storage/BsonBindingTests.cs +++ b/tests/MongoDB.EntityFrameworkCore.UnitTests/Storage/BsonBindingTests.cs @@ -64,7 +64,7 @@ public void Read_property_value() var property = entity.GetProperty(nameof(TestEntity.IntProperty)); var document = BsonDocument.Parse("{ IntProperty: 12 }"); - var value = BsonBinding.GetPropertyValue(document, property); + var value = BsonBinding.GetPropertyValue(document, null, property); Assert.Equal(12, value); } @@ -81,7 +81,7 @@ public void Read_missing_property_throws() var property = entity.GetProperty(nameof(TestEntity.IntProperty)); var document = BsonDocument.Parse("{ property: 12 }"); - var ex = Assert.Throws(() => BsonBinding.GetPropertyValue(document, property)); + var ex = Assert.Throws(() => BsonBinding.GetPropertyValue(document, null, property)); Assert.Contains("IntProperty", ex.Message); } @@ -97,7 +97,7 @@ public void Read_missing_nullable_property_returns_default() var property = entity.GetProperty(nameof(TestEntity.NullableProperty)); var document = BsonDocument.Parse("{ somevalue: 12 }"); - var value = BsonBinding.GetPropertyValue(document, property); + var value = BsonBinding.GetPropertyValue(document, null, property); Assert.Null(value); }