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);
        }
 public NavigationExpansionExpression(
     Expression operand,
     NavigationExpansionExpressionState state,
     Type returnType)
 {
     Operand     = operand;
     State       = state;
     _returnType = returnType;
 }
        private (Expression Operand, NavigationExpansionExpressionState State) ApplyIncludes(
            NavigationExpansionExpression navigationExpansionExpression)
        {
            var includeVisitor = new PendingSelectorIncludeVisitor();
            var rewrittenBody  = includeVisitor.Visit(navigationExpansionExpression.State.PendingSelector.Body);

            if (navigationExpansionExpression.State.PendingSelector.Body != rewrittenBody)
            {
                navigationExpansionExpression.State.PendingSelector      = Expression.Lambda(rewrittenBody, navigationExpansionExpression.State.PendingSelector.Parameters[0]);
                navigationExpansionExpression.State.ApplyPendingSelector = true;
            }

            if (includeVisitor.PendingIncludes.Count > 0)
            {
                var result = (Source : navigationExpansionExpression.Operand, Parameter : navigationExpansionExpression.State.CurrentParameter);
                foreach (var pendingIncludeNode in includeVisitor.PendingIncludes)
                {
                    result = NavigationExpansionHelpers.AddNavigationJoin(
                        result.Source,
                        result.Parameter,
                        pendingIncludeNode.SourceMapping,
                        pendingIncludeNode.NavTreeNode,
                        navigationExpansionExpression.State,
                        new List <INavigation>(),
                        include: true);
                }

                var pendingSelector = navigationExpansionExpression.State.PendingSelector;
                if (navigationExpansionExpression.State.CurrentParameter != result.Parameter)
                {
                    var pendingSelectorBody = new ExpressionReplacingVisitor(navigationExpansionExpression.State.CurrentParameter, result.Parameter).Visit(navigationExpansionExpression.State.PendingSelector.Body);
                    pendingSelector = Expression.Lambda(pendingSelectorBody, result.Parameter);
                }

                var newState = new NavigationExpansionExpressionState(
                    result.Parameter,
                    navigationExpansionExpression.State.SourceMappings,
                    pendingSelector,
                    applyPendingSelector: true,
                    navigationExpansionExpression.State.PendingOrderings,
                    navigationExpansionExpression.State.PendingIncludeChain,
                    navigationExpansionExpression.State.PendingCardinalityReducingOperator,
                    navigationExpansionExpression.State.CustomRootMappings,
                    navigationExpansionExpression.State.MaterializeCollectionNavigation);

                return(Operand : result.Source, newState);
            }

            return(navigationExpansionExpression.Operand, navigationExpansionExpression.State);
        }
        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);
        }
        public static (Expression Source, ParameterExpression Parameter) AddNavigationJoin(
            Expression sourceExpression,
            ParameterExpression parameterExpression,
            SourceMapping sourceMapping,
            NavigationTreeNode navigationTree,
            NavigationExpansionExpressionState state,
            List <INavigation> navigationPath,
            bool include)
        {
            var joinNeeded = include
                ? navigationTree.IncludeState == NavigationState.Pending
                : navigationTree.ExpansionState == NavigationState.Pending;

            if (joinNeeded)
            {
                var navigation = navigationTree.Navigation;
                var sourceType = sourceExpression.Type.GetSequenceType();
                var navigationTargetEntityType = navigation.GetTargetType();

                var entityQueryable = NullAsyncQueryProvider.Instance.CreateEntityQueryableExpression(navigationTargetEntityType.ClrType);
                var resultType      = typeof(TransparentIdentifier <,>).MakeGenericType(sourceType, navigationTargetEntityType.ClrType);

                var outerParameter            = Expression.Parameter(sourceType, parameterExpression.Name);
                var outerKeySelectorParameter = outerParameter;
                var transparentIdentifierAccessorExpression = outerParameter.BuildPropertyAccess(navigationTree.Parent.ToMapping);

                var outerKeySelectorBody = CreateKeyAccessExpression(
                    transparentIdentifierAccessorExpression,
                    navigation.IsDependentToPrincipal()
                        ? navigation.ForeignKey.Properties
                        : navigation.ForeignKey.PrincipalKey.Properties,
                    addNullCheck: navigationTree.Parent != null && navigationTree.Parent.Optional);

                var innerKeySelectorParameterType = navigationTargetEntityType.ClrType;
                var innerKeySelectorParameter     = Expression.Parameter(
                    innerKeySelectorParameterType,
                    parameterExpression.Name + "." + navigationTree.Navigation.Name);

                var innerKeySelectorBody = CreateKeyAccessExpression(
                    innerKeySelectorParameter,
                    navigation.IsDependentToPrincipal()
                        ? navigation.ForeignKey.PrincipalKey.Properties
                        : navigation.ForeignKey.Properties);

                if (outerKeySelectorBody.Type.IsNullableType() &&
                    !innerKeySelectorBody.Type.IsNullableType())
                {
                    innerKeySelectorBody = Expression.Convert(innerKeySelectorBody, outerKeySelectorBody.Type);
                }
                else if (innerKeySelectorBody.Type.IsNullableType() &&
                         !outerKeySelectorBody.Type.IsNullableType())
                {
                    outerKeySelectorBody = Expression.Convert(outerKeySelectorBody, innerKeySelectorBody.Type);
                }

                var outerKeySelector = Expression.Lambda(
                    outerKeySelectorBody,
                    outerKeySelectorParameter);

                var innerKeySelector = Expression.Lambda(
                    innerKeySelectorBody,
                    innerKeySelectorParameter);

                if (!sourceExpression.Type.IsQueryableType())
                {
                    var asQueryableMethodInfo = LinqMethodHelpers.AsQueryable.MakeGenericMethod(sourceType);
                    sourceExpression = Expression.Call(asQueryableMethodInfo, sourceExpression);
                }

                var joinMethodInfo = navigationTree.Optional
                    ? _leftJoinMethodInfo.MakeGenericMethod(
                    sourceType,
                    navigationTargetEntityType.ClrType,
                    outerKeySelector.Body.Type,
                    resultType)
                    : LinqMethodHelpers.QueryableJoinMethodInfo.MakeGenericMethod(
                    sourceType,
                    navigationTargetEntityType.ClrType,
                    outerKeySelector.Body.Type,
                    resultType);

                var resultSelectorOuterParameterName = outerParameter.Name;
                var resultSelectorOuterParameter     = Expression.Parameter(sourceType, resultSelectorOuterParameterName);

                var resultSelectorInnerParameterName = innerKeySelectorParameter.Name;
                var resultSelectorInnerParameter     = Expression.Parameter(navigationTargetEntityType.ClrType, resultSelectorInnerParameterName);

                var transparentIdentifierCtorInfo
                    = resultType.GetTypeInfo().GetConstructors().Single();

                var transparentIdentifierOuterMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Outer");
                var transparentIdentifierInnerMemberInfo = resultType.GetTypeInfo().GetDeclaredField("Inner");

                var resultSelector = Expression.Lambda(
                    Expression.New(
                        transparentIdentifierCtorInfo,
                        new[] { resultSelectorOuterParameter, resultSelectorInnerParameter },
                        new[] { transparentIdentifierOuterMemberInfo, transparentIdentifierInnerMemberInfo }),
                    resultSelectorOuterParameter,
                    resultSelectorInnerParameter);

                var joinMethodCall = Expression.Call(
                    joinMethodInfo,
                    sourceExpression,
                    entityQueryable,
                    outerKeySelector,
                    innerKeySelector,
                    resultSelector);

                sourceExpression = joinMethodCall;

                var transparentIdentifierParameterName = resultSelectorInnerParameterName;
                var transparentIdentifierParameter     = Expression.Parameter(resultSelector.ReturnType, transparentIdentifierParameterName);
                parameterExpression = transparentIdentifierParameter;

                // remap navigation 'To' paths -> for this navigation prepend "Inner", for every other (already expanded) navigation prepend "Outer"
                navigationTree.ToMapping.Insert(0, nameof(TransparentIdentifier <object, object> .Inner));
                foreach (var mapping in state.SourceMappings)
                {
                    var nodes = include
                        ? mapping.NavigationTree.Flatten().Where(n => (n.IncludeState == NavigationState.Complete ||
                                                                       n.ExpansionState == NavigationState.Complete ||
                                                                       n.Navigation.ForeignKey.IsOwnership) &&
                                                                 n != navigationTree)
                        : mapping.NavigationTree.Flatten().Where(n => (n.ExpansionState == NavigationState.Complete ||
                                                                       n.Navigation.ForeignKey.IsOwnership) &&
                                                                 n != navigationTree);

                    foreach (var navigationTreeNode in nodes)
                    {
                        navigationTreeNode.ToMapping.Insert(0, nameof(TransparentIdentifier <object, object> .Outer));
                    }
                }

                foreach (var customRootMapping in state.CustomRootMappings)
                {
                    customRootMapping.Insert(0, nameof(TransparentIdentifier <object, object> .Outer));
                }

                if (include)
                {
                    navigationTree.IncludeState = NavigationState.Complete;
                }
                else
                {
                    navigationTree.ExpansionState = NavigationState.Complete;
                }

                navigationPath.Add(navigation);
            }
            else
            {
                navigationPath.Add(navigationTree.Navigation);
            }

            var result = (Source : sourceExpression, Parameter : parameterExpression);

            foreach (var child in navigationTree.Children.Where(n => !n.IsCollection))
            {
                result = AddNavigationJoin(
                    result.Source,
                    result.Parameter,
                    sourceMapping,
                    child,
                    state,
                    navigationPath.ToList(),
                    include);
            }

            return(result);
        }
 public virtual NavigationExpansionExpression Update(Expression operand, NavigationExpansionExpressionState state)
 => operand != Operand || state != State
     ? new NavigationExpansionExpression(operand, state, Type)
     : this;