private (NavigationExpansionExpression navigationExpression, Expression nullKeyExpression) CreateNullComparisonArguments( NavigationBindingExpression navigationBindingExpression, NavigationExpansionExpression navigationExpansionExpression) { var navigationKeyAccessExpression = NavigationExpansionHelpers.CreateKeyAccessExpression( navigationBindingExpression, new[] { navigationBindingExpression.EntityType.FindPrimaryKey().Properties.First() }, addNullCheck: true); var nullKeyExpression = NavigationExpansionHelpers.CreateNullKeyExpression( navigationKeyAccessExpression.Type, keyCount: 1); var newNavigationExpansionExpressionState = new NavigationExpansionExpressionState( navigationExpansionExpression.State.CurrentParameter, navigationExpansionExpression.State.SourceMappings, Expression.Lambda(navigationKeyAccessExpression, navigationExpansionExpression.State.PendingSelector.Parameters[0]), applyPendingSelector: true, navigationExpansionExpression.State.PendingOrderings, navigationExpansionExpression.State.PendingIncludeChain, // we need to remap cardinality reducing operator since it's source type has now changed navigationExpansionExpression.State.PendingCardinalityReducingOperator?.GetGenericMethodDefinition().MakeGenericMethod(navigationKeyAccessExpression.Type), navigationExpansionExpression.State.CustomRootMappings, navigationExpansionExpression.State.MaterializeCollectionNavigation); var navigationExpression = new NavigationExpansionExpression( navigationExpansionExpression.Operand, newNavigationExpansionExpressionState, navigationKeyAccessExpression.Type); return(navigationExpression, nullKeyExpression); }
private Expression ProcessMemberPushdown( Expression source, NavigationExpansionExpression navigationExpansionExpression, bool efProperty, MemberInfo memberInfo, string propertyName, Type resultType) { // in case of nested FirstOrDefaults, we need to dig into the inner most - that's where the member finally gets pushed down to if (navigationExpansionExpression.State.PendingSelector.Body is NavigationExpansionExpression navigationExpansionPendingSelector && navigationExpansionPendingSelector.State.PendingCardinalityReducingOperator != null) { var newPendingSelector = (NavigationExpansionExpression)ProcessMemberPushdown(source, navigationExpansionPendingSelector, efProperty, memberInfo, propertyName, resultType); var newStateNested = new NavigationExpansionExpressionState( navigationExpansionExpression.State.CurrentParameter, navigationExpansionExpression.State.SourceMappings, Expression.Lambda(newPendingSelector, navigationExpansionExpression.State.CurrentParameter), applyPendingSelector: true, navigationExpansionExpression.State.PendingOrderings, navigationExpansionExpression.State.PendingIncludeChain, // we need to remap cardinality reducing operator since it's source type has now changed navigationExpansionExpression.State.PendingCardinalityReducingOperator.GetGenericMethodDefinition().MakeGenericMethod(newPendingSelector.Type), navigationExpansionExpression.State.CustomRootMappings, navigationExpansionExpression.State.MaterializeCollectionNavigation); return(new NavigationExpansionExpression( navigationExpansionExpression.Operand, newStateNested, resultType)); } var selectorParameter = Expression.Parameter(source.Type, navigationExpansionExpression.State.CurrentParameter.Name); var selectorBody = efProperty ? (Expression)Expression.Call(EF.PropertyMethod.MakeGenericMethod(resultType), selectorParameter, Expression.Constant(propertyName)) : Expression.MakeMemberAccess(selectorParameter, memberInfo); if (navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.QueryableFirstOrDefaultMethodInfo) || navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.QueryableFirstOrDefaultPredicateMethodInfo) || navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.QueryableSingleOrDefaultMethodInfo) || navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.QueryableSingleOrDefaultPredicateMethodInfo) || navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.EnumerableFirstOrDefaultMethodInfo) || navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.EnumerableFirstOrDefaultPredicateMethodInfo) || navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.EnumerableSingleOrDefaultMethodInfo) || navigationExpansionExpression.State.PendingCardinalityReducingOperator.MethodIsClosedFormOf(LinqMethodHelpers.EnumerableSingleOrDefaultPredicateMethodInfo)) { if (!selectorBody.Type.IsNullableType()) { selectorBody = Expression.Convert(selectorBody, selectorBody.Type.MakeNullable()); } } var selector = Expression.Lambda(selectorBody, selectorParameter); var remappedSelectorBody = ReplacingExpressionVisitor.Replace( selectorParameter, navigationExpansionExpression.State.PendingSelector.Body, selector.Body); var binder = new NavigationPropertyBindingVisitor( navigationExpansionExpression.State.CurrentParameter, navigationExpansionExpression.State.SourceMappings); var boundSelectorBody = binder.Visit(remappedSelectorBody); if (boundSelectorBody is NavigationBindingExpression navigationBindingExpression && navigationBindingExpression.NavigationTreeNode.Navigation != null) { if (navigationBindingExpression.NavigationTreeNode.IsCollection) { var lastNavigation = navigationBindingExpression.NavigationTreeNode.Navigation; var collectionNavigationElementType = lastNavigation.ForeignKey.DeclaringEntityType.ClrType; var entityQueryable = NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(collectionNavigationElementType); var outerParameter = Expression.Parameter(collectionNavigationElementType, collectionNavigationElementType.GenerateParameterName()); var outerKeyAccess = NavigationExpansionHelpers.CreateKeyAccessExpression( outerParameter, lastNavigation.ForeignKey.Properties); var innerParameter = Expression.Parameter(navigationExpansionExpression.Type); var innerKeyAccessLambda = Expression.Lambda( NavigationExpansionHelpers.CreateKeyAccessExpression( innerParameter, lastNavigation.ForeignKey.PrincipalKey.Properties), innerParameter); var combinedKeySelectorBody = ReplacingExpressionVisitor.Replace( innerKeyAccessLambda.Parameters[0], navigationExpansionExpression.State.PendingSelector.Body, innerKeyAccessLambda.Body); if (outerKeyAccess.Type != combinedKeySelectorBody.Type) { if (combinedKeySelectorBody.Type.IsNullableType()) { outerKeyAccess = Expression.Convert(outerKeyAccess, combinedKeySelectorBody.Type); } else { combinedKeySelectorBody = Expression.Convert(combinedKeySelectorBody, outerKeyAccess.Type); } } var rewrittenState = new NavigationExpansionExpressionState( navigationExpansionExpression.State.CurrentParameter, navigationExpansionExpression.State.SourceMappings, Expression.Lambda(combinedKeySelectorBody, navigationExpansionExpression.State.CurrentParameter), applyPendingSelector: true, navigationExpansionExpression.State.PendingOrderings, navigationExpansionExpression.State.PendingIncludeChain, // we need to remap cardinality reducing operator since it's source type has now changed navigationExpansionExpression.State.PendingCardinalityReducingOperator.GetGenericMethodDefinition().MakeGenericMethod(combinedKeySelectorBody.Type), navigationExpansionExpression.State.CustomRootMappings, materializeCollectionNavigation: null); var rewrittenNavigationExpansionExpression = new NavigationExpansionExpression(navigationExpansionExpression.Operand, rewrittenState, combinedKeySelectorBody.Type); var inner = new NavigationExpansionReducingVisitor().Visit(rewrittenNavigationExpansionExpression); var predicate = Expression.Lambda( Expression.Equal(outerKeyAccess, inner), outerParameter); var whereMethodInfo = LinqMethodHelpers.QueryableWhereMethodInfo.MakeGenericMethod(collectionNavigationElementType); var rewritten = Expression.Call( whereMethodInfo, entityQueryable, predicate); var entityType = lastNavigation.ForeignKey.DeclaringEntityType; return(NavigationExpansionHelpers.CreateNavigationExpansionRoot(rewritten, entityType, materializeCollectionNavigation: null)); } else { return(ProcessSelectCore( navigationExpansionExpression.Operand, navigationExpansionExpression.State, selector, selectorBody.Type)); } } var newState = new NavigationExpansionExpressionState( navigationExpansionExpression.State.CurrentParameter, navigationExpansionExpression.State.SourceMappings, Expression.Lambda(boundSelectorBody, navigationExpansionExpression.State.CurrentParameter), applyPendingSelector: true, navigationExpansionExpression.State.PendingOrderings, navigationExpansionExpression.State.PendingIncludeChain, // we need to remap cardinality reducing operator since it's source type has now changed navigationExpansionExpression.State.PendingCardinalityReducingOperator.GetGenericMethodDefinition().MakeGenericMethod(boundSelectorBody.Type), navigationExpansionExpression.State.CustomRootMappings, navigationExpansionExpression.State.MaterializeCollectionNavigation); // TODO: expand navigations var result = new NavigationExpansionExpression( navigationExpansionExpression.Operand, newState, selectorBody.Type); return(resultType != result.Type ? (Expression)Expression.Convert(result, resultType) : result); }
private Expression TryRewriteNavigationComparison(Expression left, Expression right, bool equality) { var leftBinding = left as NavigationBindingExpression; var rightBinding = right as NavigationBindingExpression; var leftNullConstant = left.IsNullConstantExpression(); var rightNullConstant = right.IsNullConstantExpression(); Expression newLeft = null; Expression newRight = null; // comparing two different collection navigations always returns false if (leftBinding != null && rightBinding != null && leftBinding.NavigationTreeNode.Navigation != rightBinding.NavigationTreeNode.Navigation && (leftBinding.NavigationTreeNode.IsCollection || rightBinding.NavigationTreeNode.IsCollection)) { if (leftBinding.NavigationTreeNode.IsCollection) { var parentTreeNode = leftBinding.NavigationTreeNode.Parent; parentTreeNode.Children.Remove(leftBinding.NavigationTreeNode); } if (rightBinding.NavigationTreeNode.IsCollection) { var parentTreeNode = rightBinding.NavigationTreeNode.Parent; parentTreeNode.Children.Remove(rightBinding.NavigationTreeNode); } return(Expression.Constant(false)); } if (leftBinding != null && rightBinding != null && leftBinding.EntityType == rightBinding.EntityType) { if (leftBinding.NavigationTreeNode.Navigation == rightBinding.NavigationTreeNode.Navigation && leftBinding.NavigationTreeNode.IsCollection) { leftBinding = CreateParentBindingExpression(leftBinding); rightBinding = CreateParentBindingExpression(rightBinding); } // TODO: what about entities without PKs? var primaryKeyProperties = leftBinding.EntityType.FindPrimaryKey().Properties; newLeft = NavigationExpansionHelpers.CreateKeyAccessExpression(leftBinding, primaryKeyProperties, addNullCheck: leftBinding.NavigationTreeNode.Optional); newRight = NavigationExpansionHelpers.CreateKeyAccessExpression(rightBinding, primaryKeyProperties, addNullCheck: rightBinding.NavigationTreeNode.Optional); } if (leftBinding != null && rightNullConstant) { if (leftBinding.NavigationTreeNode.IsCollection) { leftBinding = CreateParentBindingExpression(leftBinding); } // TODO: what about entities without PKs? var primaryKeyProperties = leftBinding.EntityType.FindPrimaryKey().Properties; newLeft = NavigationExpansionHelpers.CreateKeyAccessExpression(leftBinding, primaryKeyProperties, addNullCheck: leftBinding.NavigationTreeNode.Optional); newRight = NavigationExpansionHelpers.CreateNullKeyExpression(newLeft.Type, primaryKeyProperties.Count); } if (rightBinding != null && leftNullConstant) { if (rightBinding.NavigationTreeNode.IsCollection) { rightBinding = CreateParentBindingExpression(rightBinding); } // TODO: what about entities without PKs? var primaryKeyProperties = rightBinding.EntityType.FindPrimaryKey().Properties; newRight = NavigationExpansionHelpers.CreateKeyAccessExpression(rightBinding, primaryKeyProperties, addNullCheck: rightBinding.NavigationTreeNode.Optional); newLeft = NavigationExpansionHelpers.CreateNullKeyExpression(newRight.Type, primaryKeyProperties.Count); } if (newLeft == null || newRight == null) { return(null); } if (newLeft.Type != newRight.Type) { if (newLeft.Type.IsNullableType()) { newRight = Expression.Convert(newRight, newLeft.Type); } else { newLeft = Expression.Convert(newLeft, newRight.Type); } } return(equality ? Expression.Equal(newLeft, newRight) : Expression.NotEqual(newLeft, newRight)); }
public static Expression CreateCollectionNavigationExpression( NavigationTreeNode navigationTreeNode, ParameterExpression rootParameter, SourceMapping sourceMapping) { var collectionEntityType = navigationTreeNode.Navigation.ForeignKey.DeclaringEntityType; 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 var newSourceMapping = result.State.SourceMappings.Single(); IncludeHelpers.CopyIncludeInformation(navigationTreeNode, newSourceMapping.NavigationTree, newSourceMapping); return(result); }