/// <summary>
        ///     Creates a group join include used to describe an Include operation that should
        ///     be performed as part of a GroupJoin.
        /// </summary>
        /// <param name="navigationPath"> The included navigation path. </param>
        /// <param name="querySourceRequiresTracking"> true if this query source requires tracking. </param>
        /// <param name="existingGroupJoinInclude"> A possibly null existing group join include. </param>
        /// <param name="relatedEntitiesLoaders"> The related entities loaders. </param>
        /// <returns>
        ///     A new group join include.
        /// </returns>
        public virtual object CreateGroupJoinInclude(
            IReadOnlyList <INavigation> navigationPath,
            bool querySourceRequiresTracking,
            object existingGroupJoinInclude,
            object relatedEntitiesLoaders)
        {
            var previousGroupJoinInclude
                = new GroupJoinInclude(
                      navigationPath,
                      (IReadOnlyList <Func <QueryContext, IRelatedEntitiesLoader> >)relatedEntitiesLoaders,
                      querySourceRequiresTracking);

            var groupJoinInclude = existingGroupJoinInclude as GroupJoinInclude;

            if (groupJoinInclude != null)
            {
                groupJoinInclude.SetPrevious(previousGroupJoinInclude);

                return(null);
            }

            return(previousGroupJoinInclude);
        }
        // ReSharper disable once InconsistentNaming
        private static IEnumerable <TResult> _GroupJoin <TOuter, TInner, TKey, TResult>(
            RelationalQueryContext queryContext,
            IEnumerable <ValueBuffer> source,
            IShaper <TOuter> outerShaper,
            IShaper <TInner> innerShaper,
            Func <TInner, TKey> innerKeySelector,
            Func <TOuter, IEnumerable <TInner>, TResult> resultSelector,
            GroupJoinInclude outerGroupJoinInclude,
            GroupJoinInclude innerGroupJoinInclude)
        {
            var outerGroupJoinIncludeContext = outerGroupJoinInclude?.CreateIncludeContext(queryContext);
            var innerGroupJoinIncludeContext = innerGroupJoinInclude?.CreateIncludeContext(queryContext);

            try
            {
                using (var sourceEnumerator = source.GetEnumerator())
                {
                    var comparer  = EqualityComparer <TKey> .Default;
                    var hasNext   = sourceEnumerator.MoveNext();
                    var nextOuter = default(TOuter);

                    while (hasNext)
                    {
                        var outer
                            = Equals(nextOuter, default(TOuter))
                                ? outerShaper.Shape(queryContext, sourceEnumerator.Current)
                                : nextOuter;

                        nextOuter = default(TOuter);

                        outerGroupJoinIncludeContext?.Include(outer);

                        var inner  = innerShaper.Shape(queryContext, sourceEnumerator.Current);
                        var inners = new List <TInner>();

                        if (inner == null)
                        {
                            yield return(resultSelector(outer, inners));

                            hasNext = sourceEnumerator.MoveNext();
                        }
                        else
                        {
                            var currentGroupKey = innerKeySelector(inner);

                            innerGroupJoinIncludeContext?.Include(inner);

                            inners.Add(inner);

                            while (true)
                            {
                                hasNext = sourceEnumerator.MoveNext();

                                if (!hasNext)
                                {
                                    break;
                                }

                                nextOuter = outerShaper.Shape(queryContext, sourceEnumerator.Current);

                                if (!Equals(outer, nextOuter))
                                {
                                    break;
                                }

                                nextOuter = default(TOuter);

                                inner = innerShaper.Shape(queryContext, sourceEnumerator.Current);

                                if (inner == null)
                                {
                                    break;
                                }

                                var innerKey = innerKeySelector(inner);

                                if (!comparer.Equals(currentGroupKey, innerKey))
                                {
                                    break;
                                }

                                innerGroupJoinIncludeContext?.Include(inner);

                                inners.Add(inner);
                            }

                            yield return(resultSelector(outer, inners));
                        }
                    }
                }
            }
            finally
            {
                innerGroupJoinIncludeContext?.Dispose();
                outerGroupJoinIncludeContext?.Dispose();
            }
        }