protected virtual IQueryExecutionResult <TRecord> ExecuteGrouping <TSource, TRecord>()
        {
            var result = new QueryExecutionGroupResult <TRecord>();

            // preserve queryable.
            var queryableAfterFilters = CurrentQueryable;

            result.TotalRecords = queryableAfterFilters.LongCount();
            CalculatePageCount(result);

            // intercept groups in advance to avoid doing it more than once :)
            var finalGroups = Criteria.Groups.Select(g => InterceptGroup <TSource>(g)).ToList();

            // get the aggregates.
            var aggregateResults = FetchAggregates <TSource>(finalGroups);

            // sorting.
            finalGroups.ReversedForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending)));

            // apply sorting and paging.
            ApplySorting <TSource>();
            ApplyPaging <TSource>();

            if (Options.GroupByInMemory)
            {
                CurrentQueryable = CurrentQueryable.ToObjectList().Cast <TSource>().AsQueryable();
            }

            CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb =>
            {
                gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
                finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"));
            });

            CurrentQueryable = CurrentQueryable.Select(sb =>
            {
                sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
                finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}"));
                sb.ToList("Records");
            });


            // loop through the grouped records.
            var groupRecords = CurrentQueryable.ToDynamicClassList();

            // now join them into logical collections
            var lastLists = new List <(List <TSource> source, IGroupQueryResult <TRecord> group)>();

            result.Groups = RecursiveRegroup <TSource, TRecord>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists);

            // intercept grouped by.
            QueryInterceptToGrouped <TSource, TRecord>(lastLists).Wait();

            result.Aggregates = CalculateTotalAggregate <TSource>(queryableAfterFilters);
            return(result);
        }
        protected virtual async Task <IQueryExecutionResult <TRecord> > ExecuteAsyncGrouping <TSource, TRecord>(CancellationToken cancellationToken)
        {
            var result = new QueryExecutionGroupResult <TRecord>();

            // preserve queryable.
            var queryableAfterFilters = CurrentQueryable;

            // async.
            result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable <TSource>) queryableAfterFilters, cancellationToken);

            CalculatePageCount(result);

            // intercept groups in advance to avoid doing it more than once :)
            var finalGroups = Criteria.Groups.Select(g => InterceptGroup <TSource>(g)).ToList();

            // get the aggregates.
            var aggregateResults = await FetchAggregatesAsync <TSource>(finalGroups, cancellationToken);

            // sorting.
            finalGroups.ReversedForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending)));

            // apply sorting and paging.
            ApplySorting <TSource>();
            ApplyPaging <TSource>();

            List <DynamicClass> groupRecords;

            if (Options.GroupByInMemory)
            {
                CurrentQueryable = CurrentQueryable.ToObjectList().Cast <TSource>().AsQueryable();

                // create group & select expression.
                CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb =>
                {
                    gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
                    finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"));
                });
                CurrentQueryable = CurrentQueryable.Select(sb =>
                {
                    sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
                    finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}"));
                    sb.ToList("Records");
                });

                // loop through the grouped records.
                groupRecords = CurrentQueryable.Cast <DynamicClass>().ToList();
            }
            else
            {
                // create group & select expression.
                CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb =>
                {
                    finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"));
                });
                CurrentQueryable = CurrentQueryable.Select(sb =>
                {
                    finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}"));
                    sb.ToList("Records");
                });

                // loop through the grouped records.
                groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast <DynamicClass>(), cancellationToken);
            }

            // now join them into logical collections
            var lastLists = new List <(List <TSource> entities, IGroupQueryResult <TRecord> group)>();

            result.Groups = RecursiveRegroup <TSource, TRecord>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists);

            // converted to grouped by.
            await QueryInterceptToGrouped <TSource, TRecord>(lastLists);

            result.Aggregates = await CalculateTotalAggregateAsync <TSource>(queryableAfterFilters, cancellationToken);

            return(result);
        }