using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; using PoweredSoft.Data.Core; using PoweredSoft.DynamicLinq; using PoweredSoft.DynamicQuery.Core; namespace PoweredSoft.DynamicQuery { public class QueryHandlerAsync : QueryHandlerBase, IQueryHandlerAsync { internal MethodInfo ExecuteAsyncGeneric = typeof(QueryHandlerAsync).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).First(t => t.Name == "ExecuteAsync" && t.IsGenericMethod); public IAsyncQueryableService AsyncQueryableService { get; } internal Task ExecuteAsyncReflected(CancellationToken cancellationToken) => (Task)ExecuteAsyncGeneric.MakeGenericMethod(QueryableUnderlyingType).Invoke(this, new object[] { cancellationToken }); public QueryHandlerAsync(IAsyncQueryableService asyncQueryableService) { AsyncQueryableService = asyncQueryableService; } protected virtual Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { CommonBeforeExecute(); return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); } public Task ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) { Reset(queryable, criteria); return ExecuteAsyncReflected(cancellationToken); } protected virtual async Task ExecuteAsyncGrouping(CancellationToken cancellationToken) { var result = new QueryExecutionResult(); // 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.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); // apply sorting and paging. ApplySorting(); ApplyPaging(); // 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. var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast(), cancellationToken); // now join them into logical collections result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First()); 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 = 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; } } }