public static NavigationTreeNode CreateRoot(
            [NotNull] SourceMapping sourceMapping,
            [NotNull] List <string> fromMapping,
            bool optional)
        {
            Check.NotNull(sourceMapping, nameof(sourceMapping));
            Check.NotNull(fromMapping, nameof(fromMapping));

            return(sourceMapping.NavigationTree ?? new NavigationTreeNode(fromMapping, optional));
        }
 public NavigationBindingExpression(
     ParameterExpression rootParameter,
     NavigationTreeNode navigationTreeNode,
     IEntityType entityType,
     SourceMapping sourceMapping,
     Type type)
 {
     RootParameter      = rootParameter;
     NavigationTreeNode = navigationTreeNode;
     EntityType         = entityType;
     SourceMapping      = sourceMapping;
     Type = type;
 }
        public static NavigationTreeNode Create(
            [NotNull] SourceMapping sourceMapping,
            [NotNull] INavigation navigation,
            [NotNull] NavigationTreeNode parent,
            bool include)
        {
            Check.NotNull(sourceMapping, nameof(sourceMapping));
            Check.NotNull(navigation, nameof(navigation));
            Check.NotNull(parent, nameof(parent));

            var existingChild = parent.Children.SingleOrDefault(c => c.Navigation == navigation);

            if (existingChild != null)
            {
                if (navigation.ForeignKey.IsOwnership)
                {
                    if (include && existingChild.IncludeState == NavigationState.NotNeeded)
                    {
                        existingChild.IncludeState = NavigationState.Delayed;
                    }
                    else if (!include && existingChild.ExpansionState == NavigationState.NotNeeded)
                    {
                        existingChild.ExpansionState = NavigationState.Delayed;
                    }
                }
                else
                {
                    if (include && existingChild.IncludeState == NavigationState.NotNeeded)
                    {
                        existingChild.IncludeState = NavigationState.Pending;
                    }
                    else if (!include && existingChild.ExpansionState == NavigationState.NotNeeded)
                    {
                        existingChild.ExpansionState = NavigationState.Pending;
                    }
                }
                return(existingChild);
            }

            // if (any) parent is optional, all children must be optional also
            // TODO: what about query filters?
            var optional = parent.Optional || !navigation.ForeignKey.IsRequired || !navigation.IsDependentToPrincipal();
            var result   = new NavigationTreeNode(navigation, parent, optional, include);

            parent.Children.Add(result);

            return(result);
        }
        public static NavigationExpansionExpression CreateNavigationExpansionRoot(
            Expression operand,
            IEntityType entityType,
            INavigation materializeCollectionNavigation)
        {
            var sourceMapping = new SourceMapping
            {
                RootEntityType = entityType,
            };

            var navigationTreeRoot = NavigationTreeNode.CreateRoot(sourceMapping, fromMapping: new List <string>(), optional: false);

            sourceMapping.NavigationTree = navigationTreeRoot;

            var pendingSelectorParameter = Expression.Parameter(entityType.ClrType);
            var pendingSelector          = Expression.Lambda(
                new NavigationBindingExpression(
                    pendingSelectorParameter,
                    navigationTreeRoot,
                    entityType,
                    sourceMapping,
                    pendingSelectorParameter.Type),
                pendingSelectorParameter);

            return(new NavigationExpansionExpression(
                       operand,
                       new NavigationExpansionExpressionState(
                           pendingSelectorParameter,
                           new List <SourceMapping> {
                sourceMapping
            },
                           pendingSelector,
                           applyPendingSelector: false,
                           new List <(MethodInfo method, LambdaExpression keySelector)>(),
                           pendingIncludeChain: null,
                           pendingCardinalityReducingOperator: null,
                           customRootMappings: new List <List <string> >(),
                           materializeCollectionNavigation),
                       materializeCollectionNavigation?.ClrType ?? operand.Type));
        }
 public static void CopyIncludeInformation(NavigationTreeNode originalNavigationTree, NavigationTreeNode newNavigationTree, SourceMapping newSourceMapping)
 {
     foreach (var child in originalNavigationTree.Children.Where(n => n.IncludeState == NavigationState.Pending))
     {
         var copy = NavigationTreeNode.Create(newSourceMapping, child.Navigation, newNavigationTree, true);
         CopyIncludeInformation(child, copy, newSourceMapping);
     }
 }
        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 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);
        }