using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using PoweredSoft.Data; using PoweredSoft.Data.Core; using PoweredSoft.DynamicLinq; using PoweredSoft.DynamicQuery.Core; namespace PoweredSoft.DynamicQuery { public class QueryHandlerAsync : QueryHandlerBase, IQueryHandlerAsync { public IAsyncQueryableService AsyncQueryableService { get; } public QueryHandlerAsync(IAsyncQueryableService asyncQueryableService, IEnumerable queryInterceptorProviders) : base(queryInterceptorProviders) { AsyncQueryableService = asyncQueryableService; } protected virtual Task> FinalExecuteAsync(CancellationToken cancellationToken = default) { CommonBeforeExecute(); return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); } protected virtual async Task> ExecuteAsyncGrouping(CancellationToken cancellationToken) { var result = new QueryExecutionGroupResult(); // preserve queryable. var queryableAfterFilters = CurrentQueryable; // async. result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable)queryableAfterFilters, cancellationToken); CalculatePageCount(result); // intercept groups in advance to avoid doing it more than once :) var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); // get the aggregates. var aggregateResults = await FetchAggregatesAsync(finalGroups, cancellationToken); // sorting. finalGroups.ReversedForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); // apply sorting and paging. ApplySorting(); ApplyPaging(); List groupRecords; if (Options.GroupByInMemory) { CurrentQueryable = CurrentQueryable.ToObjectList().Cast().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().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(), cancellationToken); } // now join them into logical collections var lastLists = new List<(List entities, IGroupQueryResult group)>(); result.Groups = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); // converted to grouped by. await QueryInterceptToGrouped(lastLists); result.Aggregates = await CalculateTotalAggregateAsync(queryableAfterFilters, cancellationToken); return result; } protected async Task> ExecuteAsyncNoGrouping(CancellationToken cancellationToken) { var result = new QueryExecutionResult(); // after filter queryable IQueryable afterFilterQueryable = (IQueryable)CurrentQueryable; // total records. result.TotalRecords = await AsyncQueryableService.LongCountAsync(afterFilterQueryable, cancellationToken); CalculatePageCount(result); // sorts and paging. ApplySorting(); ApplyPaging(); // data. var entities = await AsyncQueryableService.ToListAsync(((IQueryable)CurrentQueryable), cancellationToken); var records = await InterceptConvertTo(entities); result.Data = records; // aggregates. result.Aggregates = await CalculateTotalAggregateAsync(afterFilterQueryable, cancellationToken); return result; } protected virtual async Task> CalculateTotalAggregateAsync(IQueryable queryableAfterFilters, CancellationToken cancellationToken) { if (!Criteria.Aggregates.Any()) return null; IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); var aggregateResult = await AsyncQueryableService.FirstOrDefaultAsync(selectExpression.Cast()); return MaterializeCalculateTotalAggregateResult(aggregateResult); } protected async virtual Task>> FetchAggregatesAsync(List finalGroups, CancellationToken cancellationToken) { if (!Criteria.Aggregates.Any()) return null; var previousGroups = new List(); var whenAllResult = await Task.WhenAll(finalGroups.Select(fg => { IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); var selectExpressionCasted = selectExpression.Cast(); var aggregateResult = AsyncQueryableService.ToListAsync(selectExpressionCasted, cancellationToken); previousGroups.Add(fg); return aggregateResult; })); var finalResult = whenAllResult.ToList(); return finalResult; } public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) { Reset(queryable, criteria, new QueryExecutionOptions()); return FinalExecuteAsync(cancellationToken); } public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) { Reset(queryable, criteria, new QueryExecutionOptions()); return FinalExecuteAsync(cancellationToken); } public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) { Reset(queryable, criteria, options); return FinalExecuteAsync(cancellationToken); } public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) { Reset(queryable, criteria, options); return FinalExecuteAsync(cancellationToken); } } }