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