From 7d4b425069486936369c923a1fe4a83240eb84e2 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Mon, 15 Jul 2019 16:52:21 -0700 Subject: [PATCH] Cosmos: Add embedded collection support Flatten out shaper expression visitors Don't expand owned collection Fixes #16620 Part of #12086 --- EFCore.sln.DotSettings | 1 + .../Internal/CosmosNavigationExtensions.cs | 26 + ...osmosProjectionBindingExpressionVisitor.cs | 213 ++++-- ...yableMethodTranslatingExpressionVisitor.cs | 3 +- ...osShapedQueryCompilingExpressionVisitor.cs | 609 ++++++++++++------ .../CosmosSqlTranslatingExpressionVisitor.cs | 93 +-- .../Pipeline/EntityProjectionExpression.cs | 109 +++- .../Query/Pipeline/IAccessExpression.cs | 10 + .../Query/Pipeline/KeyAccessExpression.cs | 27 +- .../Query/Pipeline/ObjectAccessExpression.cs | 32 +- .../ObjectArrayProjectionExpression.cs | 79 +++ .../Query/Pipeline/ProjectionExpression.cs | 13 +- .../Query/Pipeline/QuerySqlGenerator.cs | 19 +- .../Query/Pipeline/RootReferenceExpression.cs | 32 +- .../Query/Pipeline/SelectExpression.cs | 39 +- .../Query/Pipeline/SqlExpressionFactory.cs | 2 +- .../Query/Pipeline/SqlExpressionVisitor.cs | 7 + .../Query/Pipeline/EntityValuesExpression.cs | 5 +- .../Query/Pipeline/InMemoryQueryExpression.cs | 44 +- .../InMemoryShapedQueryExpressionVisitor.cs | 2 +- ...cs => CollectionInitializingExpression.cs} | 0 .../Pipeline/EntityProjectionExpression.cs | 42 +- ...ctory2.cs => IQuerySqlGeneratorFactory.cs} | 0 .../IncludeCompilingExpressionVisitor.cs | 13 +- ...actory2.cs => QuerySqlGeneratorFactory.cs} | 0 ...jectionBindingRemovingExpressionVisitor.cs | 5 +- ...yableMethodTranslatingExpressionVisitor.cs | 5 +- ...alShapedQueryCompilingExpressionVisitor.cs | 2 +- ...ShapedQueryOptimizingExpressionVisitors.cs | 5 +- .../SqlExpressions/SelectExpression.cs | 9 +- .../EntityFrameworkQueryableExtensions.cs | 1 - src/EFCore/Metadata/Internal/EntityType.cs | 9 - .../IEntityMaterializerSource.cs | 0 .../Internal/EntityMaterializerSource.cs | 0 ...terializeCollectionNavigationExpression.cs | 18 +- .../NavigationExpansion/NavigationState.cs | 2 - .../CollectionNavigationRewritingVisitor.cs | 84 ++- .../NavigationExpandingVisitor_MethodCall.cs | 3 +- .../NavigationExpansionReducingVisitor.cs | 26 +- .../NavigationPropertyUnbindingVisitor.cs | 66 +- .../Pipeline/CollectionShaperExpression.cs | 13 +- ....cs => IQueryCompilationContextFactory.cs} | 0 ...thodTranslatingExpressionVisitorFactory.cs | 1 - .../Pipeline/ProjectionBindingExpression.cs | 42 +- src/EFCore/Query/Pipeline/ProjectionMember.cs | 10 +- .../QueryCompilationContextFactory.cs | 6 +- .../Pipeline/ShapedQueryExpressionVisitor.cs | 119 ++-- src/EFCore/Query/QueryContext.cs | 6 +- src/EFCore/Storage/MaterializationContext.cs | 1 - .../NestedDocumentsTest.cs | 39 +- .../Query/OwnedQueryCosmosTest.cs | 22 +- .../Query/AsTrackingTestBase.cs | 1 - .../Query/ChangeTrackingTestBase.cs | 1 - .../Query/CompiledQueryTestBase.cs | 1 - .../Query/ComplexNavigationsQueryTestBase.cs | 1 - .../Query/OwnedQueryTestBase.cs | 41 +- .../Query/OwnedQuerySqlServerTest.cs | 12 +- 57 files changed, 1212 insertions(+), 759 deletions(-) create mode 100644 src/EFCore.Cosmos/Metadata/Internal/CosmosNavigationExtensions.cs create mode 100644 src/EFCore.Cosmos/Query/Pipeline/IAccessExpression.cs create mode 100644 src/EFCore.Cosmos/Query/Pipeline/ObjectArrayProjectionExpression.cs rename src/EFCore.Relational/Query/Pipeline/{CollectionInitializingExperssion.cs => CollectionInitializingExpression.cs} (100%) rename src/EFCore.Relational/Query/Pipeline/{IQuerySqlGeneratorFactory2.cs => IQuerySqlGeneratorFactory.cs} (100%) rename src/EFCore.Relational/Query/Pipeline/{QuerySqlGeneratorFactory2.cs => QuerySqlGeneratorFactory.cs} (100%) rename src/EFCore/{Metadata => Query}/IEntityMaterializerSource.cs (100%) rename src/EFCore/{Metadata => Query}/Internal/EntityMaterializerSource.cs (100%) rename src/EFCore/Query/Pipeline/{IQueryCompilationContextFactory2.cs => IQueryCompilationContextFactory.cs} (100%) diff --git a/EFCore.sln.DotSettings b/EFCore.sln.DotSettings index ce440522f6b..aec0e6f5183 100644 --- a/EFCore.sln.DotSettings +++ b/EFCore.sln.DotSettings @@ -195,6 +195,7 @@ Licensed under the Apache License, Version 2.0. See License.txt in the project r True True True + True True True True \ No newline at end of file diff --git a/src/EFCore.Cosmos/Metadata/Internal/CosmosNavigationExtensions.cs b/src/EFCore.Cosmos/Metadata/Internal/CosmosNavigationExtensions.cs new file mode 100644 index 00000000000..9fdb9fadd25 --- /dev/null +++ b/src/EFCore.Cosmos/Metadata/Internal/CosmosNavigationExtensions.cs @@ -0,0 +1,26 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal +{ + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static class CosmosNavigationExtensions + { + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public static bool IsEmbedded(this INavigation navigation) + => !navigation.IsDependentToPrincipal() + && !navigation.ForeignKey.DeclaringEntityType.IsDocumentRoot(); + } +} diff --git a/src/EFCore.Cosmos/Query/Pipeline/CosmosProjectionBindingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Pipeline/CosmosProjectionBindingExpressionVisitor.cs index 045577dce2c..25382f0e356 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/CosmosProjectionBindingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/CosmosProjectionBindingExpressionVisitor.cs @@ -5,6 +5,8 @@ using System.Collections.Generic; using System.Linq.Expressions; using System.Reflection; +using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.NavigationExpansion; @@ -59,68 +61,63 @@ public override Expression Visit(Expression expression) return null; } - if (!(expression is NewExpression - || expression is MemberInitExpression - || expression is EntityShaperExpression)) + if (expression is NewExpression + || expression is MemberInitExpression + || expression is EntityShaperExpression) { - // This skips the group parameter from GroupJoin - if (expression is ParameterExpression parameter - && parameter.Type.IsGenericType - && parameter.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) - { - return parameter; - } + return base.Visit(expression); + } - if (_clientEval) + // This skips the group parameter from GroupJoin + if (expression is ParameterExpression parameter + && parameter.Type.IsGenericType + && parameter.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + return parameter; + } + + if (_clientEval) + { + switch (expression) { - if (expression is ConstantExpression) - { + case ConstantExpression _: return expression; - } - if (expression is ParameterExpression parameterExpression) - { + case ParameterExpression parameterExpression: return Expression.Call( _getParameterValueMethodInfo.MakeGenericMethod(parameterExpression.Type), QueryCompilationContext.QueryContextParameter, Expression.Constant(parameterExpression.Name)); - } - - //if (expression is MethodCallExpression methodCallExpression - // && methodCallExpression.Method.Name == "MaterializeCollectionNavigation") - //{ - // var result = _queryableMethodTranslatingExpressionVisitor.TranslateSubquery(methodCallExpression.Arguments[0]); - // var navigation = (INavigation)((ConstantExpression)methodCallExpression.Arguments[1]).Value; - // return _selectExpression.AddCollectionProjection(result, navigation); - //} - - var translation = _sqlTranslator.Translate(expression); - if (translation == null) - { + case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression: return base.Visit(expression); - } - else - { - return new ProjectionBindingExpression( - _selectExpression, _selectExpression.AddToProjection(translation), expression.Type); - } + //return _selectExpression.AddCollectionProjection( + // _queryableMethodTranslatingExpressionVisitor.TranslateSubquery( + // materializeCollectionNavigationExpression.Subquery), + // materializeCollectionNavigationExpression.Navigation, null); } - else - { - var translation = _sqlTranslator.Translate(expression); - if (translation == null) - { - return null; - } - - _projectionMapping[_projectionMembers.Peek()] = translation; - return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type); + var translation = _sqlTranslator.Translate(expression); + if (translation == null) + { + return base.Visit(expression); } + + return new ProjectionBindingExpression( + _selectExpression, _selectExpression.AddToProjection(translation), expression.Type); } + else + { + var translation = _sqlTranslator.Translate(expression); + if (translation == null) + { + return null; + } - return base.Visit(expression); + _projectionMapping[_projectionMembers.Peek()] = translation; + + return new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), expression.Type); + } } private static readonly MethodInfo _getParameterValueMethodInfo @@ -132,39 +129,123 @@ private static T GetParameterValue(QueryContext queryContext, string paramete #pragma warning restore IDE0052 // Remove unread private members => (T)queryContext.ParameterValues[parameterName]; - protected override Expression VisitExtension(Expression extensionExpression) + protected override Expression VisitMember(MemberExpression memberExpression) { - if (extensionExpression is EntityShaperExpression entityShaperExpression) + if (!_clientEval) { - var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; - VerifySelectExpression(projectionBindingExpression); + return null; + } - if (_clientEval) - { - var entityProjection = (EntityProjectionExpression)_selectExpression.GetMappedProjection( - projectionBindingExpression.ProjectionMember); + var innerExpression = Visit(memberExpression.Expression); - return entityShaperExpression.Update( - new ProjectionBindingExpression( - _selectExpression, _selectExpression.AddToProjection(entityProjection), typeof(ValueBuffer))); + EntityShaperExpression shaperExpression; + switch (innerExpression) + { + case EntityShaperExpression shaper: + shaperExpression = shaper; + break; + + case UnaryExpression unaryExpression: + shaperExpression = unaryExpression.Operand as EntityShaperExpression; + if (shaperExpression == null) + { + return memberExpression.Update(innerExpression); + } + break; + + default: + return memberExpression.Update(innerExpression); + } + + EntityProjectionExpression innerEntityProjection; + switch (shaperExpression.ValueBufferExpression) + { + case ProjectionBindingExpression innerProjectionBindingExpression: + innerEntityProjection = (EntityProjectionExpression)_selectExpression.Projection[ + innerProjectionBindingExpression.Index.Value].Expression; + break; + + case UnaryExpression unaryExpression: + innerEntityProjection = (EntityProjectionExpression)((UnaryExpression)unaryExpression.Operand).Operand; + break; + + default: + throw new InvalidOperationException(); + } + + var navigationProjection = innerEntityProjection.BindMember(memberExpression.Member, innerExpression.Type, out var propertyBase); + + if (!(propertyBase is INavigation navigation) + || !navigation.IsEmbedded()) + { + return memberExpression.Update(innerExpression); + } + + switch (navigationProjection) + { + case EntityProjectionExpression entityProjection: + return new EntityShaperExpression( + navigation.GetTargetType(), + Expression.Convert(Expression.Convert(entityProjection, typeof(object)), typeof(ValueBuffer)), + nullable: true); + + case ObjectArrayProjectionExpression objectArrayProjectionExpression: + { + var innerShaperExpression = new EntityShaperExpression( + navigation.GetTargetType(), + Expression.Convert( + Expression.Convert(objectArrayProjectionExpression.InnerProjection, typeof(object)), typeof(ValueBuffer)), + nullable: true); + + return new CollectionShaperExpression( + objectArrayProjectionExpression, + innerShaperExpression, + navigation, + innerShaperExpression.EntityType.ClrType); } - else + + default: + throw new InvalidOperationException(); + } + } + + protected override Expression VisitExtension(Expression extensionExpression) + { + switch (extensionExpression) + { + case EntityShaperExpression entityShaperExpression: { - _projectionMapping[_projectionMembers.Peek()] - = _selectExpression.GetMappedProjection( + var projectionBindingExpression = (ProjectionBindingExpression)entityShaperExpression.ValueBufferExpression; + VerifySelectExpression(projectionBindingExpression); + + if (_clientEval) + { + var entityProjection = (EntityProjectionExpression)_selectExpression.GetMappedProjection( projectionBindingExpression.ProjectionMember); + return entityShaperExpression.Update( + new ProjectionBindingExpression( + _selectExpression, _selectExpression.AddToProjection(entityProjection), typeof(ValueBuffer))); + } + + _projectionMapping[_projectionMembers.Peek()] + = _selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember); + return entityShaperExpression.Update( new ProjectionBindingExpression(_selectExpression, _projectionMembers.Peek(), typeof(ValueBuffer))); } - } - if (extensionExpression is IncludeExpression includeExpression) - { - return _clientEval ? base.VisitExtension(includeExpression) : includeExpression; - } + case MaterializeCollectionNavigationExpression materializeCollectionNavigationExpression: + return materializeCollectionNavigationExpression.Navigation.IsEmbedded() + ? base.Visit(materializeCollectionNavigationExpression.Subquery) + : base.VisitExtension(materializeCollectionNavigationExpression); - throw new InvalidOperationException(new ExpressionPrinter().Print(extensionExpression)); + case IncludeExpression includeExpression: + return _clientEval ? base.VisitExtension(includeExpression) : null; + + default: + throw new InvalidOperationException(new ExpressionPrinter().Print(extensionExpression)); + } } protected override Expression VisitNew(NewExpression newExpression) diff --git a/src/EFCore.Cosmos/Query/Pipeline/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Pipeline/CosmosQueryableMethodTranslatingExpressionVisitor.cs index fbbba064437..125c3f55285 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.Pipeline; using Microsoft.EntityFrameworkCore.Storage; @@ -461,7 +462,7 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so return source; } - throw new InvalidOperationException(); + throw new InvalidOperationException("Unable to translate Where expression: " + new ExpressionPrinter().Print(predicate)); } private SqlExpression TranslateExpression(Expression expression) diff --git a/src/EFCore.Cosmos/Query/Pipeline/CosmosShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Pipeline/CosmosShapedQueryCompilingExpressionVisitor.cs index 41a1569faa9..6486fa6f63b 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/CosmosShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/CosmosShapedQueryCompilingExpressionVisitor.cs @@ -4,15 +4,16 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.ChangeTracking.Internal; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Conventions; using Microsoft.EntityFrameworkCore.Cosmos.Metadata.Internal; using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore.Infrastructure; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; @@ -49,10 +50,14 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s { var selectExpression = (SelectExpression)shapedQueryExpression.QueryExpression; selectExpression.ApplyProjection(); - var jObjectParameter = Expression.Parameter(typeof(JObject), "jObject"); - var shaperBody = new CosmosProjectionBindingRemovingExpressionVisitor(selectExpression, jObjectParameter, this) - .Visit(shapedQueryExpression.ShaperExpression); + + var shaperBody = shapedQueryExpression.ShaperExpression; + shaperBody = new JObjectInjectingExpressionVisitor() + .Visit(shaperBody); + shaperBody = InjectEntityMaterializers(shaperBody); + shaperBody = new CosmosProjectionBindingRemovingExpressionVisitor(selectExpression, jObjectParameter, TrackQueryResults) + .Visit(shaperBody); var shaperLambda = Expression.Lambda( shaperBody, @@ -72,8 +77,94 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s Expression.Constant(_logger)); } + private class JObjectInjectingExpressionVisitor : ExpressionVisitor + { + private int _currentEntityIndex; + + protected override Expression VisitExtension(Expression extensionExpression) + { + switch (extensionExpression) + { + case EntityShaperExpression shaperExpression: + { + _currentEntityIndex++; + + var valueBufferExpression = shaperExpression.ValueBufferExpression; + + var jObjectVariable = Expression.Variable( + typeof(JObject), + "jObject" + _currentEntityIndex); + var variables = new List + { + jObjectVariable + }; + + var expressions = new List + { + Expression.Assign( + jObjectVariable, + Expression.TypeAs( + valueBufferExpression, + typeof(JObject))), + Expression.Condition( + Expression.Equal(jObjectVariable, Expression.Constant(null, jObjectVariable.Type)), + Expression.Constant(null, shaperExpression.Type), + shaperExpression) + }; + + return Expression.Block( + shaperExpression.Type, + variables, + expressions); + } + + case CollectionShaperExpression collectionShaperExpression: + { + _currentEntityIndex++; + + var resultType = typeof(IEnumerable<>).MakeGenericType(collectionShaperExpression.ElementType); + + var jArrayVariable = Expression.Variable( + typeof(JArray), + "jArray" + _currentEntityIndex); + var variables = new List + { + jArrayVariable + }; + + var expressions = new List + { + Expression.Assign( + jArrayVariable, + Expression.TypeAs( + collectionShaperExpression.Projection, + typeof(JArray))), + + Expression.Condition( + Expression.Equal(jArrayVariable, Expression.Constant(null, jArrayVariable.Type)), + Expression.Constant(null, resultType), + Expression.Convert(collectionShaperExpression, resultType)) + }; + + return Expression.Block( + resultType, + variables, + expressions); + } + } + + return base.VisitExtension(extensionExpression); + } + } + private class CosmosProjectionBindingRemovingExpressionVisitor : ExpressionVisitor { + private static readonly MethodInfo _selectMethodInfo + = typeof(Enumerable).GetTypeInfo().GetDeclaredMethods(nameof(Enumerable.Select)) + .Single(mi => mi.GetParameters().Length == 2 && mi.GetParameters()[1].ParameterType.GetGenericArguments().Length == 3); + private static readonly MethodInfo _castMethodInfo + = typeof(Enumerable).GetTypeInfo().GetDeclaredMethods(nameof(Enumerable.Cast)) + .Single(mi => mi.GetParameters().Length == 1); private static readonly MethodInfo _getItemMethodInfo = typeof(JObject).GetTypeInfo().GetRuntimeProperties() .Single(pi => pi.Name == "Item" && pi.GetIndexParameters()[0].ParameterType == typeof(string)) @@ -84,53 +175,110 @@ private static readonly MethodInfo _toObjectMethodInfo private static readonly MethodInfo _isNullMethodInfo = typeof(CosmosProjectionBindingRemovingExpressionVisitor).GetTypeInfo().GetRuntimeMethods() .Single(mi => mi.Name == nameof(IsNull)); - private static readonly MethodInfo _isAssignableFromMethodInfo - = typeof(EntityTypeExtensions).GetMethod(nameof(EntityTypeExtensions.IsAssignableFrom), new[] { typeof(IEntityType), typeof(IEntityType) }); - private static readonly MethodInfo _accessorAddRangeMethodInfo - = typeof(IClrCollectionAccessor).GetMethod(nameof(IClrCollectionAccessor.AddRange), new[] { typeof(object), typeof(IEnumerable) }); - private readonly CosmosShapedQueryCompilingExpressionVisitor _shapedQueryCompilingExpressionVisitor; private readonly SelectExpression _selectExpression; + private readonly ParameterExpression _jObjectParameter; + private readonly bool _trackQueryResults; - private readonly IDictionary _materializationContextBindings - = new Dictionary(); - private ParameterExpression _jObjectParameter; - private EmbeddedNavigationInfo _currentEmbeddedNavigationInfo; - private int _currentEntityIndex; + private readonly IDictionary _materializationContextBindings + = new Dictionary(); + private readonly IDictionary _projectionBindings + = new Dictionary(); public CosmosProjectionBindingRemovingExpressionVisitor( SelectExpression selectExpression, ParameterExpression jObjectParameter, - CosmosShapedQueryCompilingExpressionVisitor shapedQueryCompilingExpressionVisitor) + bool trackQueryResults) { _selectExpression = selectExpression; _jObjectParameter = jObjectParameter; - _shapedQueryCompilingExpressionVisitor = shapedQueryCompilingExpressionVisitor; + _trackQueryResults = trackQueryResults; } protected override Expression VisitBinary(BinaryExpression binaryExpression) { - if (binaryExpression.NodeType == ExpressionType.Assign - && binaryExpression.Left is ParameterExpression parameterExpression - && parameterExpression.Type == typeof(MaterializationContext)) + if (binaryExpression.NodeType == ExpressionType.Assign) { - var newExpression = (NewExpression)binaryExpression.Right; + if (binaryExpression.Left is ParameterExpression parameterExpression) + { + if (parameterExpression.Type == typeof(JObject) + || parameterExpression.Type == typeof(JArray)) + { + string storeName = null; + var projectionExpression = ((UnaryExpression)binaryExpression.Right).Operand; + if (projectionExpression is ProjectionBindingExpression projectionBindingExpression) + { + var projection = GetProjection(projectionBindingExpression); + projectionExpression = projection.Expression; + storeName = projection.Alias; + } else if (projectionExpression is UnaryExpression convertExpression) + { + projectionExpression = ((UnaryExpression)convertExpression.Operand).Operand; + } - _materializationContextBindings[parameterExpression] = _jObjectParameter; + Expression accessExpression; + if (projectionExpression is ObjectArrayProjectionExpression objectArrayProjectionExpression) + { + accessExpression = objectArrayProjectionExpression.AccessExpression; + _projectionBindings[objectArrayProjectionExpression] = parameterExpression; + storeName ??= objectArrayProjectionExpression.Name; + } + else + { + var entityProjectionExpression = (EntityProjectionExpression)projectionExpression; + _projectionBindings[entityProjectionExpression.AccessExpression] = parameterExpression; + storeName ??= entityProjectionExpression.Name; + switch (entityProjectionExpression.AccessExpression) + { + case ObjectAccessExpression innerObjectAccessExpression: + accessExpression = innerObjectAccessExpression.AccessExpression; + break; + case RootReferenceExpression _: + accessExpression = _jObjectParameter; + break; + default: + throw new InvalidOperationException(); + } + } - var updatedExpression = Expression.New(newExpression.Constructor, - Expression.Constant(ValueBuffer.Empty), - newExpression.Arguments[1]); + var valueExpression = CreateGetStoreValueExpression(accessExpression, storeName, parameterExpression.Type); - return Expression.MakeBinary(ExpressionType.Assign, binaryExpression.Left, updatedExpression); - } + return Expression.MakeBinary(ExpressionType.Assign, binaryExpression.Left, valueExpression); + } - if (binaryExpression.NodeType == ExpressionType.Assign - && binaryExpression.Left is MemberExpression memberExpression - && memberExpression.Member is FieldInfo fieldInfo - && fieldInfo.IsInitOnly) - { - return memberExpression.Assign(Visit(binaryExpression.Right)); + if (parameterExpression.Type == typeof(MaterializationContext)) + { + var newExpression = (NewExpression)binaryExpression.Right; + + EntityProjectionExpression entityProjectionExpression; + if (newExpression.Arguments[0] is ProjectionBindingExpression projectionBindingExpression) + { + var projection = GetProjection(projectionBindingExpression); + entityProjectionExpression = (EntityProjectionExpression)projection.Expression; + } + else + { + var projection = ((UnaryExpression)((UnaryExpression)newExpression.Arguments[0]).Operand).Operand; + entityProjectionExpression = (EntityProjectionExpression)projection; + } + + _materializationContextBindings[parameterExpression] + = _projectionBindings[entityProjectionExpression.AccessExpression]; + + var updatedExpression = Expression.New(newExpression.Constructor, + Expression.Constant(ValueBuffer.Empty), + newExpression.Arguments[1]); + + return Expression.MakeBinary(ExpressionType.Assign, binaryExpression.Left, updatedExpression); + } + } + + if (binaryExpression.Left is MemberExpression memberExpression + && memberExpression.Member is FieldInfo fieldInfo + && fieldInfo.IsInitOnly) + { + return memberExpression.Assign(Visit(binaryExpression.Right)); + } } return base.VisitBinary(binaryExpression); @@ -145,8 +293,7 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp Expression innerExpression; if (methodCallExpression.Arguments[0] is ProjectionBindingExpression projectionBindingExpression) { - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); - var projection = _selectExpression.Projection[projectionIndex]; + var projection = GetProjection(projectionBindingExpression); innerExpression = Expression.Convert( CreateReadJTokenExpression(_jObjectParameter, projection.Alias), @@ -176,175 +323,258 @@ protected override Expression VisitExtension(Expression extensionExpression) switch (extensionExpression) { case ProjectionBindingExpression projectionBindingExpression: - { - if (_currentEmbeddedNavigationInfo != null) - { - return extensionExpression; - } + { + var projection = GetProjection(projectionBindingExpression); - var projectionIndex = (int)GetProjectionIndex(projectionBindingExpression); - var projection = _selectExpression.Projection[projectionIndex]; + return CreateGetStoreValueExpression( + _jObjectParameter, + projection.Alias, + projectionBindingExpression.Type, (projection.Expression as SqlExpression)?.TypeMapping); + } - return CreateGetStoreValueExpression( - _jObjectParameter, - projection.Alias, - ((SqlExpression)projection.Expression).TypeMapping, - projectionBindingExpression.Type); - } - case EntityShaperExpression shaperExpression: + case CollectionShaperExpression collectionShaperExpression: + { + ObjectArrayProjectionExpression objectArrayProjection; + switch (collectionShaperExpression.Projection) { - _currentEntityIndex++; - - var jObjectVariable = Expression.Variable(typeof(JObject), - "jObject" + _currentEntityIndex); - var variables = new List { jObjectVariable }; - - var expressions = new List(); - var valueBufferExpression = shaperExpression.ValueBufferExpression; - if (_currentEmbeddedNavigationInfo?.Navigation == null) - { - var projectionIndex = (int)GetProjectionIndex((ProjectionBindingExpression)valueBufferExpression); - var projection = _selectExpression.Projection[projectionIndex]; - - expressions.Add( - Expression.Assign( - jObjectVariable, - Expression.TypeAs( - CreateReadJTokenExpression(_jObjectParameter, projection.Alias), - typeof(JObject)))); - } - else - { - expressions.Add( - Expression.Assign( - jObjectVariable, - Expression.TypeAs( - CreateReadJTokenExpression( - _jObjectParameter, - _currentEmbeddedNavigationInfo.Navigation.GetTargetType().GetCosmosContainingPropertyName()), - typeof(JObject)))); - } - - var parentJObject = _jObjectParameter; - _jObjectParameter = jObjectVariable; + case ProjectionBindingExpression projectionBindingExpression: + var projection = GetProjection(projectionBindingExpression); + objectArrayProjection = (ObjectArrayProjectionExpression)projection.Expression; + break; + case ObjectArrayProjectionExpression arrayProjectionExpression: + objectArrayProjection = arrayProjectionExpression; + break; + default: + throw new InvalidOperationException(); + } - var materializerExpression = Visit(_shapedQueryCompilingExpressionVisitor.InjectEntityMaterializer(shaperExpression)); - materializerExpression = InjectEmbeddedMaterializers(materializerExpression, valueBufferExpression); + var jArray = _projectionBindings[objectArrayProjection]; + var jObjectParameter = Expression.Parameter(typeof(JObject), jArray.Name + "object"); + var ordinalParameter = Expression.Parameter(typeof(int), "ordinal"); - _jObjectParameter = parentJObject; + _projectionBindings[objectArrayProjection.InnerProjection.AccessExpression] = jObjectParameter; - expressions.Add(Expression.Condition( - Expression.Equal(jObjectVariable, Expression.Constant(null, jObjectVariable.Type)), - Expression.Constant(null, shaperExpression.Type), - materializerExpression)); + var innerShaper = Visit(collectionShaperExpression.InnerShaper); + return Expression.Call( + _selectMethodInfo.MakeGenericMethod(typeof(JObject), innerShaper.Type), + Expression.Call( + _castMethodInfo.MakeGenericMethod(typeof(JObject)), + jArray), + Expression.Lambda(innerShaper, jObjectParameter, ordinalParameter)); + } - return Expression.Block( - shaperExpression.Type, - variables, - expressions); - } case IncludeExpression includeExpression: - var fk = includeExpression.Navigation.ForeignKey; + var navigation = includeExpression.Navigation; + var fk = navigation.ForeignKey; if (includeExpression.Navigation.IsDependentToPrincipal() || fk.DeclaringEntityType.IsDocumentRoot()) { throw new InvalidOperationException("Non-embedded IncludeExpression " + new ExpressionPrinter().Print(includeExpression)); } - var parentNavigation = _currentEmbeddedNavigationInfo ?? new EmbeddedNavigationInfo(); - _currentEmbeddedNavigationInfo = new EmbeddedNavigationInfo(includeExpression.Navigation); - - Visit(includeExpression.NavigationExpression); - - parentNavigation.EmbeddedNavigations.Add(_currentEmbeddedNavigationInfo); - _currentEmbeddedNavigationInfo = parentNavigation; - - return Visit(includeExpression.EntityExpression); + // These are the expressions added by JObjectInjectingExpressionVisitor + var jObjectBlock = (BlockExpression)Visit(includeExpression.EntityExpression); + var jObjectCondition = (ConditionalExpression)jObjectBlock.Expressions[jObjectBlock.Expressions.Count - 1]; + + var shaperBlock = (BlockExpression)jObjectCondition.IfFalse; + var shaperExpressions = new List(shaperBlock.Expressions); + var instanceVariable = shaperExpressions[shaperExpressions.Count - 1]; + shaperExpressions.RemoveAt(shaperExpressions.Count - 1); + + var includeMethod = navigation.IsCollection() ? _includeCollectionMethodInfo : _includeReferenceMethodInfo; + var includingClrType = navigation.DeclaringEntityType.ClrType; + var relatedEntityClrType = navigation.GetTargetType().ClrType; + var entityEntryVariable = _trackQueryResults + ? shaperBlock.Variables.Single(v => v.Type == typeof(InternalEntityEntry)) + : (Expression)Expression.Constant(null, typeof(InternalEntityEntry)); + var concreteEntityTypeVariable = shaperBlock.Variables.Single(v => v.Type == typeof(IEntityType)); + var inverseNavigation = navigation.FindInverse(); + var fixup = GenerateFixup( + includingClrType, relatedEntityClrType, navigation, inverseNavigation) + .Compile(); + var navigationExpression = Visit(includeExpression.NavigationExpression); + + shaperExpressions.Add(Expression.Call( + includeMethod.MakeGenericMethod(includingClrType, relatedEntityClrType), + entityEntryVariable, + instanceVariable, + concreteEntityTypeVariable, + navigationExpression, + Expression.Constant(navigation), + Expression.Constant(inverseNavigation, typeof(INavigation)), + Expression.Constant(fixup))); + + shaperExpressions.Add(instanceVariable); + shaperBlock = shaperBlock.Update(shaperBlock.Variables, shaperExpressions); + + var jObjectExpressions = new List(jObjectBlock.Expressions); + jObjectExpressions.RemoveAt(jObjectExpressions.Count - 1); + + jObjectExpressions.Add( + jObjectCondition.Update(jObjectCondition.Test, jObjectCondition.IfTrue, shaperBlock)); + + return jObjectBlock.Update(jObjectBlock.Variables, jObjectExpressions); } return base.VisitExtension(extensionExpression); } - private Expression InjectEmbeddedMaterializers(Expression materializerExpression, Expression valueBufferExpression) + private static readonly MethodInfo _includeReferenceMethodInfo + = typeof(CosmosProjectionBindingRemovingExpressionVisitor).GetTypeInfo() + .GetDeclaredMethod(nameof(IncludeReference)); + + private static void IncludeReference( + InternalEntityEntry entry, + object entity, + IEntityType entityType, + TIncludedEntity relatedEntity, + INavigation navigation, + INavigation inverseNavigation, + Action fixup) { - if (_currentEmbeddedNavigationInfo == null - || _currentEmbeddedNavigationInfo.EmbeddedNavigations.Count <= 0 - || !(materializerExpression is BlockExpression blockExpression)) + if (entity == null + || !navigation.DeclaringEntityType.IsAssignableFrom(entityType)) { - return materializerExpression; + return; } - var expressions = new List(blockExpression.Expressions); - var instanceVariable = expressions[expressions.Count - 1]; - expressions.RemoveAt(expressions.Count - 1); + if (entry == null) + { + var includingEntity = (TIncludingEntity)entity; + SetIsLoadedNoTracking(includingEntity, navigation); + if (relatedEntity != null) + { + fixup(includingEntity, relatedEntity); + if (inverseNavigation != null + && !inverseNavigation.IsCollection()) + { + SetIsLoadedNoTracking(relatedEntity, inverseNavigation); + } + } + } + // For non-null relatedEntity StateManager will set the flag + else if (relatedEntity == null) + { + entry.SetIsLoaded(navigation); + } + } - var concreteEntityTypeVariableName = "entityType" + _currentEntityIndex; - var concreteEntityTypeVariable = blockExpression.Variables.Single(v => v.Name == concreteEntityTypeVariableName); + private static readonly MethodInfo _includeCollectionMethodInfo + = typeof(CosmosProjectionBindingRemovingExpressionVisitor).GetTypeInfo() + .GetDeclaredMethod(nameof(IncludeCollection)); + + private static void IncludeCollection( + InternalEntityEntry entry, + object entity, + IEntityType entityType, + IEnumerable relatedEntities, + INavigation navigation, + INavigation inverseNavigation, + Action fixup) + { + if (entity == null + || !navigation.DeclaringEntityType.IsAssignableFrom(entityType)) + { + return; + } - var embeddedExpressions = new List(blockExpression.Expressions); - var parentEmbeddedNavigationInfo = _currentEmbeddedNavigationInfo; - foreach (var embeddedNavigation in parentEmbeddedNavigationInfo.EmbeddedNavigations) + if (entry == null) { - var navigation = embeddedNavigation.Navigation; - var embeddedValueBufferExpression = _shapedQueryCompilingExpressionVisitor.CreateReadValueExpression( - valueBufferExpression, - valueBufferExpression.Type, - navigation.GetIndex(), - navigation); - - _currentEmbeddedNavigationInfo = embeddedNavigation; - var embeddedShaper = Visit( - new EntityShaperExpression(navigation.GetTargetType(), embeddedValueBufferExpression, nullable: true)); - - var navigationMemberInfo = navigation.GetMemberInfo(forConstruction: true, forSet: true); - var convertedInstanceVariable = navigationMemberInfo.DeclaringType.IsAssignableFrom(instanceVariable.Type) - ? instanceVariable - : Expression.Convert(instanceVariable, navigationMemberInfo.DeclaringType); - - Expression navigationExpression; - if (navigation.IsCollection()) + var includingEntity = (TIncludingEntity)entity; + SetIsLoadedNoTracking(includingEntity, navigation); + + foreach (var relatedEntity in relatedEntities) { - var accessorExpression = Expression.Constant(new ClrCollectionAccessorFactory().Create(navigation)); - navigationExpression = Expression.Call(accessorExpression, _accessorAddRangeMethodInfo, - convertedInstanceVariable, new CollectionShaperExpression(null, embeddedShaper, navigation, null)); - throw new NotImplementedException(); + fixup(includingEntity, relatedEntity); + if (inverseNavigation != null) + { + SetIsLoadedNoTracking(relatedEntity, inverseNavigation); + } } - else + } + else + { + entry.SetIsLoaded(navigation); + using (var enumerator = relatedEntities.GetEnumerator()) { - navigationExpression = Expression.Assign(Expression.MakeMemberAccess( - convertedInstanceVariable, - navigationMemberInfo), - embeddedShaper); + while (enumerator.MoveNext()) + { + } } + } + } - var embeddedMaterializer = Expression.IfThen( - Expression.Call(_isAssignableFromMethodInfo, - Expression.Constant(navigation.DeclaringEntityType), - concreteEntityTypeVariable), - navigationExpression); + private static void SetIsLoadedNoTracking(object entity, INavigation navigation) + => ((ILazyLoader)((PropertyBase)navigation + .DeclaringEntityType + .GetServiceProperties() + .FirstOrDefault(p => p.ClrType == typeof(ILazyLoader))) + ?.Getter.GetClrValue(entity)) + ?.SetLoaded(entity, navigation.Name); + + private static LambdaExpression GenerateFixup( + Type entityType, + Type relatedEntityType, + INavigation navigation, + INavigation inverseNavigation) + { + var entityParameter = Expression.Parameter(entityType); + var relatedEntityParameter = Expression.Parameter(relatedEntityType); + var expressions = new List + { + navigation.IsCollection() + ? AddToCollectionNavigation(entityParameter, relatedEntityParameter, navigation) + : AssignReferenceNavigation(entityParameter, relatedEntityParameter, navigation) + }; + + if (inverseNavigation != null) + { + expressions.Add( + inverseNavigation.IsCollection() + ? AddToCollectionNavigation(relatedEntityParameter, entityParameter, inverseNavigation) + : AssignReferenceNavigation(relatedEntityParameter, entityParameter, inverseNavigation)); - embeddedExpressions.Add(embeddedMaterializer); } - _currentEmbeddedNavigationInfo = parentEmbeddedNavigationInfo; - expressions.Add(Expression.IfThen( - Expression.NotEqual(instanceVariable, Expression.Constant(null, instanceVariable.Type)), - Expression.Block(embeddedExpressions))); - expressions.Add(instanceVariable); - return blockExpression.Update(blockExpression.Variables, expressions); + return Expression.Lambda(Expression.Block(typeof(void), expressions), entityParameter, relatedEntityParameter); } - private object GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression) + private static Expression AssignReferenceNavigation( + ParameterExpression entity, + ParameterExpression relatedEntity, + INavigation navigation) + => entity.MakeMemberAccess(navigation.GetMemberInfo(forConstruction: false, forSet: true)) + .CreateAssignExpression(relatedEntity); + + private static Expression AddToCollectionNavigation( + ParameterExpression entity, + ParameterExpression relatedEntity, + INavigation navigation) + => Expression.Call( + Expression.Constant(navigation.GetCollectionAccessor()), + _collectionAccessorAddMethodInfo, + entity, + relatedEntity); + + private static readonly MethodInfo _collectionAccessorAddMethodInfo + = typeof(IClrCollectionAccessor).GetTypeInfo() + .GetDeclaredMethod(nameof(IClrCollectionAccessor.Add)); + + private int GetProjectionIndex(ProjectionBindingExpression projectionBindingExpression) => projectionBindingExpression.ProjectionMember != null - ? ((ConstantExpression)_selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember)).Value - : (projectionBindingExpression.Index != null - ? projectionBindingExpression.Index - : throw new InvalidOperationException()); + ? (int)((ConstantExpression)_selectExpression.GetMappedProjection(projectionBindingExpression.ProjectionMember)).Value + : projectionBindingExpression.Index ?? throw new InvalidOperationException(); + + private ProjectionExpression GetProjection(ProjectionBindingExpression projectionBindingExpression) + { + var index = GetProjectionIndex(projectionBindingExpression); + return _selectExpression.Projection[index]; + } private static Expression CreateReadJTokenExpression(Expression jObjectExpression, string propertyName) => Expression.Call(jObjectExpression, _getItemMethodInfo, Expression.Constant(propertyName)); - private static Expression CreateGetValueExpression( + private Expression CreateGetValueExpression( Expression jObjectExpression, IProperty property) { @@ -359,19 +589,37 @@ private static Expression CreateGetValueExpression( return Expression.Default(property.ClrType); } - return CreateGetStoreValueExpression(jObjectExpression, storeName, property.GetTypeMapping(), property.ClrType); + return CreateGetStoreValueExpression(jObjectExpression, storeName, property.ClrType, property.GetTypeMapping()); } - private static Expression CreateGetStoreValueExpression( + private Expression CreateGetStoreValueExpression( Expression jObjectExpression, string storeName, - CoreTypeMapping typeMapping, - Type clrType) + Type clrType, + CoreTypeMapping typeMapping = null) { - var jTokenExpression = Expression.Call(jObjectExpression, _getItemMethodInfo, Expression.Constant(storeName)); + var innerExpression = jObjectExpression; + if (_projectionBindings.TryGetValue(jObjectExpression, out var innerVariable)) + { + innerExpression = innerVariable; + } + else if (jObjectExpression is RootReferenceExpression rootReferenceExpression) + { + innerExpression = CreateGetStoreValueExpression( + _jObjectParameter, rootReferenceExpression.Alias, typeof(JObject)); + } + else if (jObjectExpression is ObjectAccessExpression objectAccessExpression) + { + var innerAccessExpression = objectAccessExpression.AccessExpression; + + innerExpression = CreateGetStoreValueExpression( + innerAccessExpression, ((IAccessExpression)innerAccessExpression).Name, typeof(JObject)); + } + + var jTokenExpression = Expression.Call(innerExpression, _getItemMethodInfo, Expression.Constant(storeName)); Expression valueExpression; - var converter = typeMapping.Converter; + var converter = typeMapping?.Converter; if (converter != null) { valueExpression = ConvertJTokenToType(jTokenExpression, converter.ProviderClrType); @@ -404,32 +652,17 @@ private static Expression CreateGetStoreValueExpression( } private static Expression ConvertJTokenToType(Expression jTokenExpression, Type type) - { - return Expression.Call( - _toObjectMethodInfo.MakeGenericMethod(type), - jTokenExpression); - } + => type == typeof(JToken) + ? jTokenExpression + : Expression.Call( + _toObjectMethodInfo.MakeGenericMethod(type), + jTokenExpression); private static T SafeToObject(JToken token) => token == null ? default : token.ToObject(); private static bool IsNull(JToken token) => token == null || token.Type == JTokenType.Null; - - private class EmbeddedNavigationInfo - { - public EmbeddedNavigationInfo() - { - } - - public EmbeddedNavigationInfo(INavigation navigation) - { - Navigation = navigation; - } - - public INavigation Navigation { get; } - public List EmbeddedNavigations { get; } = new List(); - } } private class QueryingEnumerable : IEnumerable diff --git a/src/EFCore.Cosmos/Query/Pipeline/CosmosSqlTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Pipeline/CosmosSqlTranslatingExpressionVisitor.cs index fc30cf9e539..1105ac7e141 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/CosmosSqlTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/CosmosSqlTranslatingExpressionVisitor.cs @@ -9,6 +9,7 @@ using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; +using Microsoft.EntityFrameworkCore.Query.NavigationExpansion; using Microsoft.EntityFrameworkCore.Query.Pipeline; namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Pipeline @@ -55,12 +56,9 @@ private class SqlTypeMappingVerifyingExpressionVisitor : ExpressionVisitor protected override Expression VisitExtension(Expression node) { if (node is SqlExpression sqlExpression - && !(node is ObjectAccessExpression)) + && sqlExpression.TypeMapping == null) { - if (sqlExpression.TypeMapping == null) - { - throw new InvalidOperationException("Null TypeMapping in Sql Tree"); - } + throw new InvalidOperationException("Null TypeMapping in Sql Tree"); } return base.VisitExtension(node); @@ -71,7 +69,7 @@ protected override Expression VisitMember(MemberExpression memberExpression) { var innerExpression = Visit(memberExpression.Expression); - if (TryBindProperty(innerExpression, MemberIdentity.Create(memberExpression.Member), out var result)) + if (TryBindMember(innerExpression, MemberIdentity.Create(memberExpression.Member), out var result)) { return result; } @@ -81,43 +79,20 @@ protected override Expression VisitMember(MemberExpression memberExpression) : _memberTranslatorProvider.Translate((SqlExpression)innerExpression, memberExpression.Member, memberExpression.Type); } - private bool TryBindProperty(Expression source, MemberIdentity member, out SqlExpression expression) + private bool TryBindMember(Expression source, MemberIdentity member, out Expression expression) { if (source is EntityProjectionExpression entityProjectionExpression) { - var entityType = entityProjectionExpression.EntityType; - var property = member.MemberInfo != null - ? entityType.FindProperty(member.MemberInfo) - : entityType.FindProperty(member.Name); - if (property != null) - { - expression = entityProjectionExpression.BindProperty(property); - return true; - } - - var navigation = member.MemberInfo != null - ? entityType.FindNavigation(member.MemberInfo) - : entityType.FindNavigation(member.Name); - expression = entityProjectionExpression.BindNavigation(navigation); + expression = member.MemberInfo != null + ? entityProjectionExpression.BindMember(member.MemberInfo, null, out _) + : entityProjectionExpression.BindMember(member.Name, null, out _); return true; } - else if (source is ObjectAccessExpression objectAccessExpression) - { - var entityType = objectAccessExpression.Navigation.GetTargetType(); - var property = member.MemberInfo != null - ? entityType.FindProperty(member.MemberInfo) - : entityType.FindProperty(member.Name); - if (property != null) - { - expression = new KeyAccessExpression(property, objectAccessExpression); - return true; - } - var navigation = member.MemberInfo != null - ? entityType.FindNavigation(member.MemberInfo) - : entityType.FindNavigation(member.Name); - expression = new ObjectAccessExpression(navigation, objectAccessExpression); - return true; + if (source is MemberExpression innerMemberExpression + && TryBindMember(innerMemberExpression, MemberIdentity.Create(innerMemberExpression.Member), out var innerResult)) + { + return TryBindMember(innerResult, member, out expression); } expression = null; @@ -128,10 +103,11 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp { if (methodCallExpression.TryGetEFPropertyArguments(out var source, out var propertyName)) { - if (!TryBindProperty(Visit(source), MemberIdentity.Create(propertyName), out var result)) + if (!TryBindMember(Visit(source), MemberIdentity.Create(propertyName), out var result)) { - throw new InvalidOperationException(); + throw new InvalidOperationException($"Property {propertyName} not found on {source}"); } + return result; } @@ -180,28 +156,27 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp private static Expression TryRemoveImplicitConvert(Expression expression) { - if (expression is UnaryExpression unaryExpression) + if (expression is UnaryExpression unaryExpression + && (unaryExpression.NodeType == ExpressionType.Convert + || unaryExpression.NodeType == ExpressionType.ConvertChecked)) { - if (unaryExpression.NodeType == ExpressionType.Convert - || unaryExpression.NodeType == ExpressionType.ConvertChecked) + var innerType = unaryExpression.Operand.Type.UnwrapNullableType(); + if (innerType.IsEnum) { - var innerType = unaryExpression.Operand.Type.UnwrapNullableType(); - if (innerType.IsEnum) - { - innerType = Enum.GetUnderlyingType(innerType); - } - var convertedType = unaryExpression.Type.UnwrapNullableType(); - - if (innerType == convertedType - || (convertedType == typeof(int) - && (innerType == typeof(byte) - || innerType == typeof(sbyte) - || innerType == typeof(char) - || innerType == typeof(short) - || innerType == typeof(ushort)))) - { - return TryRemoveImplicitConvert(unaryExpression.Operand); - } + innerType = Enum.GetUnderlyingType(innerType); + } + + var convertedType = unaryExpression.Type.UnwrapNullableType(); + + if (innerType == convertedType + || (convertedType == typeof(int) + && (innerType == typeof(byte) + || innerType == typeof(sbyte) + || innerType == typeof(char) + || innerType == typeof(short) + || innerType == typeof(ushort)))) + { + return TryRemoveImplicitConvert(unaryExpression.Operand); } } diff --git a/src/EFCore.Cosmos/Query/Pipeline/EntityProjectionExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/EntityProjectionExpression.cs index 8273f4635d0..3e8bd509c2f 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/EntityProjectionExpression.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/EntityProjectionExpression.cs @@ -5,46 +5,47 @@ using System.Collections.Generic; using System.Linq; using System.Linq.Expressions; +using System.Reflection; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; +using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Pipeline { - public class EntityProjectionExpression : Expression + public class EntityProjectionExpression : Expression, IPrintable, IAccessExpression { - private readonly IDictionary _propertyExpressionsCache - = new Dictionary(); - private readonly IDictionary _navigationExpressionsCache - = new Dictionary(); + private readonly IDictionary _propertyExpressionsCache + = new Dictionary(); + private readonly IDictionary _navigationExpressionsCache + = new Dictionary(); - public EntityProjectionExpression(IEntityType entityType, RootReferenceExpression accessExpression, string alias) + public EntityProjectionExpression(IEntityType entityType, Expression accessExpression) { EntityType = entityType; AccessExpression = accessExpression; - Alias = alias; + Name = (accessExpression as IAccessExpression)?.Name; } public override ExpressionType NodeType => ExpressionType.Extension; public override Type Type => EntityType.ClrType; - public string Alias { get; } - - public RootReferenceExpression AccessExpression { get; } - - public IEntityType EntityType { get; } + public virtual Expression AccessExpression { get; } + public virtual IEntityType EntityType { get; } + public virtual string Name { get; } protected override Expression VisitChildren(ExpressionVisitor visitor) { - var accessExpression = (RootReferenceExpression)visitor.Visit(AccessExpression); + var accessExpression = visitor.Visit(AccessExpression); return accessExpression != AccessExpression - ? new EntityProjectionExpression(EntityType, accessExpression, Alias) + ? new EntityProjectionExpression(EntityType, accessExpression) : this; } - public KeyAccessExpression BindProperty(IProperty property) + public SqlExpression BindProperty(IProperty property) { - if (!EntityType.GetTypesInHierarchy().Contains(property.DeclaringEntityType)) + if (!EntityType.IsAssignableFrom(property.DeclaringEntityType) + && !property.DeclaringEntityType.IsAssignableFrom(EntityType)) { throw new InvalidOperationException( $"Called EntityProjectionExpression.GetProperty() with incorrect IProperty. EntityType:{EntityType.DisplayName()}, Property:{property.Name}"); @@ -59,9 +60,10 @@ public KeyAccessExpression BindProperty(IProperty property) return expression; } - public ObjectAccessExpression BindNavigation(INavigation navigation) + public Expression BindNavigation(INavigation navigation) { - if (!EntityType.GetTypesInHierarchy().Contains(navigation.DeclaringEntityType)) + if (!EntityType.IsAssignableFrom(navigation.DeclaringEntityType) + && !navigation.DeclaringEntityType.IsAssignableFrom(EntityType)) { throw new InvalidOperationException( $"Called EntityProjectionExpression.GetNavigation() with incorrect INavigation. EntityType:{EntityType.DisplayName()}, Navigation:{navigation.Name}"); @@ -69,11 +71,78 @@ public ObjectAccessExpression BindNavigation(INavigation navigation) if (!_navigationExpressionsCache.TryGetValue(navigation, out var expression)) { - expression = new ObjectAccessExpression(navigation, AccessExpression); + if (navigation.IsCollection()) + { + expression = new ObjectArrayProjectionExpression(navigation, AccessExpression); + } + else + { + expression = new EntityProjectionExpression( + navigation.GetTargetType(), + new ObjectAccessExpression(navigation, AccessExpression)); + } + _navigationExpressionsCache[navigation] = expression; } return expression; } + + public Expression BindMember(string name, Type entityClrType, out IPropertyBase propertyBase) + { + var entityType = EntityType; + if (entityClrType != null + && !entityClrType.IsAssignableFrom(entityType.ClrType)) + { + entityType = entityType.GetDerivedTypes().First(e => entityClrType.IsAssignableFrom(e.ClrType)); + } + + var property = entityType.FindProperty(name); + if (property != null) + { + propertyBase = property; + return BindProperty(property); + } + + var navigation = entityType.FindNavigation(name); + propertyBase = navigation; + return BindNavigation(navigation); + } + + public Expression BindMember(MemberInfo memberInfo, Type entityClrType, out IPropertyBase propertyBase) + { + var entityType = EntityType; + if (entityClrType != null + && !entityClrType.IsAssignableFrom(entityType.ClrType)) + { + entityType = entityType.GetDerivedTypes().First(e => entityClrType.IsAssignableFrom(e.ClrType)); + } + + var property = entityType.FindProperty(memberInfo); + if (property != null) + { + propertyBase = property; + return BindProperty(property); + } + + var navigation = entityType.FindNavigation(memberInfo); + propertyBase = navigation; + return BindNavigation(navigation); + } + + public void Print(ExpressionPrinter expressionPrinter) + => expressionPrinter.Visit(AccessExpression); + + public override bool Equals(object obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is EntityProjectionExpression entityProjectionExpression + && Equals(entityProjectionExpression)); + + private bool Equals(EntityProjectionExpression entityProjectionExpression) + => Equals(EntityType, entityProjectionExpression.EntityType) + && AccessExpression.Equals(entityProjectionExpression.AccessExpression); + + public override int GetHashCode() => HashCode.Combine(EntityType, AccessExpression); } } diff --git a/src/EFCore.Cosmos/Query/Pipeline/IAccessExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/IAccessExpression.cs new file mode 100644 index 00000000000..2015ca0b886 --- /dev/null +++ b/src/EFCore.Cosmos/Query/Pipeline/IAccessExpression.cs @@ -0,0 +1,10 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Pipeline +{ + public interface IAccessExpression + { + string Name { get; } + } +} diff --git a/src/EFCore.Cosmos/Query/Pipeline/KeyAccessExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/KeyAccessExpression.cs index 2ed66ab3d5b..e396e666025 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/KeyAccessExpression.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/KeyAccessExpression.cs @@ -8,37 +8,36 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Pipeline { - public class KeyAccessExpression : SqlExpression + public class KeyAccessExpression : SqlExpression, IAccessExpression { - private readonly IProperty _property; - private readonly Expression _outerExpression; - - public KeyAccessExpression(IProperty property, Expression outerExpression) + public KeyAccessExpression(IProperty property, Expression accessExpression) : base(property.ClrType, property.GetTypeMapping()) { Name = property.GetCosmosPropertyName(); - _property = property; - _outerExpression = outerExpression; + Property = property; + AccessExpression = accessExpression; } - public string Name { get; } + public virtual string Name { get; } + public new virtual IProperty Property { get; } + public virtual Expression AccessExpression { get; } protected override Expression VisitChildren(ExpressionVisitor visitor) { - var outerExpression = visitor.Visit(_outerExpression); + var outerExpression = visitor.Visit(AccessExpression); return Update(outerExpression); } public KeyAccessExpression Update(Expression outerExpression) - => outerExpression != _outerExpression - ? new KeyAccessExpression(_property, outerExpression) + => outerExpression != AccessExpression + ? new KeyAccessExpression(Property, outerExpression) : this; public override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.StringBuilder.Append(ToString()); - public override string ToString() => $"{_outerExpression}[\"{Name}\"]"; + public override string ToString() => $"{AccessExpression}[\"{Name}\"]"; public override bool Equals(object obj) => obj != null @@ -49,8 +48,8 @@ public override bool Equals(object obj) private bool Equals(KeyAccessExpression keyAccessExpression) => base.Equals(keyAccessExpression) && string.Equals(Name, keyAccessExpression.Name) - && _outerExpression.Equals(keyAccessExpression._outerExpression); + && AccessExpression.Equals(keyAccessExpression.AccessExpression); - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Name, _outerExpression); + public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Name, AccessExpression); } } diff --git a/src/EFCore.Cosmos/Query/Pipeline/ObjectAccessExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/ObjectAccessExpression.cs index 3169ba6e32a..9493a722979 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/ObjectAccessExpression.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/ObjectAccessExpression.cs @@ -4,16 +4,14 @@ using System; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Pipeline { - public class ObjectAccessExpression : SqlExpression + public class ObjectAccessExpression : Expression, IPrintable, IAccessExpression { - private readonly Expression _outerExpression; - - public ObjectAccessExpression(INavigation navigation, Expression outerExpression) - : base(navigation.ClrType, null) + public ObjectAccessExpression(INavigation navigation, Expression accessExpression) { Name = navigation.GetTargetType().GetCosmosContainingPropertyName(); if (Name == null) @@ -23,28 +21,29 @@ public ObjectAccessExpression(INavigation navigation, Expression outerExpression } Navigation = navigation; - _outerExpression = outerExpression; + AccessExpression = accessExpression; } - public string Name { get; } - - public INavigation Navigation { get; } + public override Type Type => Navigation.ClrType; + public virtual string Name { get; } + public virtual INavigation Navigation { get; } + public virtual Expression AccessExpression { get; } protected override Expression VisitChildren(ExpressionVisitor visitor) { - var outerExpression = visitor.Visit(_outerExpression); + var outerExpression = visitor.Visit(AccessExpression); return Update(outerExpression); } public ObjectAccessExpression Update(Expression outerExpression) - => outerExpression != _outerExpression + => outerExpression != AccessExpression ? new ObjectAccessExpression(Navigation, outerExpression) : this; - public override void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.StringBuilder.Append(ToString()); + public virtual void Print(ExpressionPrinter expressionPrinter) => expressionPrinter.StringBuilder.Append(ToString()); - public override string ToString() => $"{_outerExpression}[\"{Name}\"]"; + public override string ToString() => $"{AccessExpression}[\"{Name}\"]"; public override bool Equals(object obj) => obj != null @@ -53,10 +52,9 @@ public override bool Equals(object obj) && Equals(objectAccessExpression)); private bool Equals(ObjectAccessExpression objectAccessExpression) - => base.Equals(objectAccessExpression) - && string.Equals(Name, objectAccessExpression.Name) - && _outerExpression.Equals(objectAccessExpression._outerExpression); + => Navigation == objectAccessExpression.Navigation + && AccessExpression.Equals(objectAccessExpression.AccessExpression); - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Name, _outerExpression); + public override int GetHashCode() => HashCode.Combine(Navigation, AccessExpression); } } diff --git a/src/EFCore.Cosmos/Query/Pipeline/ObjectArrayProjectionExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/ObjectArrayProjectionExpression.cs new file mode 100644 index 00000000000..d579b00c595 --- /dev/null +++ b/src/EFCore.Cosmos/Query/Pipeline/ObjectArrayProjectionExpression.cs @@ -0,0 +1,79 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; +using Microsoft.EntityFrameworkCore.Query.Internal; + +namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Pipeline +{ + public class ObjectArrayProjectionExpression : Expression, IPrintable, IAccessExpression + { + public ObjectArrayProjectionExpression( + INavigation navigation, Expression accessExpression, EntityProjectionExpression innerProjection = null) + { + var targetType = navigation.GetTargetType(); + Type = typeof(IEnumerable<>).MakeGenericType(targetType.ClrType); + + Name = targetType.GetCosmosContainingPropertyName(); + if (Name == null) + { + throw new InvalidOperationException( + $"Navigation '{navigation.DeclaringEntityType.DisplayName()}.{navigation.Name}' doesn't point to an embedded entity."); + } + + Navigation = navigation; + AccessExpression = accessExpression; + InnerProjection = innerProjection ?? new EntityProjectionExpression( + targetType, + new RootReferenceExpression(targetType, "")); + } + + public override ExpressionType NodeType => ExpressionType.Extension; + public override Type Type { get; } + + public virtual string Name { get; } + public virtual INavigation Navigation { get; } + public virtual Expression AccessExpression { get; } + public virtual EntityProjectionExpression InnerProjection { get; } + + protected override Expression VisitChildren(ExpressionVisitor visitor) + { + var accessExpression = visitor.Visit(AccessExpression); + var innerProjection = visitor.Visit(InnerProjection); + + return Update(accessExpression, (EntityProjectionExpression)innerProjection); + } + + public ObjectArrayProjectionExpression Update(Expression accessExpression, EntityProjectionExpression innerProjection) + => accessExpression != AccessExpression || innerProjection != InnerProjection + ? new ObjectArrayProjectionExpression(Navigation, accessExpression, innerProjection) + : this; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual void Print(ExpressionPrinter expressionPrinter) + => expressionPrinter.StringBuilder.Append(ToString()); + + public override string ToString() => $"{AccessExpression}[\"{Name}\"]"; + + public override bool Equals(object obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is ObjectArrayProjectionExpression arrayProjectionExpression + && Equals(arrayProjectionExpression)); + + private bool Equals(ObjectArrayProjectionExpression objectArrayProjectionExpression) + => AccessExpression.Equals(objectArrayProjectionExpression.AccessExpression) + && InnerProjection.Equals(objectArrayProjectionExpression.InnerProjection); + + public override int GetHashCode() => HashCode.Combine(AccessExpression, InnerProjection); + } +} diff --git a/src/EFCore.Cosmos/Query/Pipeline/ProjectionExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/ProjectionExpression.cs index 096b3d4ba58..65bae9cea66 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/ProjectionExpression.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/ProjectionExpression.cs @@ -2,7 +2,6 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; -using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -20,6 +19,9 @@ public ProjectionExpression(Expression expression, string alias) public string Alias { get; } public Expression Expression { get; } + public virtual string Name + => (Expression as IAccessExpression)?.Name; + public override Type Type => Expression.Type; public override ExpressionType NodeType => ExpressionType.Extension; @@ -35,17 +37,12 @@ public void Print(ExpressionPrinter expressionPrinter) { expressionPrinter.Visit(Expression); if (!string.Equals(string.Empty, Alias) - && !string.Equals(Alias, GetName())) + && !string.Equals(Alias, Name)) { expressionPrinter.StringBuilder.Append(" AS " + Alias); } } - private string GetName() - => (Expression as KeyAccessExpression)?.Name - ?? (Expression as ObjectAccessExpression)?.Name - ?? (Expression as EntityProjectionExpression)?.Alias; - public override bool Equals(object obj) => obj != null && (ReferenceEquals(this, obj) @@ -56,6 +53,6 @@ private bool Equals(ProjectionExpression projectionExpression) => string.Equals(Alias, projectionExpression.Alias) && Expression.Equals(projectionExpression.Expression); - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), Alias, Expression); + public override int GetHashCode() => HashCode.Combine(Alias, Expression); } } diff --git a/src/EFCore.Cosmos/Query/Pipeline/QuerySqlGenerator.cs b/src/EFCore.Cosmos/Query/Pipeline/QuerySqlGenerator.cs index 8f6039ec952..e12fb459c20 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/QuerySqlGenerator.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/QuerySqlGenerator.cs @@ -70,11 +70,18 @@ public CosmosSqlQuery GetSqlQuery(SelectExpression selectExpression, IReadOnlyDi protected override Expression VisitEntityProjection(EntityProjectionExpression entityProjectionExpression) { - _sqlBuilder.Append(entityProjectionExpression.Alias); + Visit(entityProjectionExpression.AccessExpression); return entityProjectionExpression; } + protected override Expression VisitObjectArrayProjection(ObjectArrayProjectionExpression objectArrayProjectionExpression) + { + _sqlBuilder.Append(objectArrayProjectionExpression); + + return objectArrayProjectionExpression; + } + protected override Expression VisitKeyAccess(KeyAccessExpression keyAccessExpression) { _sqlBuilder.Append(keyAccessExpression); @@ -94,7 +101,7 @@ protected override Expression VisitProjection(ProjectionExpression projectionExp Visit(projectionExpression.Expression); if (!string.Equals(string.Empty, projectionExpression.Alias) - && !string.Equals(projectionExpression.Alias, GetName(projectionExpression))) + && !string.Equals(projectionExpression.Alias, projectionExpression.Name)) { _sqlBuilder.Append(" AS " + projectionExpression.Alias); } @@ -102,12 +109,6 @@ protected override Expression VisitProjection(ProjectionExpression projectionExp return projectionExpression; } - private string GetName(ProjectionExpression projectionExpression) - { - return (projectionExpression.Expression as KeyAccessExpression)?.Name - ?? (projectionExpression.Expression as EntityProjectionExpression)?.Alias; - } - protected override Expression VisitRootReference(RootReferenceExpression rootReferenceExpression) { _sqlBuilder.Append(rootReferenceExpression); @@ -147,7 +148,7 @@ protected override Expression VisitSelect(SelectExpression selectExpression) if (selectExpression.Offset != null || selectExpression.Limit != null) { - _sqlBuilder.AppendLine().Append($"OFFSET "); + _sqlBuilder.AppendLine().Append("OFFSET "); if (selectExpression.Offset != null) { diff --git a/src/EFCore.Cosmos/Query/Pipeline/RootReferenceExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/RootReferenceExpression.cs index 8d38c1b3952..3cd27be08fd 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/RootReferenceExpression.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/RootReferenceExpression.cs @@ -7,22 +7,34 @@ namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Pipeline { - public class RootReferenceExpression : Expression + public class RootReferenceExpression : Expression, IAccessExpression { - private readonly IEntityType _entityType; - private readonly string _alias; - - public override ExpressionType NodeType => ExpressionType.Extension; - public override Type Type => _entityType.ClrType; - public RootReferenceExpression(IEntityType entityType, string alias) { - _entityType = entityType; - _alias = alias; + EntityType = entityType; + Alias = alias; } + public override ExpressionType NodeType => ExpressionType.Extension; + public override Type Type => EntityType.ClrType; + public IEntityType EntityType { get; } + public string Alias { get; } + string IAccessExpression.Name => Alias; + protected override Expression VisitChildren(ExpressionVisitor visitor) => this; - public override string ToString() => _alias; + public override string ToString() => Alias; + + public override bool Equals(object obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is RootReferenceExpression rootReferenceExpression + && Equals(rootReferenceExpression)); + + private bool Equals(RootReferenceExpression rootReferenceExpression) + => string.Equals(Alias, rootReferenceExpression.Alias) + && EntityType.Equals(rootReferenceExpression.EntityType); + + public override int GetHashCode() => HashCode.Combine(Alias, EntityType); } } diff --git a/src/EFCore.Cosmos/Query/Pipeline/SelectExpression.cs b/src/EFCore.Cosmos/Query/Pipeline/SelectExpression.cs index bf6c1b3d5df..02ac7d09aa8 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/SelectExpression.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/SelectExpression.cs @@ -23,7 +23,7 @@ public SelectExpression(IEntityType entityType) { ContainerName = entityType.GetCosmosContainerName(); FromExpression = new RootReferenceExpression(entityType, RootAlias); - _projectionMapping[new ProjectionMember()] = new EntityProjectionExpression(entityType, FromExpression, RootAlias); + _projectionMapping[new ProjectionMember()] = new EntityProjectionExpression(entityType, FromExpression); } public SelectExpression( @@ -44,9 +44,7 @@ public SelectExpression( public bool IsDistinct { get; private set; } public Expression GetMappedProjection(ProjectionMember projectionMember) - { - return _projectionMapping[projectionMember]; - } + => _projectionMapping[projectionMember]; public void ApplyProjection() { @@ -75,15 +73,11 @@ public void ReplaceProjectionMapping(IDictionary p } } - public int AddToProjection(SqlExpression sqlExpression) - { - return AddToProjection(sqlExpression, null); - } + public int AddToProjection(SqlExpression sqlExpression) => AddToProjection(sqlExpression, null); - public int AddToProjection(EntityProjectionExpression entityProjection) - { - return AddToProjection(entityProjection, null); - } + public int AddToProjection(EntityProjectionExpression entityProjection) => AddToProjection(entityProjection, null); + + public int AddToProjection(ObjectArrayProjectionExpression objectArrayProjection) => AddToProjection(objectArrayProjection, null); private int AddToProjection(Expression expression, string alias) { @@ -94,17 +88,14 @@ private int AddToProjection(Expression expression, string alias) } var baseAlias = alias - ?? (expression as KeyAccessExpression)?.Name - ?? (expression as EntityProjectionExpression)?.Alias + ?? (expression as IAccessExpression)?.Name ?? "c"; + var currentAlias = baseAlias; - if (baseAlias != null) + var counter = 0; + while (_projection.Any(pe => string.Equals(pe.Alias, currentAlias, StringComparison.OrdinalIgnoreCase))) { - var counter = 0; - while (_projection.Any(pe => string.Equals(pe.Alias, currentAlias, StringComparison.OrdinalIgnoreCase))) - { - currentAlias = $"{baseAlias}{counter++}"; - } + currentAlias = $"{baseAlias}{counter++}"; } _projection.Add(new ProjectionExpression(expression, currentAlias)); @@ -190,16 +181,16 @@ public void ReverseOrderings() throw new InvalidOperationException(); } - var existingOrdering = _orderings.ToArray(); + var existingOrderings = _orderings.ToArray(); _orderings.Clear(); - for (var i = 0; i < existingOrdering.Length; i++) + foreach (var existingOrdering in existingOrderings) { _orderings.Add( new OrderingExpression( - existingOrdering[i].Expression, - !existingOrdering[i].Ascending)); + existingOrdering.Expression, + !existingOrdering.Ascending)); } } diff --git a/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionFactory.cs b/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionFactory.cs index b2a4346a8c2..fb86ba233ba 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionFactory.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionFactory.cs @@ -368,7 +368,7 @@ private void AddDiscriminator(SelectExpression selectExpression, IEntityType ent if (concreteEntityType.GetDiscriminatorProperty() != null) { var discriminatorColumn = ((EntityProjectionExpression)selectExpression.GetMappedProjection(new ProjectionMember())) - .BindProperty(concreteEntityType.GetDiscriminatorProperty()); + .BindProperty(concreteEntityType.GetDiscriminatorProperty()); selectExpression.ApplyPredicate( Equal(discriminatorColumn, Constant(concreteEntityType.GetDiscriminatorValue()))); diff --git a/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionVisitor.cs index 2fe0a3d77fb..f0d3f1c7f2f 100644 --- a/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Pipeline/SqlExpressionVisitor.cs @@ -20,12 +20,18 @@ protected override Expression VisitExtension(Expression extensionExpression) case EntityProjectionExpression entityProjectionExpression: return VisitEntityProjection(entityProjectionExpression); + case ObjectArrayProjectionExpression arrayProjectionExpression: + return VisitObjectArrayProjection(arrayProjectionExpression); + case RootReferenceExpression rootReferenceExpression: return VisitRootReference(rootReferenceExpression); case KeyAccessExpression keyAccessExpression: return VisitKeyAccess(keyAccessExpression); + case ObjectAccessExpression objectAccessExpression: + return VisitObjectAccess(objectAccessExpression); + case SqlBinaryExpression sqlBinaryExpression: return VisitSqlBinary(sqlBinaryExpression); @@ -66,6 +72,7 @@ protected override Expression VisitExtension(Expression extensionExpression) protected abstract Expression VisitObjectAccess(ObjectAccessExpression objectAccessExpression); protected abstract Expression VisitRootReference(RootReferenceExpression rootReferenceExpression); protected abstract Expression VisitEntityProjection(EntityProjectionExpression entityProjectionExpression); + protected abstract Expression VisitObjectArrayProjection(ObjectArrayProjectionExpression objectArrayProjectionExpression); protected abstract Expression VisitProjection(ProjectionExpression projectionExpression); protected abstract Expression VisitSelect(SelectExpression selectExpression); } diff --git a/src/EFCore.InMemory/Query/Pipeline/EntityValuesExpression.cs b/src/EFCore.InMemory/Query/Pipeline/EntityValuesExpression.cs index 3d1e634b0f2..e3b9ef3551c 100644 --- a/src/EFCore.InMemory/Query/Pipeline/EntityValuesExpression.cs +++ b/src/EFCore.InMemory/Query/Pipeline/EntityValuesExpression.cs @@ -3,10 +3,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.InMemory.Query.Pipeline { @@ -27,7 +25,8 @@ public EntityProjectionExpression( public Expression BindProperty(IProperty property) { - if (!EntityType.GetTypesInHierarchy().Contains(property.DeclaringEntityType)) + if (!EntityType.IsAssignableFrom(property.DeclaringEntityType) + && !property.DeclaringEntityType.IsAssignableFrom(EntityType)) { throw new InvalidOperationException( $"Called EntityProjectionExpression.BindProperty() with incorrect IProperty. EntityType:{EntityType.DisplayName()}, Property:{property.Name}"); diff --git a/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs b/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs index 9decc62f5fc..aa6500c1c5e 100644 --- a/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs +++ b/src/EFCore.InMemory/Query/Pipeline/InMemoryQueryExpression.cs @@ -170,14 +170,10 @@ public int AddToProjection(Expression expression) } private IEnumerable GetAllPropertiesInHierarchy(IEntityType entityType) - { - return entityType.GetTypesInHierarchy().SelectMany(e => e.GetDeclaredProperties()); - } + => entityType.GetTypesInHierarchy().SelectMany(EntityTypeExtensions.GetDeclaredProperties); public Expression GetMappedProjection(ProjectionMember member) - { - return _projectionMapping[member]; - } + => _projectionMapping[member]; public void ApplyPendingSelector() { @@ -323,14 +319,14 @@ public void AddInnerJoin( { if (projection.Value is EntityProjectionExpression entityProjection) { - var readExperssionMap = new Dictionary(); + var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExperssionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); } projectionMapping[projection.Key.ShiftMember(outerMemberInfo)] - = new EntityProjectionExpression(entityProjection.EntityType, readExperssionMap); + = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); } else { @@ -345,14 +341,14 @@ public void AddInnerJoin( { if (projection.Value is EntityProjectionExpression entityProjection) { - var readExperssionMap = new Dictionary(); + var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExperssionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); } projectionMapping[projection.Key.ShiftMember(innerMemberInfo)] - = new EntityProjectionExpression(entityProjection.EntityType, readExperssionMap); + = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); } else { @@ -454,15 +450,15 @@ public void AddLeftJoin( { if (projection.Value is EntityProjectionExpression entityProjection) { - var readExperssionMap = new Dictionary(); + var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property)); resultValueBufferExpressions.Add(replacedExpression); - readExperssionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); + readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); } projectionMapping[projection.Key.ShiftMember(outerMemberInfo)] - = new EntityProjectionExpression(entityProjection.EntityType, readExperssionMap); + = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); } else { @@ -480,16 +476,16 @@ public void AddLeftJoin( { if (projection.Value is EntityProjectionExpression entityProjection) { - var readExperssionMap = new Dictionary(); + var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { var replacedExpression = replacingVisitor.Visit(entityProjection.BindProperty(property)); replacedExpression = nullableReadValueExpressionVisitor.Visit(replacedExpression); resultValueBufferExpressions.Add(replacedExpression); - readExperssionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); + readExpressionMap[property] = CreateReadValueExpression(replacedExpression.Type, index++, property); } projectionMapping[projection.Key.ShiftMember(innerMemberInfo)] - = new EntityProjectionExpression(entityProjection.EntityType, readExperssionMap); + = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); } else { @@ -552,14 +548,14 @@ public void AddCrossJoin(InMemoryQueryExpression innerQueryExpression, Type tran { if (projection.Value is EntityProjectionExpression entityProjection) { - var readExperssionMap = new Dictionary(); + var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExperssionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); } projectionMapping[projection.Key.ShiftMember(outerMemberInfo)] - = new EntityProjectionExpression(entityProjection.EntityType, readExperssionMap); + = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); } else { @@ -574,14 +570,14 @@ public void AddCrossJoin(InMemoryQueryExpression innerQueryExpression, Type tran { if (projection.Value is EntityProjectionExpression entityProjection) { - var readExperssionMap = new Dictionary(); + var readExpressionMap = new Dictionary(); foreach (var property in GetAllPropertiesInHierarchy(entityProjection.EntityType)) { resultValueBufferExpressions.Add(replacingVisitor.Visit(entityProjection.BindProperty(property))); - readExperssionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); + readExpressionMap[property] = CreateReadValueExpression(property.ClrType, index++, property); } projectionMapping[projection.Key.ShiftMember(innerMemberInfo)] - = new EntityProjectionExpression(entityProjection.EntityType, readExperssionMap); + = new EntityProjectionExpression(entityProjection.EntityType, readExpressionMap); } else { diff --git a/src/EFCore.InMemory/Query/Pipeline/InMemoryShapedQueryExpressionVisitor.cs b/src/EFCore.InMemory/Query/Pipeline/InMemoryShapedQueryExpressionVisitor.cs index 2192521f9da..ffade8e486b 100644 --- a/src/EFCore.InMemory/Query/Pipeline/InMemoryShapedQueryExpressionVisitor.cs +++ b/src/EFCore.InMemory/Query/Pipeline/InMemoryShapedQueryExpressionVisitor.cs @@ -56,7 +56,7 @@ protected override Expression VisitExtension(Expression extensionExpression) protected override Expression VisitShapedQueryExpression(ShapedQueryExpression shapedQueryExpression) { - var shaperBody = InjectEntityMaterializer(shapedQueryExpression.ShaperExpression); + var shaperBody = InjectEntityMaterializers(shapedQueryExpression.ShaperExpression); var innerEnumerable = Visit(shapedQueryExpression.QueryExpression); diff --git a/src/EFCore.Relational/Query/Pipeline/CollectionInitializingExperssion.cs b/src/EFCore.Relational/Query/Pipeline/CollectionInitializingExpression.cs similarity index 100% rename from src/EFCore.Relational/Query/Pipeline/CollectionInitializingExperssion.cs rename to src/EFCore.Relational/Query/Pipeline/CollectionInitializingExpression.cs diff --git a/src/EFCore.Relational/Query/Pipeline/EntityProjectionExpression.cs b/src/EFCore.Relational/Query/Pipeline/EntityProjectionExpression.cs index f3b4d4debfa..a61337da021 100644 --- a/src/EFCore.Relational/Query/Pipeline/EntityProjectionExpression.cs +++ b/src/EFCore.Relational/Query/Pipeline/EntityProjectionExpression.cs @@ -3,10 +3,8 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Pipeline; using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; @@ -45,22 +43,20 @@ protected override Expression VisitChildren(ExpressionVisitor visitor) ? new EntityProjectionExpression(EntityType, table, _nullable) : this; } - else + + var changed = false; + var newCache = new Dictionary(); + foreach (var expression in _propertyExpressionsCache) { - var changed = false; - var newCache = new Dictionary(); - foreach (var expression in _propertyExpressionsCache) - { - var newExpression = (ColumnExpression)visitor.Visit(expression.Value); - changed |= newExpression != expression.Value; - - newCache[expression.Key] = newExpression; - } - - return changed - ? new EntityProjectionExpression(EntityType, newCache) - : this; + var newExpression = (ColumnExpression)visitor.Visit(expression.Value); + changed |= newExpression != expression.Value; + + newCache[expression.Key] = newExpression; } + + return changed + ? new EntityProjectionExpression(EntityType, newCache) + : this; } public EntityProjectionExpression MakeNullable() @@ -69,16 +65,14 @@ public EntityProjectionExpression MakeNullable() { return new EntityProjectionExpression(EntityType, _innerTable, true); } - else - { - var newCache = new Dictionary(); - foreach (var expression in _propertyExpressionsCache) - { - newCache[expression.Key] = expression.Value.MakeNullable(); - } - return new EntityProjectionExpression(EntityType, newCache); + var newCache = new Dictionary(); + foreach (var expression in _propertyExpressionsCache) + { + newCache[expression.Key] = expression.Value.MakeNullable(); } + + return new EntityProjectionExpression(EntityType, newCache); } public EntityProjectionExpression UpdateEntityType(IEntityType derivedType) diff --git a/src/EFCore.Relational/Query/Pipeline/IQuerySqlGeneratorFactory2.cs b/src/EFCore.Relational/Query/Pipeline/IQuerySqlGeneratorFactory.cs similarity index 100% rename from src/EFCore.Relational/Query/Pipeline/IQuerySqlGeneratorFactory2.cs rename to src/EFCore.Relational/Query/Pipeline/IQuerySqlGeneratorFactory.cs diff --git a/src/EFCore.Relational/Query/Pipeline/IncludeCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/IncludeCompilingExpressionVisitor.cs index 4a0d14a767f..99b11737820 100644 --- a/src/EFCore.Relational/Query/Pipeline/IncludeCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Pipeline/IncludeCompilingExpressionVisitor.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Data.Common; using System.Diagnostics; using System.Linq; @@ -278,12 +277,12 @@ private static TCollection InitializeCollection( } private static void SetIsLoadedNoTracking(object entity, INavigation navigation) - => ((ILazyLoader)((PropertyBase)navigation - .DeclaringEntityType - .GetServiceProperties() - .FirstOrDefault(p => p.ClrType == typeof(ILazyLoader))) - ?.Getter.GetClrValue(entity)) - ?.SetLoaded(entity, navigation.Name); + => ((ILazyLoader)((PropertyBase)navigation + .DeclaringEntityType + .GetServiceProperties() + .FirstOrDefault(p => p.ClrType == typeof(ILazyLoader))) + ?.Getter.GetClrValue(entity)) + ?.SetLoaded(entity, navigation.Name); protected override Expression VisitExtension(Expression extensionExpression) { diff --git a/src/EFCore.Relational/Query/Pipeline/QuerySqlGeneratorFactory2.cs b/src/EFCore.Relational/Query/Pipeline/QuerySqlGeneratorFactory.cs similarity index 100% rename from src/EFCore.Relational/Query/Pipeline/QuerySqlGeneratorFactory2.cs rename to src/EFCore.Relational/Query/Pipeline/QuerySqlGeneratorFactory.cs diff --git a/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs index b075347dd58..7e34afeaf05 100644 --- a/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Pipeline/RelationalProjectionBindingRemovingExpressionVisitor.cs @@ -9,7 +9,6 @@ using System.Reflection; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; -using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Query.Pipeline; using Microsoft.EntityFrameworkCore.Relational.Query.Pipeline.SqlExpressions; @@ -119,9 +118,7 @@ private object GetProjectionIndex(ProjectionBindingExpression projectionBindingE } private static bool IsNullableProjection(ProjectionExpression projection) - { - return projection.Expression is ColumnExpression column ? column.Nullable : true; - } + => !(projection.Expression is ColumnExpression column) || column.Nullable; private static Expression CreateGetValueExpression( Expression dbDataReader, diff --git a/src/EFCore.Relational/Query/Pipeline/RelationalQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/RelationalQueryableMethodTranslatingExpressionVisitor.cs index 5c1ac5491bf..8f8adb6fd25 100644 --- a/src/EFCore.Relational/Query/Pipeline/RelationalQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Pipeline/RelationalQueryableMethodTranslatingExpressionVisitor.cs @@ -68,15 +68,12 @@ protected override Expression VisitMethodCall(MethodCallExpression methodCallExp } public override ShapedQueryExpression TranslateSubquery(Expression expression) - { - return (ShapedQueryExpression)new RelationalQueryableMethodTranslatingExpressionVisitor( + => (ShapedQueryExpression)new RelationalQueryableMethodTranslatingExpressionVisitor( _model, _sqlTranslator, _weakEntityExpandingExpressionVisitor, _sqlExpressionFactory).Visit(expression); - } - protected override ShapedQueryExpression CreateShapedQueryExpression(Type elementType) { var entityType = _model.FindEntityType(elementType); diff --git a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs index b228b0d9f3c..6a22c06f45d 100644 --- a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs +++ b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryCompilingExpressionVisitor.cs @@ -56,7 +56,7 @@ protected override Expression VisitShapedQueryExpression(ShapedQueryExpression s indexMapParameter) .Inject(shapedQueryExpression.ShaperExpression); - shaper = InjectEntityMaterializer(shaper); + shaper = InjectEntityMaterializers(shaper); shaper = new RelationalProjectionBindingRemovingExpressionVisitor(selectExpression, dataReaderParameter) .Visit(shaper); diff --git a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryOptimizingExpressionVisitors.cs b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryOptimizingExpressionVisitors.cs index 17c398dc8a5..d1c6547448a 100644 --- a/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryOptimizingExpressionVisitors.cs +++ b/src/EFCore.Relational/Query/Pipeline/RelationalShapedQueryOptimizingExpressionVisitors.cs @@ -49,7 +49,8 @@ protected override Expression VisitExtension(Expression extensionExpression) if (extensionExpression is CollectionShaperExpression collectionShaperExpression) { var collectionId = _collectionId++; - var selectExpression = (SelectExpression)collectionShaperExpression.Projection.QueryExpression; + var projectionBindingExpression = (ProjectionBindingExpression)collectionShaperExpression.Projection; + var selectExpression = (SelectExpression)projectionBindingExpression.QueryExpression; // Do pushdown beforehand so it updates all pending collections first if (selectExpression.IsDistinct || selectExpression.Limit != null @@ -63,7 +64,7 @@ protected override Expression VisitExtension(Expression extensionExpression) var innerShaper = Visit(collectionShaperExpression.InnerShaper); return selectExpression.ApplyCollectionJoin( - collectionShaperExpression.Projection.Index.Value, + projectionBindingExpression.Index.Value, collectionId, innerShaper, collectionShaperExpression.Navigation, diff --git a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs index c85322b5f58..438ad939795 100644 --- a/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs +++ b/src/EFCore.Relational/Query/Pipeline/SqlExpressions/SelectExpression.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Metadata.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -159,9 +158,7 @@ public void ApplyProjection() } private IEnumerable GetAllPropertiesInHierarchy(IEntityType entityType) - { - return entityType.GetTypesInHierarchy().SelectMany(e => e.GetDeclaredProperties()); - } + => entityType.GetTypesInHierarchy().SelectMany(EntityTypeExtensions.GetDeclaredProperties); public void ReplaceProjectionMapping(IDictionary projectionMapping) { @@ -173,9 +170,7 @@ public void ReplaceProjectionMapping(IDictionary p } public Expression GetMappedProjection(ProjectionMember projectionMember) - { - return _projectionMapping[projectionMember]; - } + => _projectionMapping[projectionMember]; public int AddToProjection(SqlExpression sqlExpression) { diff --git a/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs b/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs index 6df69b431ad..72fa5094a2d 100644 --- a/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs +++ b/src/EFCore/Extensions/EntityFrameworkQueryableExtensions.cs @@ -12,7 +12,6 @@ using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.ChangeTracking; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Query; using Microsoft.EntityFrameworkCore.Query.Internal; using Microsoft.EntityFrameworkCore.Utilities; diff --git a/src/EFCore/Metadata/Internal/EntityType.cs b/src/EFCore/Metadata/Internal/EntityType.cs index f7c2b1dea36..869aeb4acec 100644 --- a/src/EFCore/Metadata/Internal/EntityType.cs +++ b/src/EFCore/Metadata/Internal/EntityType.cs @@ -1886,15 +1886,6 @@ public virtual Property AddProperty( return (Property)Model.ConventionDispatcher.OnPropertyAdded(property.Builder)?.Metadata; } - /// - /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to - /// the same compatibility standards as public APIs. It may be changed or removed without notice in - /// any release. You should only use it directly in your code with extreme caution and knowing that - /// doing so can result in application failures when updating to a new Entity Framework Core release. - /// - public virtual Property FindProperty([NotNull] PropertyInfo propertyInfo) - => FindProperty(propertyInfo.GetSimpleMemberName()); - /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore/Metadata/IEntityMaterializerSource.cs b/src/EFCore/Query/IEntityMaterializerSource.cs similarity index 100% rename from src/EFCore/Metadata/IEntityMaterializerSource.cs rename to src/EFCore/Query/IEntityMaterializerSource.cs diff --git a/src/EFCore/Metadata/Internal/EntityMaterializerSource.cs b/src/EFCore/Query/Internal/EntityMaterializerSource.cs similarity index 100% rename from src/EFCore/Metadata/Internal/EntityMaterializerSource.cs rename to src/EFCore/Query/Internal/EntityMaterializerSource.cs diff --git a/src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs b/src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs index ccb21d94086..b0b652980da 100644 --- a/src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs +++ b/src/EFCore/Query/NavigationExpansion/MaterializeCollectionNavigationExpression.cs @@ -3,7 +3,6 @@ using System; using System.Linq.Expressions; -using JetBrains.Annotations; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.Expressions.Internal; using Microsoft.EntityFrameworkCore.Query.Internal; @@ -24,20 +23,19 @@ public MaterializeCollectionNavigationExpression(Expression subquery, INavigatio public override ExpressionType NodeType => ExpressionType.Extension; public override Type Type => Navigation.ClrType; + protected override Expression VisitChildren(ExpressionVisitor visitor) + => Update(visitor.Visit(Subquery)); + + public virtual MaterializeCollectionNavigationExpression Update(Expression subquery) + => subquery != Subquery + ? new MaterializeCollectionNavigationExpression(subquery, Navigation) + : this; + public virtual void Print(ExpressionPrinter expressionPrinter) { expressionPrinter.StringBuilder.Append($"MaterializeCollectionNavigation({Navigation}, "); expressionPrinter.Visit(Subquery); expressionPrinter.StringBuilder.Append(")"); } - - protected override Expression VisitChildren(ExpressionVisitor visitor) - { - var subquery = visitor.Visit(Subquery); - - return subquery != Subquery - ? new MaterializeCollectionNavigationExpression(subquery, Navigation) - : this; - } } } diff --git a/src/EFCore/Query/NavigationExpansion/NavigationState.cs b/src/EFCore/Query/NavigationExpansion/NavigationState.cs index 66cdc2ff798..5f0a5ab361a 100644 --- a/src/EFCore/Query/NavigationExpansion/NavigationState.cs +++ b/src/EFCore/Query/NavigationExpansion/NavigationState.cs @@ -1,8 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System; - namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion { public enum NavigationState diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs index cb2e61bedc2..a11df117f9b 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/CollectionNavigationRewritingVisitor.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore.Internal; -using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query.Internal; namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors @@ -144,39 +143,56 @@ public static Expression CreateCollectionNavigationExpression( NavigationTreeNode navigationTreeNode, ParameterExpression rootParameter, SourceMapping sourceMapping) { var collectionEntityType = navigationTreeNode.Navigation.ForeignKey.DeclaringEntityType; - var entityQueryable = (Expression)NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(collectionEntityType.ClrType); - - var outerBinding = new NavigationBindingExpression( - rootParameter, - navigationTreeNode.Parent, - navigationTreeNode.Navigation.DeclaringEntityType, - sourceMapping, - navigationTreeNode.Navigation.DeclaringEntityType.ClrType); - - var outerKeyAccess = NavigationExpansionHelpers.CreateKeyAccessExpression( - outerBinding, - navigationTreeNode.Navigation.ForeignKey.PrincipalKey.Properties, - addNullCheck: outerBinding.NavigationTreeNode.Optional); - - var collectionCurrentParameter = Expression.Parameter(collectionEntityType.ClrType, collectionEntityType.ClrType.GenerateParameterName()); - - var innerKeyAccess = NavigationExpansionHelpers.CreateKeyAccessExpression( - collectionCurrentParameter, - navigationTreeNode.Navigation.ForeignKey.Properties); - - var predicate = Expression.Lambda( - CreateKeyComparisonExpressionForCollectionNavigationSubquery( - outerKeyAccess, - innerKeyAccess, - outerBinding), - collectionCurrentParameter); - - var operand = Expression.Call( - LinqMethodHelpers.QueryableWhereMethodInfo.MakeGenericMethod(collectionEntityType.ClrType), - entityQueryable, - predicate); - - var result = NavigationExpansionHelpers.CreateNavigationExpansionRoot(operand, collectionEntityType, navigationTreeNode.Navigation); + + Expression operand; + if (navigationTreeNode.IncludeState == NavigationState.Pending + || navigationTreeNode.ExpansionState == NavigationState.Pending) + { + var entityQueryable = (Expression)NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(collectionEntityType.ClrType); + + var outerBinding = new NavigationBindingExpression( + rootParameter, + navigationTreeNode.Parent, + navigationTreeNode.Navigation.DeclaringEntityType, + sourceMapping, + navigationTreeNode.Navigation.DeclaringEntityType.ClrType); + + var outerKeyAccess = NavigationExpansionHelpers.CreateKeyAccessExpression( + outerBinding, + navigationTreeNode.Navigation.ForeignKey.PrincipalKey.Properties, + addNullCheck: outerBinding.NavigationTreeNode.Optional); + + var collectionCurrentParameter = Expression.Parameter( + collectionEntityType.ClrType, collectionEntityType.ClrType.GenerateParameterName()); + + var innerKeyAccess = NavigationExpansionHelpers.CreateKeyAccessExpression( + collectionCurrentParameter, + navigationTreeNode.Navigation.ForeignKey.Properties); + + var predicate = Expression.Lambda( + CreateKeyComparisonExpressionForCollectionNavigationSubquery( + outerKeyAccess, + innerKeyAccess, + outerBinding), + collectionCurrentParameter); + + operand = Expression.Call( + LinqMethodHelpers.QueryableWhereMethodInfo.MakeGenericMethod(collectionEntityType.ClrType), + entityQueryable, + predicate); + } + else + { + operand = new NavigationBindingExpression( + rootParameter, + navigationTreeNode, + collectionEntityType, + sourceMapping, + collectionEntityType.ClrType); + } + + var result = NavigationExpansionHelpers.CreateNavigationExpansionRoot( + operand, collectionEntityType, navigationTreeNode.Navigation); // this is needed for cases like: root.Include(r => r.Collection).ThenInclude(c => c.Reference).Select(r => r.Collection) // result should be elements of the collection navigation with their 'Reference' included diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs index ef15939a099..15ca7bb3889 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpandingVisitor_MethodCall.cs @@ -6,7 +6,6 @@ using System.Linq; using System.Linq.Expressions; using System.Reflection; -using System.Xml; using Microsoft.EntityFrameworkCore.Diagnostics; using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.Metadata; @@ -16,7 +15,7 @@ namespace Microsoft.EntityFrameworkCore.Query.NavigationExpansion.Visitors { - public partial class NavigationExpandingVisitor : ExpressionVisitor + public partial class NavigationExpandingVisitor { protected override Expression VisitMethodCall(MethodCallExpression methodCallExpression) { diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs index 6403d6e7a6c..684c7f95a21 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationExpansionReducingVisitor.cs @@ -19,17 +19,16 @@ protected override Expression VisitExtension(Expression extensionExpression) switch (extensionExpression) { case NavigationBindingExpression navigationBindingExpression: - { - return navigationBindingExpression.RootParameter.BuildPropertyAccess(navigationBindingExpression.NavigationTreeNode.ToMapping); - } + return navigationBindingExpression.RootParameter.BuildPropertyAccess( + navigationBindingExpression.NavigationTreeNode.ToMapping); case NavigationExpansionRootExpression navigationExpansionRootExpression: return Visit(navigationExpansionRootExpression.Unwrap()); + case NavigationExpansionExpression navigationExpansionExpression: { - var includeResult = ApplyIncludes(navigationExpansionExpression); - var state = includeResult.State; - var result = Visit(includeResult.Operand); + var (result, state) = ApplyIncludes(navigationExpansionExpression); + result = Visit(result); if (!state.ApplyPendingSelector && state.PendingOrderings.Count == 0 @@ -39,7 +38,7 @@ protected override Expression VisitExtension(Expression extensionExpression) return result; } - var parameter = Expression.Parameter(result.Type.GetSequenceType()); + var parameterType = result.Type.GetSequenceType(); foreach (var pendingOrdering in state.PendingOrderings) { @@ -56,11 +55,11 @@ protected override Expression VisitExtension(Expression extensionExpression) var pendingSelectorBodyType = pendingSelector.Type.GetGenericArguments()[1]; var pendingSelectMethod = result.Type.IsGenericType && (result.Type.GetGenericTypeDefinition() == typeof(IEnumerable<>) || result.Type.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>)) - ? LinqMethodHelpers.EnumerableSelectMethodInfo.MakeGenericMethod(parameter.Type, pendingSelectorBodyType) - : LinqMethodHelpers.QueryableSelectMethodInfo.MakeGenericMethod(parameter.Type, pendingSelectorBodyType); + ? LinqMethodHelpers.EnumerableSelectMethodInfo.MakeGenericMethod(parameterType, pendingSelectorBodyType) + : LinqMethodHelpers.QueryableSelectMethodInfo.MakeGenericMethod(parameterType, pendingSelectorBodyType); result = Expression.Call(pendingSelectMethod, result, pendingSelector); - parameter = Expression.Parameter(result.Type.GetSequenceType()); + parameterType = result.Type.GetSequenceType(); } if (state.PendingCardinalityReducingOperator != null) @@ -77,13 +76,14 @@ protected override Expression VisitExtension(Expression extensionExpression) { if (navigationExpansionExpression.Type.GetGenericTypeDefinition() == typeof(IOrderedQueryable<>)) { - var toOrderedQueryableMethodInfo = ToOrderedQueryableMethod.MakeGenericMethod(parameter.Type); + var toOrderedQueryableMethodInfo = ToOrderedQueryableMethod.MakeGenericMethod(parameterType); return Expression.Call(toOrderedQueryableMethodInfo, result); } - else if (navigationExpansionExpression.Type.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>)) + + if (navigationExpansionExpression.Type.GetGenericTypeDefinition() == typeof(IOrderedEnumerable<>)) { - var toOrderedEnumerableMethodInfo = ToOrderedEnumerableMethod.MakeGenericMethod(parameter.Type); + var toOrderedEnumerableMethodInfo = ToOrderedEnumerableMethod.MakeGenericMethod(parameterType); return Expression.Call(toOrderedEnumerableMethodInfo, result); } diff --git a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs index ef4c3b0185e..0c3f5e022d7 100644 --- a/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs +++ b/src/EFCore/Query/NavigationExpansion/Visitors/NavigationPropertyUnbindingVisitor.cs @@ -19,48 +19,52 @@ public NavigationPropertyUnbindingVisitor(ParameterExpression rootParameter) protected override Expression VisitExtension(Expression extensionExpression) { - if (extensionExpression is NavigationBindingExpression navigationBindingExpression - && navigationBindingExpression.RootParameter == _rootParameter) + switch (extensionExpression) { - var node = navigationBindingExpression.NavigationTreeNode; - var navigations = new List(); - while (node != null) + case NavigationBindingExpression navigationBindingExpression + when navigationBindingExpression.RootParameter == _rootParameter: { - if (node.Navigation != null) + var node = navigationBindingExpression.NavigationTreeNode; + var navigations = new List(); + while (node != null) { - navigations.Add(node.Navigation); + if (node.Navigation != null) + { + navigations.Add(node.Navigation); + } + node = node.Parent; } - node = node.Parent; - } - var result = navigationBindingExpression.RootParameter.BuildPropertyAccess( - navigationBindingExpression.NavigationTreeNode.ToMapping, - navigations.Count == navigationBindingExpression.NavigationTreeNode.ToMapping.Count ? navigations : null); + var result = navigationBindingExpression.RootParameter.BuildPropertyAccess( + navigationBindingExpression.NavigationTreeNode.ToMapping, + navigations.Count == navigationBindingExpression.NavigationTreeNode.ToMapping.Count ? navigations : null); - return result.Type != navigationBindingExpression.Type - ? Expression.Convert(result, navigationBindingExpression.Type) - : result; - } + return result.Type != navigationBindingExpression.Type + ? Expression.Convert(result, navigationBindingExpression.Type) + : result; + } - if (extensionExpression is CustomRootExpression customRootExpression - && customRootExpression.RootParameter == _rootParameter) - { - var result = _rootParameter.BuildPropertyAccess(customRootExpression.Mapping); + case CustomRootExpression customRootExpression + when customRootExpression.RootParameter == _rootParameter: + { + var result = _rootParameter.BuildPropertyAccess(customRootExpression.Mapping); - return result.Type != customRootExpression.Type - ? Expression.Convert(result, customRootExpression.Type) - : result; - } + return result.Type != customRootExpression.Type + ? Expression.Convert(result, customRootExpression.Type) + : result; + } - if (extensionExpression is NavigationExpansionRootExpression - || extensionExpression is NavigationExpansionExpression) - { - var result = new NavigationExpansionReducingVisitor().Visit(extensionExpression); + case NavigationExpansionRootExpression _: + case NavigationExpansionExpression _: + { + var result = new NavigationExpansionReducingVisitor().Visit(extensionExpression); - return Visit(result); - } + return Visit(result); + } - return base.VisitExtension(extensionExpression); + default: + return base.VisitExtension(extensionExpression); + } } } } diff --git a/src/EFCore/Query/Pipeline/CollectionShaperExpression.cs b/src/EFCore/Query/Pipeline/CollectionShaperExpression.cs index 3ceb469f038..2cbf752642b 100644 --- a/src/EFCore/Query/Pipeline/CollectionShaperExpression.cs +++ b/src/EFCore/Query/Pipeline/CollectionShaperExpression.cs @@ -13,7 +13,7 @@ namespace Microsoft.EntityFrameworkCore.Query.Pipeline public class CollectionShaperExpression : Expression, IPrintable { public CollectionShaperExpression( - ProjectionBindingExpression projection, + Expression projection, Expression innerShaper, INavigation navigation, Type elementType) @@ -26,24 +26,21 @@ public CollectionShaperExpression( protected override Expression VisitChildren(ExpressionVisitor visitor) { - var projection = (ProjectionBindingExpression)visitor.Visit(Projection); + var projection = visitor.Visit(Projection); var innerShaper = visitor.Visit(InnerShaper); return Update(projection, innerShaper); } - public CollectionShaperExpression Update(ProjectionBindingExpression projection, Expression innerShaper) - { - return projection != Projection || innerShaper != InnerShaper + public CollectionShaperExpression Update(Expression projection, Expression innerShaper) + => projection != Projection || innerShaper != InnerShaper ? new CollectionShaperExpression(projection, innerShaper, Navigation, ElementType) : this; - } public override ExpressionType NodeType => ExpressionType.Extension; - public override Type Type => Navigation?.ClrType ?? typeof(List<>).MakeGenericType(ElementType); - public ProjectionBindingExpression Projection { get; } + public Expression Projection { get; } public Expression InnerShaper { get; } public INavigation Navigation { get; } public Type ElementType { get; } diff --git a/src/EFCore/Query/Pipeline/IQueryCompilationContextFactory2.cs b/src/EFCore/Query/Pipeline/IQueryCompilationContextFactory.cs similarity index 100% rename from src/EFCore/Query/Pipeline/IQueryCompilationContextFactory2.cs rename to src/EFCore/Query/Pipeline/IQueryCompilationContextFactory.cs diff --git a/src/EFCore/Query/Pipeline/IQueryableMethodTranslatingExpressionVisitorFactory.cs b/src/EFCore/Query/Pipeline/IQueryableMethodTranslatingExpressionVisitorFactory.cs index e7531412ee2..a3190059ede 100644 --- a/src/EFCore/Query/Pipeline/IQueryableMethodTranslatingExpressionVisitorFactory.cs +++ b/src/EFCore/Query/Pipeline/IQueryableMethodTranslatingExpressionVisitorFactory.cs @@ -9,5 +9,4 @@ public interface IQueryableMethodTranslatingExpressionVisitorFactory { QueryableMethodTranslatingExpressionVisitor Create(IModel model); } - } diff --git a/src/EFCore/Query/Pipeline/ProjectionBindingExpression.cs b/src/EFCore/Query/Pipeline/ProjectionBindingExpression.cs index d0b3581eca4..26ce322e1fb 100644 --- a/src/EFCore/Query/Pipeline/ProjectionBindingExpression.cs +++ b/src/EFCore/Query/Pipeline/ProjectionBindingExpression.cs @@ -41,30 +41,7 @@ public ProjectionBindingExpression(Expression queryExpression, IDictionary ExpressionType.Extension; - protected override Expression VisitChildren(ExpressionVisitor visitor) - { - return this; - } - - #region Equality & HashCode - public override bool Equals(object obj) - => obj != null - && (ReferenceEquals(this, obj) - || obj is ProjectionBindingExpression projectionBindingExpression - && Equals(projectionBindingExpression)); - - private bool Equals(ProjectionBindingExpression projectionBindingExpression) - => QueryExpression.Equals(projectionBindingExpression.QueryExpression) - && (ProjectionMember == null - ? projectionBindingExpression.ProjectionMember == null - : ProjectionMember.Equals(projectionBindingExpression.ProjectionMember)) - && Index == projectionBindingExpression.Index - // Using reference equality here since if we are this far, we don't need to compare this. - && IndexMap == projectionBindingExpression.IndexMap; - - public override int GetHashCode() => HashCode.Combine(base.GetHashCode(), QueryExpression, ProjectionMember, Index, IndexMap); - - #endregion + protected override Expression VisitChildren(ExpressionVisitor visitor) => this; public void Print(ExpressionPrinter expressionPrinter) { @@ -88,5 +65,22 @@ public void Print(ExpressionPrinter expressionPrinter) } } } + + public override bool Equals(object obj) + => obj != null + && (ReferenceEquals(this, obj) + || obj is ProjectionBindingExpression projectionBindingExpression + && Equals(projectionBindingExpression)); + + private bool Equals(ProjectionBindingExpression projectionBindingExpression) + => QueryExpression.Equals(projectionBindingExpression.QueryExpression) + && Type == projectionBindingExpression.Type + && (ProjectionMember?.Equals(projectionBindingExpression.ProjectionMember) + ?? projectionBindingExpression.ProjectionMember == null) + && Index == projectionBindingExpression.Index + // Using reference equality here since if we are this far, we don't need to compare this. + && IndexMap == projectionBindingExpression.IndexMap; + + public override int GetHashCode() => HashCode.Combine(QueryExpression, ProjectionMember, Index, IndexMap); } } diff --git a/src/EFCore/Query/Pipeline/ProjectionMember.cs b/src/EFCore/Query/Pipeline/ProjectionMember.cs index ab3f1eedc0a..eaa421253b3 100644 --- a/src/EFCore/Query/Pipeline/ProjectionMember.cs +++ b/src/EFCore/Query/Pipeline/ProjectionMember.cs @@ -45,6 +45,7 @@ public ProjectionMember ShiftMember(MemberInfo member) public override int GetHashCode() { var hash = new HashCode(); + // ReSharper disable once ForCanBeConvertedToForeach for (var i = 0; i < _memberChain.Count; i++) { hash.Add(_memberChain[i]); @@ -54,12 +55,9 @@ public override int GetHashCode() } public override bool Equals(object obj) - { - return obj is null - ? false - : obj is ProjectionMember projectionMember - && Equals(projectionMember); - } + => obj != null + && (obj is ProjectionMember projectionMember + && Equals(projectionMember)); private bool Equals(ProjectionMember other) { diff --git a/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs b/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs index a4d26f44b55..164366983c7 100644 --- a/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs +++ b/src/EFCore/Query/Pipeline/QueryCompilationContextFactory.cs @@ -39,8 +39,7 @@ public QueryCompilationContextFactory( } public QueryCompilationContext Create(bool async) - { - var queryCompilationContext = new QueryCompilationContext( + => new QueryCompilationContext( _model, _queryOptimizerFactory, _queryableMethodTranslatingExpressionVisitorFactory, @@ -50,8 +49,5 @@ public QueryCompilationContext Create(bool async) _contextOptions, _logger, async); - - return queryCompilationContext; - } } } diff --git a/src/EFCore/Query/Pipeline/ShapedQueryExpressionVisitor.cs b/src/EFCore/Query/Pipeline/ShapedQueryExpressionVisitor.cs index 85d8b171366..3cf14d86afe 100644 --- a/src/EFCore/Query/Pipeline/ShapedQueryExpressionVisitor.cs +++ b/src/EFCore/Query/Pipeline/ShapedQueryExpressionVisitor.cs @@ -34,7 +34,7 @@ private static readonly PropertyInfo _cancellationTokenMemberInfo private readonly Expression _cancellationTokenParameter; private readonly EntityMaterializerInjectingExpressionVisitor _entityMaterializerInjectingExpressionVisitor; - public ShapedQueryCompilingExpressionVisitor( + protected ShapedQueryCompilingExpressionVisitor( QueryCompilationContext queryCompilationContext, IEntityMaterializerSource entityMaterializerSource) { @@ -58,37 +58,34 @@ public ShapedQueryCompilingExpressionVisitor( protected override Expression VisitExtension(Expression extensionExpression) { - switch (extensionExpression) + if (extensionExpression is ShapedQueryExpression shapedQueryExpression) { - case ShapedQueryExpression shapedQueryExpression: - var serverEnumerable = VisitShapedQueryExpression(shapedQueryExpression); - switch (shapedQueryExpression.ResultType) - { - case ResultType.Enumerable: - return serverEnumerable; - - case ResultType.Single: - return Async - ? Expression.Call( - _singleAsyncMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), - serverEnumerable, - _cancellationTokenParameter) - : Expression.Call( - _singleMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), - serverEnumerable); - - case ResultType.SingleWithDefault: - return Async - ? Expression.Call( - _singleOrDefaultAsyncMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), - serverEnumerable, - _cancellationTokenParameter) - : Expression.Call( - _singleOrDefaultMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), - serverEnumerable); - } - - break; + var serverEnumerable = VisitShapedQueryExpression(shapedQueryExpression); + switch (shapedQueryExpression.ResultType) + { + case ResultType.Enumerable: + return serverEnumerable; + + case ResultType.Single: + return Async + ? Expression.Call( + _singleAsyncMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), + serverEnumerable, + _cancellationTokenParameter) + : Expression.Call( + _singleMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), + serverEnumerable); + + case ResultType.SingleWithDefault: + return Async + ? Expression.Call( + _singleOrDefaultAsyncMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), + serverEnumerable, + _cancellationTokenParameter) + : Expression.Call( + _singleOrDefaultMethodInfo.MakeGenericMethod(serverEnumerable.Type.TryGetSequenceType()), + serverEnumerable); + } } return base.VisitExtension(extensionExpression); @@ -156,7 +153,7 @@ protected virtual Expression CreateReadValueExpression( IPropertyBase property) => _entityMaterializerSource.CreateReadValueExpression(valueBufferExpression, type, index, property); - protected virtual Expression InjectEntityMaterializer(Expression expression) + protected virtual Expression InjectEntityMaterializers(Expression expression) => _entityMaterializerInjectingExpressionVisitor.Inject(expression); private class EntityMaterializerInjectingExpressionVisitor : ExpressionVisitor @@ -195,18 +192,14 @@ public EntityMaterializerInjectingExpressionVisitor( public Expression Inject(Expression expression) => Visit(expression); protected override Expression VisitExtension(Expression extensionExpression) - { - if (extensionExpression is EntityShaperExpression entityShaperExpression) - { - return ProcessEntityShaper(entityShaperExpression); - } - - return base.VisitExtension(extensionExpression); - } + => extensionExpression is EntityShaperExpression entityShaperExpression + ? ProcessEntityShaper(entityShaperExpression) + : base.VisitExtension(extensionExpression); private Expression ProcessEntityShaper(EntityShaperExpression entityShaperExpression) { _currentEntityIndex++; + var expressions = new List(); var variables = new List(); @@ -246,14 +239,14 @@ private Expression ProcessEntityShaper(EntityShaperExpression entityShaperExpres if (_trackQueryResults) { - var entry = Expression.Variable(typeof(InternalEntityEntry), "entry" + _currentEntityIndex); - var hasNullKey = Expression.Variable(typeof(bool), "hasNullKey" + _currentEntityIndex); - variables.Add(entry); - variables.Add(hasNullKey); + var entryVariable = Expression.Variable(typeof(InternalEntityEntry), "entry" + _currentEntityIndex); + var hasNullKeyVariable = Expression.Variable(typeof(bool), "hasNullKey" + _currentEntityIndex); + variables.Add(entryVariable); + variables.Add(hasNullKeyVariable); expressions.Add( Expression.Assign( - entry, + entryVariable, Expression.Call( Expression.MakeMemberAccess( QueryCompilationContext.QueryContextParameter, @@ -269,22 +262,23 @@ private Expression ProcessEntityShaper(EntityShaperExpression entityShaperExpres p.GetIndex(), p))), Expression.Constant(!entityShaperExpression.Nullable), - hasNullKey))); + hasNullKeyVariable))); expressions.Add(Expression.IfThen( - Expression.Not(hasNullKey), + Expression.Not(hasNullKeyVariable), Expression.IfThenElse( Expression.NotEqual( - entry, + entryVariable, Expression.Constant(default(InternalEntityEntry), typeof(InternalEntityEntry))), Expression.Block( - Expression.Assign(instanceVariable, Expression.Convert( - Expression.MakeMemberAccess(entry, _entityMemberInfo), - entityType.ClrType)), Expression.Assign( concreteEntityTypeVariable, - Expression.MakeMemberAccess(entry, _entityTypeMemberInfo))), - MaterializeEntity(entityType, materializationContextVariable, concreteEntityTypeVariable, instanceVariable)))); + Expression.MakeMemberAccess(entryVariable, _entityTypeMemberInfo)), + Expression.Assign(instanceVariable, Expression.Convert( + Expression.MakeMemberAccess(entryVariable, _entityMemberInfo), + entityType.ClrType))), + MaterializeEntity( + entityType, materializationContextVariable, concreteEntityTypeVariable, instanceVariable, entryVariable)))); } else { @@ -309,7 +303,8 @@ private Expression ProcessEntityShaper(EntityShaperExpression entityShaperExpres p), Expression.Constant(null))) .Aggregate((a, b) => Expression.OrElse(a, b)), - MaterializeEntity(entityType, materializationContextVariable, concreteEntityTypeVariable, instanceVariable))); + MaterializeEntity( + entityType, materializationContextVariable, concreteEntityTypeVariable, instanceVariable, null))); } expressions.Add(instanceVariable); @@ -320,7 +315,8 @@ private Expression MaterializeEntity( IEntityType entityType, ParameterExpression materializationContextVariable, ParameterExpression concreteEntityTypeVariable, - ParameterExpression instanceVariable) + ParameterExpression instanceVariable, + ParameterExpression entryVariable) { var expressions = new List(); var variables = new List(); @@ -385,12 +381,13 @@ var discriminatorValue if (_trackQueryResults) { expressions.Add( - Expression.Call( - QueryCompilationContext.QueryContextParameter, - _startTrackingMethodInfo, - concreteEntityTypeVariable, - instanceVariable, - shadowValuesVariable)); + Expression.Assign( + entryVariable, Expression.Call( + QueryCompilationContext.QueryContextParameter, + _startTrackingMethodInfo, + concreteEntityTypeVariable, + instanceVariable, + shadowValuesVariable))); } expressions.Add(instanceVariable); diff --git a/src/EFCore/Query/QueryContext.cs b/src/EFCore/Query/QueryContext.cs index 339b87fd5b8..72c1788a26e 100644 --- a/src/EFCore/Query/QueryContext.cs +++ b/src/EFCore/Query/QueryContext.cs @@ -156,12 +156,10 @@ public virtual object RemoveParameter(string name) /// public virtual void BeginTrackingQuery() => StateManager.BeginTrackingQuery(); - public virtual void StartTracking( + public virtual InternalEntityEntry StartTracking( IEntityType entityType, object entity, ValueBuffer valueBuffer) - { - StateManager.StartTrackingFromQuery(entityType, entity, valueBuffer, handledForeignKeys: null); - } + => StateManager.StartTrackingFromQuery(entityType, entity, valueBuffer, handledForeignKeys: null); } } diff --git a/src/EFCore/Storage/MaterializationContext.cs b/src/EFCore/Storage/MaterializationContext.cs index b08153bf76a..dfccd995458 100644 --- a/src/EFCore/Storage/MaterializationContext.cs +++ b/src/EFCore/Storage/MaterializationContext.cs @@ -1,7 +1,6 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. -using System.Collections.Generic; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; diff --git a/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs b/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs index a09f3acec57..8a86728f4ca 100644 --- a/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/NestedDocumentsTest.cs @@ -177,28 +177,39 @@ public virtual async Task Can_add_collection_dependent_to_owner() } } - // #13559 + [ConditionalFact] + public virtual async Task Can_query_just_nested_reference() + { + await using (var testDatabase = CreateTestStore()) + { + using (var context = CreateContext()) + { + var firstOperator = context.Set().Select(v => v.Operator).OrderBy(o => o.VehicleName).AsNoTracking().First(); + + Assert.Equal("Albert Williams", firstOperator.Name); + Assert.Null(firstOperator.Vehicle); + } + } + } + + // #12086 //[ConditionalFact] - public virtual async Task Can_update_just_dependents() + public virtual async Task Can_query_just_nested_collection() { await using (var testDatabase = CreateTestStore()) { - Operator firstOperator; - Engine firstEngine; using (var context = CreateContext()) { - firstOperator = context.Set().OrderBy(o => o.VehicleName).First(); - firstOperator.Name += "1"; - firstEngine = context.Set().OrderBy(o => o.VehicleName).First(); - firstEngine.Description += "1"; + context.Add(new Person { Id = 3, Addresses = new[] { new Address { Street = "First", City = "City" }, new Address { Street = "Second", City = "City" } } }); context.SaveChanges(); } using (var context = CreateContext()) { - Assert.Equal(firstOperator.Name, context.Set().OrderBy(o => o.VehicleName).First().Name); - Assert.Equal(firstEngine.Description, context.Set().OrderBy(o => o.VehicleName).First().Description); + var addresses = context.Set().Select(p => p.Addresses).AsNoTracking().First(); + + Assert.Equal(2, addresses.Count); } } } @@ -328,16 +339,16 @@ protected virtual DbContextOptionsBuilder AddOptions(DbContextOptionsBuilder bui .Log(CoreEventId.SensitiveDataLoggingEnabledWarning) .Log(CoreEventId.PossibleUnintendedReferenceComparisonWarning)); - protected virtual NestedTransportationContext CreateContext() + protected virtual EmbeddedTransportationContext CreateContext() { var options = AddOptions(TestStore.AddProviderOptions(new DbContextOptionsBuilder())) .UseInternalServiceProvider(ServiceProvider).Options; - return new NestedTransportationContext(options); + return new EmbeddedTransportationContext(options); } - protected class NestedTransportationContext : TransportationContext + protected class EmbeddedTransportationContext : TransportationContext { - public NestedTransportationContext(DbContextOptions options) : base(options) + public EmbeddedTransportationContext(DbContextOptions options) : base(options) { } diff --git a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs index 1cc6333df20..e48b980b284 100644 --- a/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/Query/OwnedQueryCosmosTest.cs @@ -50,7 +50,6 @@ public override void Navigation_rewrite_on_owned_collection_with_composition_com base.Navigation_rewrite_on_owned_collection_with_composition_complex(); } - [ConditionalFact(Skip = "Owned projection #12086")] public override void Navigation_rewrite_on_owned_reference_projecting_entity() { base.Navigation_rewrite_on_owned_reference_projecting_entity(); @@ -58,10 +57,19 @@ public override void Navigation_rewrite_on_owned_reference_projecting_entity() AssertSql( @"SELECT c FROM root c -WHERE (((c[""Discriminator""] = ""LeafB"") OR ((c[""Discriminator""] = ""LeafA"") OR ((c[""Discriminator""] = ""Branch"") OR (c[""Discriminator""] = ""OwnedPerson"")))) AND (c[""PersonAddress""][""Country""][""Name""] = ""USA""))"); +WHERE (c[""Discriminator""] IN (""OwnedPerson"", ""Branch"", ""LeafB"", ""LeafA"") AND (c[""PersonAddress""][""Country""][""Name""] = ""USA""))"); + } + + public override void Navigation_rewrite_on_owned_reference_projecting_scalar() + { + base.Navigation_rewrite_on_owned_reference_projecting_scalar(); + + AssertSql( + @"SELECT c[""PersonAddress""][""Country""][""Name""] +FROM root c +WHERE (c[""Discriminator""] IN (""OwnedPerson"", ""Branch"", ""LeafB"", ""LeafA"") AND (c[""PersonAddress""][""Country""][""Name""] = ""USA""))"); } - [ConditionalFact(Skip = "Owned collection #12086")] public override void Query_for_base_type_loads_all_owned_navs() { base.Query_for_base_type_loads_all_owned_navs(); @@ -69,10 +77,9 @@ public override void Query_for_base_type_loads_all_owned_navs() AssertSql( @"SELECT c FROM root c -WHERE (((c[""Discriminator""] = ""LeafB"") OR ((c[""Discriminator""] = ""LeafA"") OR ((c[""Discriminator""] = ""Branch"") OR (c[""Discriminator""] = ""OwnedPerson"")))) AND (c[""PersonAddress""][""Country""][""Name""] = ""USA""))"); +WHERE c[""Discriminator""] IN (""OwnedPerson"", ""Branch"", ""LeafB"", ""LeafA"")"); } - [ConditionalFact(Skip = "Owned collection #12086")] public override void Query_for_branch_type_loads_all_owned_navs() { base.Query_for_branch_type_loads_all_owned_navs(); @@ -80,10 +87,9 @@ public override void Query_for_branch_type_loads_all_owned_navs() AssertSql( @"SELECT c FROM root c -WHERE (((c[""Discriminator""] = ""LeafB"") OR ((c[""Discriminator""] = ""LeafA"") OR ((c[""Discriminator""] = ""Branch"") OR (c[""Discriminator""] = ""OwnedPerson"")))) AND (c[""PersonAddress""][""Country""][""Name""] = ""USA""))"); +WHERE c[""Discriminator""] IN (""Branch"", ""LeafA"")"); } - [ConditionalFact(Skip = "Owned collection #12086")] public override void Query_for_leaf_type_loads_all_owned_navs() { base.Query_for_leaf_type_loads_all_owned_navs(); @@ -91,7 +97,7 @@ public override void Query_for_leaf_type_loads_all_owned_navs() AssertSql( @"SELECT c FROM root c -WHERE (((c[""Discriminator""] = ""LeafB"") OR ((c[""Discriminator""] = ""LeafA"") OR ((c[""Discriminator""] = ""Branch"") OR (c[""Discriminator""] = ""OwnedPerson"")))) AND (c[""PersonAddress""][""Country""][""Name""] = ""USA""))"); +WHERE (c[""Discriminator""] = ""LeafA"")"); } [ConditionalFact(Skip = "LeftJoin #12086")] diff --git a/test/EFCore.Specification.Tests/Query/AsTrackingTestBase.cs b/test/EFCore.Specification.Tests/Query/AsTrackingTestBase.cs index ac8aba5df3c..eb207d036a4 100644 --- a/test/EFCore.Specification.Tests/Query/AsTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/AsTrackingTestBase.cs @@ -4,7 +4,6 @@ using System.Linq; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Xunit; // ReSharper disable InconsistentNaming diff --git a/test/EFCore.Specification.Tests/Query/ChangeTrackingTestBase.cs b/test/EFCore.Specification.Tests/Query/ChangeTrackingTestBase.cs index e83e59196e0..c2cca231699 100644 --- a/test/EFCore.Specification.Tests/Query/ChangeTrackingTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ChangeTrackingTestBase.cs @@ -4,7 +4,6 @@ using System.Linq; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Xunit; // ReSharper disable InconsistentNaming diff --git a/test/EFCore.Specification.Tests/Query/CompiledQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/CompiledQueryTestBase.cs index adaa68810fc..9a8d186942f 100644 --- a/test/EFCore.Specification.Tests/Query/CompiledQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/CompiledQueryTestBase.cs @@ -7,7 +7,6 @@ using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.TestModels.Northwind; using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Xunit; // ReSharper disable AccessToModifiedClosure diff --git a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs index db18104f9c4..39e69dfb02f 100644 --- a/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/ComplexNavigationsQueryTestBase.cs @@ -7,7 +7,6 @@ using System.Threading.Tasks; using Microsoft.EntityFrameworkCore.TestModels.ComplexNavigationsModel; using Microsoft.EntityFrameworkCore.TestUtilities; -using Microsoft.EntityFrameworkCore.TestUtilities.Xunit; using Xunit; // ReSharper disable ConvertClosureToMethodGroup diff --git a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs index 1bf777f10ee..623cb7ad85e 100644 --- a/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs +++ b/test/EFCore.Specification.Tests/Query/OwnedQueryTestBase.cs @@ -3,10 +3,8 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using Microsoft.EntityFrameworkCore.Diagnostics; -using Microsoft.EntityFrameworkCore.Internal; using Microsoft.EntityFrameworkCore.TestUtilities; using Xunit; @@ -81,9 +79,16 @@ public virtual void Query_for_base_type_loads_all_owned_navs() Assert.True(people.OfType().All(b => b.BranchAddress != null)); Assert.True(people.OfType().All(a => a.LeafAAddress != null)); Assert.True(people.OfType().All(b => b.LeafBAddress != null)); - - // TODO: Owned collections are expanded in nav expansion //Assert.True(people.All(p => p.Orders.Count == (p.Id == 1 ? 2 : 1))); + //Assert.True(people.All(p => p.Orders.All(o => o.Client == p))); + + //var branch = people.OfType().First(); + //var branchEntry = context.Entry(branch); + //Assert.True(branchEntry.Collection(b => b.Orders).IsLoaded); + //Assert.True(branchEntry.Reference(b => b.PersonAddress).IsLoaded); + //Assert.True(branchEntry.Reference(b => b.BranchAddress).IsLoaded); + + //Assert.True(people.All(p => p.Orders.All(o => context.Entry(o).Reference(oe => oe.Client).IsLoaded))); } } @@ -109,8 +114,6 @@ public virtual void Query_for_branch_type_loads_all_owned_navs() Assert.True(people.All(p => p.PersonAddress != null)); Assert.True(people.All(b => b.BranchAddress != null)); Assert.True(people.OfType().All(a => a.LeafAAddress != null)); - - // TODO: Owned collections are expanded in nav expansion //Assert.True(people.All(p => p.Orders.Count == 1)); } } @@ -126,6 +129,7 @@ public virtual void Query_for_leaf_type_loads_all_owned_navs() Assert.True(people.All(p => p.PersonAddress != null)); Assert.True(people.All(b => b.BranchAddress != null)); Assert.True(people.All(a => a.LeafAAddress != null)); + //Assert.True(people.All(p => p.Orders.Count == 1)); } } @@ -218,12 +222,10 @@ public virtual void Navigation_rewrite_on_owned_collection_with_composition() var query = ctx.Set().Select(p => p.Orders.Select(o => o.Id != 42).FirstOrDefault()); var result = query.ToList(); - //Assert.Equal(4, result.Count); - //Assert.True(result.All(r => r.Count > 0)); + Assert.Equal(4, result.Count); } } - [ConditionalFact(Skip = "Issue#16620")] public virtual void Navigation_rewrite_on_owned_collection_with_composition_complex() { @@ -232,8 +234,7 @@ public virtual void Navigation_rewrite_on_owned_collection_with_composition_comp var query = ctx.Set().Select(p => p.Orders.Select(o => o.Client.PersonAddress.Country.Name).FirstOrDefault()); var result = query.ToList(); - //Assert.Equal(4, result.Count); - //Assert.True(result.All(r => r.Count > 0)); + Assert.Equal(4, result.Count); } } @@ -660,14 +661,16 @@ protected override void OnModelCreating(ModelBuilder modelBuilder, DbContext con sb => { sb.HasData(new Star { Id = 1, Name = "Sol" }); - sb.OwnsMany( - s => s.Composition, ob => - { - ob.HasKey(e => e.Id); - ob.HasData( - new { Id = "H", Name = "Hydrogen", StarId = 1 }, - new { Id = "He", Name = "Helium", StarId = 1 }); - }); + // TODO: Owned collections are expanded in nav expansion + sb.Ignore(p => p.Composition); + //sb.OwnsMany( + // s => s.Composition, ob => + // { + // ob.HasKey(e => e.Id); + // ob.HasData( + // new { Id = "H", Name = "Hydrogen", StarId = 1 }, + // new { Id = "He", Name = "Helium", StarId = 1 }); + // }); }); } diff --git a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs index 0986bd446eb..254f71feb2f 100644 --- a/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs +++ b/test/EFCore.SqlServer.FunctionalTests/Query/OwnedQuerySqlServerTest.cs @@ -421,15 +421,13 @@ public override void Navigation_rewrite_on_owned_reference_followed_by_regular_e base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference(); AssertSql( - @"SELECT [s].[Id], [s].[Name], [o].[Id], [e].[Id], [e].[Name], [e].[StarId] + @"SELECT [s].[Id], [s].[Name] FROM [OwnedPerson] AS [o] LEFT JOIN [OwnedPerson] AS [o0] ON [o].[Id] = [o0].[Id] LEFT JOIN [OwnedPerson] AS [o1] ON [o0].[Id] = [o1].[Id] LEFT JOIN [Planet] AS [p] ON [o1].[PersonAddress_Country_PlanetId] = [p].[Id] LEFT JOIN [Star] AS [s] ON [p].[StarId] = [s].[Id] -LEFT JOIN [Element] AS [e] ON [s].[Id] = [e].[StarId] -WHERE [o].[Discriminator] IN (N'OwnedPerson', N'Branch', N'LeafB', N'LeafA') -ORDER BY [o].[Id], [e].[Id]"); +WHERE [o].[Discriminator] IN (N'OwnedPerson', N'Branch', N'LeafB', N'LeafA')"); } public override void Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_and_scalar() @@ -452,15 +450,13 @@ public override void base.Navigation_rewrite_on_owned_reference_followed_by_regular_entity_and_another_reference_in_predicate_and_projection(); AssertSql( - @"SELECT [s].[Id], [s].[Name], [o].[Id], [e].[Id], [e].[Name], [e].[StarId] + @"SELECT [s].[Id], [s].[Name] FROM [OwnedPerson] AS [o] LEFT JOIN [OwnedPerson] AS [o0] ON [o].[Id] = [o0].[Id] LEFT JOIN [OwnedPerson] AS [o1] ON [o0].[Id] = [o1].[Id] LEFT JOIN [Planet] AS [p] ON [o1].[PersonAddress_Country_PlanetId] = [p].[Id] LEFT JOIN [Star] AS [s] ON [p].[StarId] = [s].[Id] -LEFT JOIN [Element] AS [e] ON [s].[Id] = [e].[StarId] -WHERE [o].[Discriminator] IN (N'OwnedPerson', N'Branch', N'LeafB', N'LeafA') AND (([s].[Name] = N'Sol') AND [s].[Name] IS NOT NULL) -ORDER BY [o].[Id], [e].[Id]"); +WHERE [o].[Discriminator] IN (N'OwnedPerson', N'Branch', N'LeafB', N'LeafA') AND (([s].[Name] = N'Sol') AND [s].[Name] IS NOT NULL)"); }