From c67507bba60bcb1fdc3343d39d15355b549a86b6 Mon Sep 17 00:00:00 2001 From: Smit Patel Date: Mon, 27 Apr 2020 16:47:58 -0700 Subject: [PATCH] Query: Clean up in query filter to partition key detection (#20762) --- ...yableMethodTranslatingExpressionVisitor.cs | 299 +++++++----------- .../Query/Internal/SelectExpression.cs | 42 +-- 2 files changed, 140 insertions(+), 201 deletions(-) diff --git a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs index d7ef6147907..1f1a984b226 100644 --- a/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs +++ b/src/EFCore.Cosmos/Query/Internal/CosmosQueryableMethodTranslatingExpressionVisitor.cs @@ -91,19 +91,18 @@ public override Expression Visit(Expression expression) var entityType = queryRootExpression.EntityType; if (queryRootMethodCallExpression.Arguments[1] is UnaryExpression unaryExpression - && unaryExpression.Operand is LambdaExpression lambdaExpression - && lambdaExpression.Body is BinaryExpression lambdaBodyBinaryExpression) + && unaryExpression.Operand is LambdaExpression lambdaExpression) { var queryProperties = new List(); var parameterNames = new List(); - if (ProcessJoinCondition(lambdaBodyBinaryExpression, queryProperties, parameterNames)) + if (ExtractPartitionKeyFromPredicate(entityType, lambdaExpression.Body, queryProperties, parameterNames)) { var entityTypePrimaryKeyProperties = entityType.FindPrimaryKey().Properties; var idProperty = entityType.GetProperties() .First(p => p.GetJsonPropertyName() == StoreKeyConvention.IdPropertyName); - if (TryGetPartitionKeyProperty(out var partitionKeyProperty) + if (TryGetPartitionKeyProperty(entityType, out var partitionKeyProperty) && entityTypePrimaryKeyProperties.SequenceEqual(queryProperties) && (partitionKeyProperty == null || entityTypePrimaryKeyProperties.Contains(partitionKeyProperty)) @@ -116,75 +115,59 @@ public override Expression Visit(Expression expression) var readItemExpression = new ReadItemExpression(entityType, propertyParameterList); - var shapedQueryExpression = new ShapedQueryExpression( - readItemExpression, - new EntityShaperExpression( - entityType, - new ProjectionBindingExpression( - readItemExpression, - new ProjectionMember(), - typeof(ValueBuffer)), - false)); - - shapedQueryExpression = shapedQueryExpression.UpdateResultCardinality(ResultCardinality.Single); - - return shapedQueryExpression; + return CreateShapedQueryExpression(readItemExpression, entityType) + .UpdateResultCardinality(ResultCardinality.Single); } } + } + } + } + } - bool ProcessJoinCondition( - Expression joinCondition, ICollection properties, ICollection paramNames) - { - if (joinCondition is BinaryExpression joinBinaryExpression) - { - switch (joinBinaryExpression.NodeType) - { - case ExpressionType.AndAlso: - return ProcessJoinCondition(joinBinaryExpression.Left, properties, paramNames) - && ProcessJoinCondition(joinBinaryExpression.Right, properties, paramNames); - - case ExpressionType.Equal: - if (joinBinaryExpression.Left is MethodCallExpression equalMethodCallExpression - && joinBinaryExpression.Right is ParameterExpression equalParameterExpresion) - { - if (equalMethodCallExpression.TryGetEFPropertyArguments(out _, out var propertyName)) - { - var property = entityType.FindProperty(propertyName); - if (property == null) - { - return false; - } - properties.Add(property); - paramNames.Add(equalParameterExpresion.Name); - return true; - } - } - return false; - - default: - return false; - } - } - return false; - } + return base.Visit(expression); - bool TryGetPartitionKeyProperty(out IProperty partitionKeyProperty) - { - var partitionKeyPropertyName = entityType.GetPartitionKeyPropertyName(); - if (partitionKeyPropertyName is null) - { - partitionKeyProperty = null; - return true; - } + static bool ExtractPartitionKeyFromPredicate(IEntityType entityType, Expression joinCondition, + ICollection properties, ICollection parameterNames) + { + if (joinCondition is BinaryExpression joinBinaryExpression) + { + if (joinBinaryExpression.NodeType == ExpressionType.AndAlso) + { + return ExtractPartitionKeyFromPredicate(entityType, joinBinaryExpression.Left, properties, parameterNames) + && ExtractPartitionKeyFromPredicate(entityType, joinBinaryExpression.Right, properties, parameterNames); + } - partitionKeyProperty = entityType.FindProperty(partitionKeyPropertyName); - return true; - } + if (joinBinaryExpression.NodeType == ExpressionType.Equal + && joinBinaryExpression.Left is MethodCallExpression equalMethodCallExpression + && joinBinaryExpression.Right is ParameterExpression equalParameterExpresion + && equalMethodCallExpression.TryGetEFPropertyArguments(out _, out var propertyName)) + { + var property = entityType.FindProperty(propertyName); + if (property == null) + { + return false; } + properties.Add(property); + parameterNames.Add(equalParameterExpresion.Name); + return true; } } + + return false; + } + + static bool TryGetPartitionKeyProperty(IEntityType entityType, out IProperty partitionKeyProperty) + { + var partitionKeyPropertyName = entityType.GetPartitionKeyPropertyName(); + if (partitionKeyPropertyName is null) + { + partitionKeyProperty = null; + return true; + } + + partitionKeyProperty = entityType.FindProperty(partitionKeyPropertyName); + return true; } - return base.Visit(expression); } /// @@ -246,16 +229,16 @@ protected override ShapedQueryExpression CreateShapedQueryExpression(IEntityType var selectExpression = _sqlExpressionFactory.Select(entityType); - return new ShapedQueryExpression( - selectExpression, + return CreateShapedQueryExpression(selectExpression, entityType); + } + + private ShapedQueryExpression CreateShapedQueryExpression(Expression queryExpression, IEntityType entityType) + => new ShapedQueryExpression( + queryExpression, new EntityShaperExpression( entityType, - new ProjectionBindingExpression( - selectExpression, - new ProjectionMember(), - typeof(ValueBuffer)), + new ProjectionBindingExpression(queryExpression, new ProjectionMember(), typeof(ValueBuffer)), false)); - } /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -1003,23 +986,22 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so Check.NotNull(source, nameof(source)); Check.NotNull(predicate, nameof(predicate)); - if (source.ShaperExpression is EntityShaperExpression entityShaperExpression) + if (source.ShaperExpression is EntityShaperExpression entityShaperExpression + && entityShaperExpression.EntityType.GetPartitionKeyPropertyName() != null + && TryExtractPartitionKey(predicate.Body, entityShaperExpression.EntityType, out var newPredicate) is Expression partitionKeyValue) { - var (predicateExpression, partitionKeyProperty, partitionKeyValueExpression) = ExtractPartitionKey(predicate.Body, entityShaperExpression.EntityType); + var partitionKeyProperty = entityShaperExpression.EntityType.GetProperty( + entityShaperExpression.EntityType.GetPartitionKeyPropertyName()); + ((SelectExpression)source.QueryExpression).SetPartitionKey(partitionKeyProperty, partitionKeyValue); - if (partitionKeyProperty != null && partitionKeyValueExpression != null) + if (newPredicate == null) { - ((SelectExpression)source.QueryExpression).SetPartitionKeyProperty(partitionKeyProperty, partitionKeyValueExpression); - - if (predicateExpression is null) - { - return source; - } - - predicate = Expression.Lambda(predicateExpression, predicate.Parameters); + return source; } + + predicate = Expression.Lambda(newPredicate, predicate.Parameters); } - + var translation = TranslateLambdaExpression(source, predicate); if (translation != null) { @@ -1030,127 +1012,82 @@ protected override ShapedQueryExpression TranslateWhere(ShapedQueryExpression so return null; - static (Expression predicateExpression, IProperty partitionKeyProperty, Expression partitionKeyValueExpression) - ExtractPartitionKey(Expression expression, IEntityType entityType) + Expression TryExtractPartitionKey(Expression expression, IEntityType entityType, out Expression updatedPredicate) { if (expression is BinaryExpression binaryExpression) { - if (binaryExpression.Left is BinaryExpression leftBinaryExpression - && binaryExpression.Right is BinaryExpression rightBinaryExpression) + partitionKeyValue = GetPartitionKeyValue(binaryExpression, entityType); + if (partitionKeyValue != null) { - switch (binaryExpression.NodeType) - { - case ExpressionType.AndAlso when leftBinaryExpression.NodeType == ExpressionType.Equal - && TryGetPartitionKeyPropertyAndValueExpression( - leftBinaryExpression.Left, leftBinaryExpression.Right, entityType, - out var leftProperty, - out var leftValueExpression): - - return (rightBinaryExpression, leftProperty, leftValueExpression); - - case ExpressionType.AndAlso when rightBinaryExpression.NodeType == ExpressionType.Equal - && TryGetPartitionKeyPropertyAndValueExpression( - rightBinaryExpression.Left, rightBinaryExpression.Right, entityType, - out var rightProperty, - out var rightValueExpression): - - return (leftBinaryExpression, rightProperty, rightValueExpression); + updatedPredicate = null; + return partitionKeyValue; + } - case ExpressionType.Equal when TryGetPartitionKeyPropertyAndValueExpression( - binaryExpression.Left, binaryExpression.Right, entityType, - out var property, - out var valueExpression): + if (binaryExpression.NodeType == ExpressionType.AndAlso) + { + var leftPartitionKeyValue = TryExtractPartitionKey(binaryExpression.Left, entityType, out var leftPredicate); + var rightPartitionKeyValue = TryExtractPartitionKey(binaryExpression.Right, entityType, out var rightPredicate); + if ((leftPartitionKeyValue != null) ^ (rightPartitionKeyValue != null)) + { + updatedPredicate = leftPredicate != null + ? rightPredicate != null + ? binaryExpression.Update(leftPredicate, binaryExpression.Conversion, rightPredicate) + : leftPredicate + : rightPredicate; - return (null, property, valueExpression); + return leftPartitionKeyValue ?? rightPartitionKeyValue; } - - var left = ExtractPartitionKey(leftBinaryExpression, entityType); - var right = ExtractPartitionKey(rightBinaryExpression, entityType); - - return ( - Expression.MakeBinary(binaryExpression.NodeType, left.predicateExpression, right.predicateExpression), - left.partitionKeyProperty ?? right.partitionKeyProperty, - left.partitionKeyValueExpression ?? right.partitionKeyValueExpression); } - return (binaryExpression, null, null); } - return (expression, null, null); - } - - static bool TryGetPartitionKeyPropertyAndValueExpression(Expression leftBinaryExpression, Expression rightBinaryExpression, IEntityType entityType, - out IProperty partitionKeyProperty, - out Expression paritionKeyPropertyValueExpression) - { - partitionKeyProperty = null; - paritionKeyPropertyValueExpression = null; - - if (TryGetPropertyAndValueExpression(leftBinaryExpression, rightBinaryExpression, entityType, - out var leftSidedProperty, - out var leftSidedExpression)) - { - partitionKeyProperty = leftSidedProperty; - paritionKeyPropertyValueExpression = leftSidedExpression; - return true; - } + updatedPredicate = expression; - if (TryGetPropertyAndValueExpression(rightBinaryExpression, leftSidedExpression, entityType, - out var rightSidedProperty, - out var rightSidedExpression)) - { - partitionKeyProperty = rightSidedProperty; - paritionKeyPropertyValueExpression = rightSidedExpression; - return true; - } - - return false; + return null; } - static bool TryGetPropertyAndValueExpression(Expression leftExpression, Expression rightExpression, IEntityType entityType, - out IProperty property, - out Expression expression) + Expression GetPartitionKeyValue(BinaryExpression binaryExpression, IEntityType entityType) { - property = null; - expression = null; - - if (leftExpression is MemberExpression memberExpression - && rightExpression is ConstantExpression constantExpression - && memberExpression.Member.GetSimpleMemberName() == entityType.GetPartitionKeyPropertyName()) + if (binaryExpression.NodeType == ExpressionType.Equal) { - property = entityType.FindProperty(memberExpression.Member.GetSimpleMemberName()); - - if (property is null) + var valueExpression = IsPartitionKeyPropertyAccess(binaryExpression.Left, entityType) + ? binaryExpression.Right + : IsPartitionKeyPropertyAccess(binaryExpression.Right, entityType) + ? binaryExpression.Left + : null; + + if (valueExpression is ConstantExpression + || (valueExpression is ParameterExpression valueParameterExpression + && valueParameterExpression.Name?.StartsWith(QueryCompilationContext.QueryParameterPrefix) == true)) { - return false; + return valueExpression; } - - expression = constantExpression; - return true; } - if (leftExpression is MethodCallExpression methodCallExpression - && rightExpression is ParameterExpression parameterExpression) - { - property = GetPropertyFromMethodCall(methodCallExpression, entityType); - - if (property is null) - { - return false; - } + return null; + } - expression = parameterExpression; - return true; + bool IsPartitionKeyPropertyAccess(Expression expression, IEntityType entityType) + { + IProperty property = null; + switch (expression) + { + case MemberExpression memberExpression: + property = entityType.FindProperty(memberExpression.Member.GetSimpleMemberName()); + break; + + case MethodCallExpression methodCallExpression + when methodCallExpression.TryGetEFPropertyArguments(out _, out var propertyName): + property = entityType.FindProperty(propertyName); + break; + + case MethodCallExpression methodCallExpression + when methodCallExpression.TryGetIndexerArguments(_model, out _, out var propertyName): + property = entityType.FindProperty(propertyName); + break; } - return false; + return property != null && property.Name == entityType.GetPartitionKeyPropertyName(); } - - static IProperty GetPropertyFromMethodCall(MethodCallExpression methodCallExpression, IEntityType entityType) => - methodCallExpression.TryGetEFPropertyArguments(out _, out var parameterName) - ? entityType.FindProperty(parameterName) - : methodCallExpression.TryGetIndexerArguments(entityType.Model, out _, out var indexerName) - ? entityType.FindProperty(indexerName) - : default; } private SqlExpression TranslateExpression(Expression expression) diff --git a/src/EFCore.Cosmos/Query/Internal/SelectExpression.cs b/src/EFCore.Cosmos/Query/Internal/SelectExpression.cs index a9bfd99e772..781bddad163 100644 --- a/src/EFCore.Cosmos/Query/Internal/SelectExpression.cs +++ b/src/EFCore.Cosmos/Query/Internal/SelectExpression.cs @@ -9,8 +9,8 @@ using Microsoft.EntityFrameworkCore.Cosmos.Internal; using Microsoft.EntityFrameworkCore.Metadata; using Microsoft.EntityFrameworkCore.Query; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; using Microsoft.EntityFrameworkCore.Utilities; -using Newtonsoft.Json.Linq; namespace Microsoft.EntityFrameworkCore.Cosmos.Query.Internal { @@ -28,8 +28,8 @@ public class SelectExpression : Expression private readonly List _projection = new List(); private readonly List _orderings = new List(); - private IProperty _partitionKeyProperty; - private Expression _paritionKeyValueExpression; + private ValueConverter _partitionKeyValueConverter; + private Expression _partitionKeyValue; /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to @@ -133,17 +133,17 @@ public SelectExpression( public virtual Expression GetMappedProjection([NotNull] ProjectionMember projectionMember) => _projectionMapping[projectionMember]; - + /// /// 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 SetPartitionKeyProperty([NotNull]IProperty partitionKeyProperty, [NotNull]Expression expression) + public virtual void SetPartitionKey([NotNull] IProperty partitionKeyProperty, [NotNull] Expression expression) { - _partitionKeyProperty = partitionKeyProperty; - _paritionKeyValueExpression = expression; + _partitionKeyValueConverter = partitionKeyProperty.GetTypeMapping().Converter; + _partitionKeyValue = expression; } /// @@ -152,24 +152,26 @@ public virtual void SetPartitionKeyProperty([NotNull]IProperty partitionKeyPrope /// 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 string GetPartitionKey([NotNull]IReadOnlyDictionary parameterValues) + public virtual string GetPartitionKey([NotNull] IReadOnlyDictionary parameterValues) { - return _partitionKeyProperty != null && _paritionKeyValueExpression is ConstantExpression constantExpression - ? GetString(_partitionKeyProperty, constantExpression.Value) - : _partitionKeyProperty != null - && _paritionKeyValueExpression is ParameterExpression parameterExpression - && parameterValues.TryGetValue(parameterExpression.Name, out var value) - ? GetString(_partitionKeyProperty, value) - : null; - - static string GetString(IProperty property, object value) + switch (_partitionKeyValue) { - var converter = property.GetTypeMapping().Converter; + case ConstantExpression constantExpression: + return GetString(_partitionKeyValueConverter, constantExpression.Value); + + case ParameterExpression parameterExpression + when parameterValues.TryGetValue(parameterExpression.Name, out var value): + return GetString(_partitionKeyValueConverter, value); + + default: + return null; - return converter is null + } + + static string GetString(ValueConverter converter, object value) + => converter is null ? (string)value : (string)converter.ConvertToProvider(value); - } } ///