From 9e6dc08e535bb1e83d7cf8268c1494620ebdc05c Mon Sep 17 00:00:00 2001 From: David Lebee Date: Tue, 19 Mar 2019 22:54:15 -0500 Subject: [PATCH] strongly typed returned interfaces, next is the async mode. --- .../IQueryHandler.cs | 7 +- PoweredSoft.DynamicQuery.Core/IQueryResult.cs | 22 +- .../AggregateTests.cs | 6 +- PoweredSoft.DynamicQuery.Test/AsyncTests.cs | 254 +++++++++--------- .../ConvertibleInterceptorTests.cs | 4 +- .../GroupInterceptorTests.cs | 5 +- PoweredSoft.DynamicQuery.Test/GroupTests.cs | 16 +- .../Extensions/GroupResultExtensions.cs | 18 ++ PoweredSoft.DynamicQuery/QueryHandler.cs | 64 ++--- PoweredSoft.DynamicQuery/QueryHandlerAsync.cs | 3 +- PoweredSoft.DynamicQuery/QueryHandlerBase.cs | 160 +++++------ PoweredSoft.DynamicQuery/QueryResult.cs | 15 +- .../ServiceCollectionExtensions.cs | 2 +- 13 files changed, 316 insertions(+), 260 deletions(-) create mode 100644 PoweredSoft.DynamicQuery/Extensions/GroupResultExtensions.cs diff --git a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs index cdf5a43..4ce5ab2 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs @@ -13,11 +13,14 @@ namespace PoweredSoft.DynamicQuery.Core public interface IQueryHandler : IInterceptableQueryHandler { - IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); + IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); + IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); } public interface IQueryHandlerAsync : IInterceptableQueryHandler { - Task ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); + Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); + Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); + Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); } } diff --git a/PoweredSoft.DynamicQuery.Core/IQueryResult.cs b/PoweredSoft.DynamicQuery.Core/IQueryResult.cs index d42fceb..aa2fa75 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryResult.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryResult.cs @@ -11,23 +11,35 @@ namespace PoweredSoft.DynamicQuery.Core object Value { get; set; } } - public interface IQueryResult + public interface IQueryResult { List Aggregates { get; } - List Data { get; } + List Data { get; } } - public interface IGroupQueryResult : IQueryResult + public interface IGroupQueryResult : IQueryResult { string GroupPath { get; set; } object GroupValue { get; set; } + bool HasSubGroups { get; set; } + List> SubGroups { get; set; } } - public interface IQueryExecutionResult : IQueryResult + public interface IQueryExecutionResultPaging { long TotalRecords { get; set; } long? NumberOfPages { get; set; } } - + public interface IQueryExecutionResult : IQueryResult, IQueryExecutionResultPaging + { + + } + + public interface IQueryExecutionGroupResult : IQueryExecutionResult + { + List> Groups { get; set; } + } + + } diff --git a/PoweredSoft.DynamicQuery.Test/AggregateTests.cs b/PoweredSoft.DynamicQuery.Test/AggregateTests.cs index cee7f3c..cb36f65 100644 --- a/PoweredSoft.DynamicQuery.Test/AggregateTests.cs +++ b/PoweredSoft.DynamicQuery.Test/AggregateTests.cs @@ -114,7 +114,11 @@ namespace PoweredSoft.DynamicQuery.Test var queryHandler = new QueryHandler(); var result = queryHandler.Execute(ctx.OrderItems, criteria); - var groups = result.Data.Cast().ToList(); + + var groupedResult = result as IQueryExecutionGroupResult; + Assert.NotNull(groupedResult); + + var groups = groupedResult.Groups; // validate group and aggregates of groups. Assert.Equal(groups.Count, shouldResults.Count); diff --git a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs index 26ad8b9..e1915f9 100644 --- a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs +++ b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs @@ -1,141 +1,141 @@ -using PoweredSoft.Data; -using PoweredSoft.Data.EntityFrameworkCore; -using PoweredSoft.DynamicQuery.Core; -using PoweredSoft.DynamicQuery.Test.Mock; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using Xunit; +//using PoweredSoft.Data; +//using PoweredSoft.Data.EntityFrameworkCore; +//using PoweredSoft.DynamicQuery.Core; +//using PoweredSoft.DynamicQuery.Test.Mock; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; +//using Xunit; -namespace PoweredSoft.DynamicQuery.Test -{ - public class AsyncTests - { - [Fact] - public void TestEmptyCriteria() - { - MockContextFactory.SeedAndTestContextFor("AsyncTests_TestEmptyCriteria", TestSeeders.SimpleSeedScenario, async ctx => - { - var resultShouldMatch = ctx.Items.ToList(); - var queryable = ctx.Items.AsQueryable(); +//namespace PoweredSoft.DynamicQuery.Test +//{ +// public class AsyncTests +// { +// [Fact] +// public void TestEmptyCriteria() +// { +// MockContextFactory.SeedAndTestContextFor("AsyncTests_TestEmptyCriteria", TestSeeders.SimpleSeedScenario, async ctx => +// { +// var resultShouldMatch = ctx.Items.ToList(); +// var queryable = ctx.Items.AsQueryable(); - // query handler that is empty should be the same as running to list. - var aqf = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); - var criteria = new QueryCriteria(); - var queryHandler = new QueryHandlerAsync(aqf); - var result = await queryHandler.ExecuteAsync(queryable, criteria); - Assert.Equal(resultShouldMatch, result.Data); - }); - } +// // query handler that is empty should be the same as running to list. +// var aqf = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); +// var criteria = new QueryCriteria(); +// var queryHandler = new QueryHandlerAsync(aqf); +// var result = await queryHandler.ExecuteAsync(queryable, criteria); +// Assert.Equal(resultShouldMatch, result.Data); +// }); +// } - [Fact] - public void WithGrouping() - { - MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGrouping", TestSeeders.SimpleSeedScenario, async ctx => - { - var shouldResults = ctx.OrderItems - .GroupBy(t => t.Order.CustomerId) - .Select(t => new - { - GroupValue = t.Key, - Count = t.Count(), - ItemQuantityAverage = t.Average(t2 => t2.Quantity), - ItemQuantitySum = t.Sum(t2 => t2.Quantity), - AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime) - }) - .ToList(); +// [Fact] +// public void WithGrouping() +// { +// MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGrouping", TestSeeders.SimpleSeedScenario, async ctx => +// { +// var shouldResults = ctx.OrderItems +// .GroupBy(t => t.Order.CustomerId) +// .Select(t => new +// { +// GroupValue = t.Key, +// Count = t.Count(), +// ItemQuantityAverage = t.Average(t2 => t2.Quantity), +// ItemQuantitySum = t.Sum(t2 => t2.Quantity), +// AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime) +// }) +// .ToList(); - // query handler that is empty should be the same as running to list. - var criteria = new QueryCriteria() - { - Groups = new List - { - new Group { Path = "Order.CustomerId" } - }, - Aggregates = new List - { - new Aggregate { Type = AggregateType.Count }, - new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, - new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, - new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"} - } - }; - var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); - var queryHandler = new QueryHandlerAsync(asyncService); - var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); - var groups = result.Data.Cast().ToList(); +// // query handler that is empty should be the same as running to list. +// var criteria = new QueryCriteria() +// { +// Groups = new List +// { +// new Group { Path = "Order.CustomerId" } +// }, +// Aggregates = new List +// { +// new Aggregate { Type = AggregateType.Count }, +// new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, +// new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, +// new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"} +// } +// }; +// var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); +// var queryHandler = new QueryHandlerAsync(asyncService); +// var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); +// var groups = result.Data.Cast().ToList(); - // validate group and aggregates of groups. - Assert.Equal(groups.Count, shouldResults.Count); - Assert.All(groups, g => - { - var index = groups.IndexOf(g); - var shouldResult = shouldResults[index]; +// // validate group and aggregates of groups. +// Assert.Equal(groups.Count, shouldResults.Count); +// Assert.All(groups, g => +// { +// var index = groups.IndexOf(g); +// var shouldResult = shouldResults[index]; - // validate the group value. - Assert.Equal(g.GroupValue, shouldResult.GroupValue); +// // validate the group value. +// Assert.Equal(g.GroupValue, shouldResult.GroupValue); - // validate the group aggregates. - var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count); - var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); - var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); - var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); - Assert.Equal(shouldResult.Count, aggCount.Value); - Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); - Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); - Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); - }); - }); - } +// // validate the group aggregates. +// var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count); +// var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); +// var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); +// var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); +// Assert.Equal(shouldResult.Count, aggCount.Value); +// Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); +// Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); +// Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); +// }); +// }); +// } - [Fact] - public void SimpleFilter() - { - MockContextFactory.SeedAndTestContextFor("AsyncTests_SimpleFilter", TestSeeders.SimpleSeedScenario, async ctx => - { - var resultShouldMatch = ctx.Items.Where(t => t.Name.EndsWith("Cables")).ToList(); +// [Fact] +// public void SimpleFilter() +// { +// MockContextFactory.SeedAndTestContextFor("AsyncTests_SimpleFilter", TestSeeders.SimpleSeedScenario, async ctx => +// { +// var resultShouldMatch = ctx.Items.Where(t => t.Name.EndsWith("Cables")).ToList(); - var criteria = new QueryCriteria() - { - Filters = new List - { - new SimpleFilter - { - Path = "Name", - Type = FilterType.EndsWith, - Value = "Cables" - } - } - }; +// var criteria = new QueryCriteria() +// { +// Filters = new List +// { +// new SimpleFilter +// { +// Path = "Name", +// Type = FilterType.EndsWith, +// Value = "Cables" +// } +// } +// }; - var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); - var queryHandler = new QueryHandlerAsync(asyncService); - var result = await queryHandler.ExecuteAsync(ctx.Items, criteria); - Assert.Equal(resultShouldMatch, result.Data); - }); - } +// var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); +// var queryHandler = new QueryHandlerAsync(asyncService); +// var result = await queryHandler.ExecuteAsync(ctx.Items, criteria); +// Assert.Equal(resultShouldMatch, result.Data); +// }); +// } - [Fact] - public void TestPaging() - { - MockContextFactory.SeedAndTestContextFor("AsyncTests_TestPagging", TestSeeders.SimpleSeedScenario, async ctx => - { - var resultShouldMatch = ctx.OrderItems.OrderBy(t => t.Id).Skip(5).Take(5).ToList(); +// [Fact] +// public void TestPaging() +// { +// MockContextFactory.SeedAndTestContextFor("AsyncTests_TestPagging", TestSeeders.SimpleSeedScenario, async ctx => +// { +// var resultShouldMatch = ctx.OrderItems.OrderBy(t => t.Id).Skip(5).Take(5).ToList(); - // query handler that is empty should be the same as running to list. - var criteria = new QueryCriteria(); - criteria.Sorts.Add(new Sort("Id")); - criteria.Page = 2; - criteria.PageSize = 5; +// // query handler that is empty should be the same as running to list. +// var criteria = new QueryCriteria(); +// criteria.Sorts.Add(new Sort("Id")); +// criteria.Page = 2; +// criteria.PageSize = 5; - var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); - var queryHandler = new QueryHandlerAsync(asyncService); - var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); - Assert.Equal(resultShouldMatch, result.Data); - }); - } - } +// var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); +// var queryHandler = new QueryHandlerAsync(asyncService); +// var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); +// Assert.Equal(resultShouldMatch, result.Data); +// }); +// } +// } -} +//} diff --git a/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs b/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs index d43990d..002345a 100644 --- a/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs +++ b/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs @@ -63,7 +63,7 @@ namespace PoweredSoft.DynamicQuery.Test var criteria = new QueryCriteria(); var queryHandler = new QueryHandler(); queryHandler.AddInterceptor(new MockQueryConvertInterceptor()); - var result = queryHandler.Execute(ctx.Customers, criteria); + var result = queryHandler.Execute(ctx.Customers, criteria); Assert.All(result.Data, t => Assert.IsType(t)); }); } @@ -76,7 +76,7 @@ namespace PoweredSoft.DynamicQuery.Test var criteria = new QueryCriteria(); var queryHandler = new QueryHandler(); queryHandler.AddInterceptor(new MockQueryConvertGenericInterceptor()); - var result = queryHandler.Execute(ctx.Customers, criteria); + var result = queryHandler.Execute(ctx.Customers, criteria); Assert.All(result.Data, t => Assert.IsType(t)); }); } diff --git a/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs b/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs index 8a6a111..42e3d06 100644 --- a/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs +++ b/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs @@ -1,4 +1,5 @@ using PoweredSoft.DynamicQuery.Core; +using PoweredSoft.DynamicQuery.Extensions; using PoweredSoft.DynamicQuery.Test.Mock; using System; using System.Collections.Generic; @@ -37,7 +38,9 @@ namespace PoweredSoft.DynamicQuery.Test var queryHandler = new QueryHandler(); queryHandler.AddInterceptor(new MockGroupInterceptor()); var result = queryHandler.Execute(ctx.Orders, criteria); - var actual = result.Data.Cast().Select(t => t.GroupValue).ToList(); + + var groupedResult = result.GroupedResult(); + var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList(); Assert.Equal(expected, actual); }); } diff --git a/PoweredSoft.DynamicQuery.Test/GroupTests.cs b/PoweredSoft.DynamicQuery.Test/GroupTests.cs index 05efd1b..5f9c3b2 100644 --- a/PoweredSoft.DynamicQuery.Test/GroupTests.cs +++ b/PoweredSoft.DynamicQuery.Test/GroupTests.cs @@ -1,4 +1,5 @@ using PoweredSoft.DynamicQuery.Core; +using PoweredSoft.DynamicQuery.Extensions; using PoweredSoft.DynamicQuery.Test.Mock; using System; using System.Collections.Generic; @@ -34,13 +35,14 @@ namespace PoweredSoft.DynamicQuery.Test var queryHandler = new QueryHandler(); var result = queryHandler.Execute(ctx.Orders, criteria); + var groupedResult = result.GroupedResult(); // top level should have same amount of group levels. - Assert.Equal(result.Data.Count, shouldResult.Count); + Assert.Equal(groupedResult.Groups.Count, shouldResult.Count); for (var i = 0; i < shouldResult.Count; i++) { var expected = shouldResult[0]; - var actual = ((IGroupQueryResult)result.Data[0]); + var actual = groupedResult.Groups[0]; Assert.Equal(expected.Customer.Id, (actual.GroupValue as Customer).Id); var expectedOrderIds = expected.Orders.Select(t => t.Id).ToList(); @@ -71,13 +73,15 @@ namespace PoweredSoft.DynamicQuery.Test var queryHandler = new QueryHandler(); var result = queryHandler.Execute(ctx.Tickets, criteria); - var firstGroup = result.Data[0] as IGroupQueryResult; + var groupedResult = result.GroupedResult(); + + var firstGroup = groupedResult.Groups.FirstOrDefault(); Assert.NotNull(firstGroup); - var secondGroup = result.Data[1] as IGroupQueryResult; + var secondGroup = groupedResult.Groups.Skip(1).FirstOrDefault(); Assert.NotNull(secondGroup); var expected = ctx.Tickets.Select(t => t.TicketType).Distinct().Count(); - var c = result.Data.Cast().Select(t => t.GroupValue).Count(); + var c = groupedResult.Groups.Select(t => t.GroupValue).Count(); Assert.Equal(expected, c); }); } @@ -102,7 +106,7 @@ namespace PoweredSoft.DynamicQuery.Test var interceptor = new InterceptorsWithGrouping(); var queryHandler = new QueryHandler(); queryHandler.AddInterceptor(interceptor); - var result = queryHandler.Execute(ctx.Tickets, criteria); + var result = queryHandler.Execute(ctx.Tickets, criteria); Assert.Equal(4, interceptor.Count); Assert.True(interceptor.Test); Assert.True(interceptor.Test2); diff --git a/PoweredSoft.DynamicQuery/Extensions/GroupResultExtensions.cs b/PoweredSoft.DynamicQuery/Extensions/GroupResultExtensions.cs new file mode 100644 index 0000000..6dcdb39 --- /dev/null +++ b/PoweredSoft.DynamicQuery/Extensions/GroupResultExtensions.cs @@ -0,0 +1,18 @@ +using PoweredSoft.DynamicQuery.Core; +using System; +using System.Collections.Generic; +using System.Text; + +namespace PoweredSoft.DynamicQuery.Extensions +{ + public static class GroupResultExtensions + { + public static IQueryExecutionGroupResult GroupedResult(this IQueryExecutionResult source) + { + if (source is IQueryExecutionGroupResult ret) + return ret; + + throw new Exception("this result is not a grouped result"); + } + } +} diff --git a/PoweredSoft.DynamicQuery/QueryHandler.cs b/PoweredSoft.DynamicQuery/QueryHandler.cs index 1e2a5c0..ecccdb6 100644 --- a/PoweredSoft.DynamicQuery/QueryHandler.cs +++ b/PoweredSoft.DynamicQuery/QueryHandler.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; +using System.Threading; using System.Threading.Tasks; using PoweredSoft.DynamicLinq; using PoweredSoft.DynamicLinq.Fluent; @@ -13,19 +14,15 @@ namespace PoweredSoft.DynamicQuery { public class QueryHandler : QueryHandlerBase, IQueryHandler { - internal MethodInfo ExecuteGeneric = typeof(QueryHandler).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).First(t => t.Name == "Execute" && t.IsGenericMethod); - internal IQueryExecutionResult ExecuteReflected() => (IQueryExecutionResult)ExecuteGeneric.MakeGenericMethod(QueryableUnderlyingType).Invoke(this, new object[] { }); - - protected virtual IQueryExecutionResult Execute() + protected virtual IQueryExecutionResult FinalExecute() { - CommonBeforeExecute(); - return HasGrouping ? ExecuteGrouping() : ExecuteNoGrouping(); + CommonBeforeExecute(); + return HasGrouping ? ExecuteGrouping() : ExecuteNoGrouping(); } - - protected virtual IQueryExecutionResult ExecuteGrouping() + protected virtual IQueryExecutionResult ExecuteGrouping() { - var result = new QueryExecutionResult(); + var result = new QueryGroupExecutionResult(); // preserve queryable. var queryableAfterFilters = CurrentQueryable; @@ -34,17 +31,17 @@ namespace PoweredSoft.DynamicQuery CalculatePageCount(result); // intercept groups in advance to avoid doing it more than once :) - var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); + var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); // get the aggregates. - var aggregateResults = FetchAggregates(finalGroups); + var aggregateResults = FetchAggregates(finalGroups); // sorting. finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); // apply sorting and paging. - ApplySorting(); - ApplyPaging(); + ApplySorting(); + ApplyPaging(); // create group & select expression. CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); @@ -58,26 +55,26 @@ namespace PoweredSoft.DynamicQuery var groupRecords = CurrentQueryable.ToDynamicClassList(); // now join them into logical collections - var lastLists = new List>(); - result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); + var lastLists = new List<(List source, IGroupQueryResult group)>(); + result.Groups = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); // intercept grouped by. - QueryInterceptToGrouped(lastLists).Wait(); + QueryInterceptToGrouped(lastLists).Wait(); - result.Aggregates = CalculateTotalAggregate(queryableAfterFilters); + result.Aggregates = CalculateTotalAggregate(queryableAfterFilters); return result; } - protected virtual List CalculateTotalAggregate(IQueryable queryableAfterFilters) + protected virtual List CalculateTotalAggregate(IQueryable queryableAfterFilters) { if (!Criteria.Aggregates.Any()) return null; - IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); + IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); var aggregateResult = selectExpression.ToDynamicClassList().FirstOrDefault(); return MaterializeCalculateTotalAggregateResult(aggregateResult); } - protected virtual List> FetchAggregates(List finalGroups) + protected virtual List> FetchAggregates(List finalGroups) { if (!Criteria.Aggregates.Any()) return null; @@ -85,7 +82,7 @@ namespace PoweredSoft.DynamicQuery var previousGroups = new List(); var ret = finalGroups.Select(fg => { - IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); + IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); var aggregateResult = selectExpression.ToDynamicClassList(); previousGroups.Add(fg); return aggregateResult; @@ -93,9 +90,9 @@ namespace PoweredSoft.DynamicQuery return ret; } - protected virtual IQueryExecutionResult ExecuteNoGrouping() + protected virtual IQueryExecutionResult ExecuteNoGrouping() { - var result = new QueryExecutionResult(); + var result = new QueryExecutionResult(); // after filter queryable var afterFilterQueryable = CurrentQueryable; @@ -105,25 +102,30 @@ namespace PoweredSoft.DynamicQuery CalculatePageCount(result); // sorts and paging. - ApplySorting(); - ApplyPaging(); + ApplySorting(); + ApplyPaging(); // data. - var entities = ((IQueryable)CurrentQueryable).ToList(); - var records = InterceptConvertTo(entities).Result; + var entities = ((IQueryable)CurrentQueryable).ToList(); + var records = InterceptConvertTo(entities).Result; result.Data = records; // aggregates. - result.Aggregates = CalculateTotalAggregate(afterFilterQueryable); + result.Aggregates = CalculateTotalAggregate(afterFilterQueryable); return result; } - - public virtual IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) + public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) { Reset(queryable, criteria); - return ExecuteReflected(); + return FinalExecute(); + } + + public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) + { + Reset(queryable, criteria); + return FinalExecute(); } } } diff --git a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs index 23ddbb7..d2a6771 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs @@ -9,6 +9,7 @@ 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); @@ -134,5 +135,5 @@ namespace PoweredSoft.DynamicQuery var finalResult = whenAllResult.ToList(); return finalResult; } - } + }*/ } diff --git a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs index f0f21c4..0eaa2ec 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs @@ -31,11 +31,11 @@ namespace PoweredSoft.DynamicQuery CurrentQueryable = QueryableAtStart; } - protected virtual void CommonBeforeExecute() + protected virtual void CommonBeforeExecute() { - ApplyIncludeStrategyInterceptors(); - ApplyBeforeFilterInterceptors(); - ApplyFilters(); + ApplyIncludeStrategyInterceptors(); + ApplyBeforeFilterInterceptors(); + ApplyFilters(); } public virtual void AddInterceptor(IQueryInterceptor interceptor) @@ -46,7 +46,7 @@ namespace PoweredSoft.DynamicQuery Interceptors.Add(interceptor); } - protected virtual IGroup InterceptGroup(IGroup group) + protected virtual IGroup InterceptGroup(IGroup group) { var ret = Interceptors .Where(t => t is IGroupInterceptor) @@ -57,28 +57,28 @@ namespace PoweredSoft.DynamicQuery } - protected virtual void ApplyPaging() + protected virtual void ApplyPaging() { if (!HasPaging) return; - var q = (IQueryable) CurrentQueryable; + var q = (IQueryable) CurrentQueryable; var skip = ((Criteria.Page ?? 1) - 1) * Criteria.PageSize.Value; CurrentQueryable = q.Skip(skip).Take(Criteria.PageSize.Value); } - protected virtual void ApplySorting() + protected virtual void ApplySorting() { if (Criteria.Sorts?.Any() != true) { - ApplyNoSortInterceptor(); + ApplyNoSortInterceptor(); return; } bool isAppending = false; Criteria.Sorts.ForEach(sort => { - var transformedSort = InterceptSort(sort); + var transformedSort = InterceptSort(sort); if (transformedSort.Count == 0) return; @@ -90,7 +90,7 @@ namespace PoweredSoft.DynamicQuery }); } - protected DynamicClass FindMatchingAggregateResult(List> aggregateResults, List groups, List groupResults) + protected DynamicClass FindMatchingAggregateResult(List> aggregateResults, List groups, List> groupResults) { var groupIndex = groupResults.Count - 1; var aggregateLevel = aggregateResults[groupIndex]; @@ -108,7 +108,7 @@ namespace PoweredSoft.DynamicQuery return ret; } - protected virtual IQueryable CreateFetchAggregateSelectExpression(IGroup finalGroup, List previousGroups) + protected virtual IQueryable CreateFetchAggregateSelectExpression(IGroup finalGroup, List previousGroups) { var groupExpression = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => { @@ -124,7 +124,7 @@ namespace PoweredSoft.DynamicQuery sb.Key($"Key_{++groupKeyIndex}", $"Key_{groupKeyIndex}"); Criteria.Aggregates.ForEach((a, ai) => { - var fa = InterceptAggregate(a); + var fa = InterceptAggregate(a); var selectType = ResolveSelectFrom(fa.Type); sb.Aggregate(fa.Path, selectType, $"Agg_{ai}"); }); @@ -147,14 +147,14 @@ namespace PoweredSoft.DynamicQuery return ret; } - protected virtual IQueryable CreateTotalAggregateSelectExpression(IQueryable queryableAfterFilters) + protected virtual IQueryable CreateTotalAggregateSelectExpression(IQueryable queryableAfterFilters) { var groupExpression = queryableAfterFilters.EmptyGroupBy(QueryableUnderlyingType); var selectExpression = groupExpression.Select(sb => { Criteria.Aggregates.ForEach((a, index) => { - var fa = InterceptAggregate(a); + var fa = InterceptAggregate(a); var selectType = ResolveSelectFrom(fa.Type); sb.Aggregate(fa.Path, selectType, $"Agg_{index}"); }); @@ -162,7 +162,7 @@ namespace PoweredSoft.DynamicQuery return selectExpression; } - protected virtual void CalculatePageCount(IQueryExecutionResult result) + protected virtual void CalculatePageCount(IQueryExecutionResultPaging result) { if (!HasPaging) return; @@ -173,7 +173,7 @@ namespace PoweredSoft.DynamicQuery result.NumberOfPages = result.TotalRecords / Criteria.PageSize + (result.TotalRecords % Criteria.PageSize != 0 ? 1 : 0); } - protected virtual IAggregate InterceptAggregate(IAggregate aggregate) + protected virtual IAggregate InterceptAggregate(IAggregate aggregate) { var ret = Interceptors .Where(t => t is IAggregateInterceptor) @@ -182,47 +182,47 @@ namespace PoweredSoft.DynamicQuery return ret; } - protected virtual async Task> InterceptConvertTo(List entities) + protected virtual async Task> InterceptConvertTo(List entities) { await AfterEntityReadInterceptors(entities); - var objects = entities.Cast().ToList(); - for (var i = 0; i < objects.Count; i++) - objects[i] = InterceptConvertToObject(objects[i]); + var ret = new List(); + for (var i = 0; i < entities.Count; i++) + ret.Add(InterceptConvertToObject(entities[i])); - var pairs = entities.Select((t, index) => Tuple.Create(t, objects[index])).ToList(); - await AfterReadInterceptors(pairs); + var pairs = entities.Select((t, index) => Tuple.Create(t, (object)ret[index])).ToList(); + await AfterReadInterceptors(pairs); - return objects; + return ret; } - protected virtual async Task AfterEntityReadInterceptors(List entities) + protected virtual async Task AfterEntityReadInterceptors(List entities) { Interceptors - .Where(t => t is IAfterReadEntityInterceptor) - .Cast>() + .Where(t => t is IAfterReadEntityInterceptor) + .Cast>() .ToList() .ForEach(t => t.AfterReadEntity(entities)); - var asyncInterceptors = Interceptors.Where(t => t is IAfterReadEntityInterceptorAsync).Cast>(); + var asyncInterceptors = Interceptors.Where(t => t is IAfterReadEntityInterceptorAsync).Cast>(); foreach (var interceptor in asyncInterceptors) await interceptor.AfterReadEntityAsync(entities); } - protected virtual async Task AfterReadInterceptors(List> pairs) + protected virtual async Task AfterReadInterceptors(List> pairs) { Interceptors - .Where(t => t is IAfterReadInterceptor) - .Cast>() + .Where(t => t is IAfterReadInterceptor) + .Cast>() .ToList() .ForEach(t => t.AfterRead(pairs)); - var asyncInterceptors = Interceptors.Where(t => t is IAfterReadInterceptorAsync).Cast>(); + var asyncInterceptors = Interceptors.Where(t => t is IAfterReadInterceptorAsync).Cast>(); foreach (var interceptor in asyncInterceptors) await interceptor.AfterReadAsync(pairs); } - protected virtual object InterceptConvertToObject(object o) + protected virtual TRecord InterceptConvertToObject(object o) { o = Interceptors .Where(t => t is IQueryConvertInterceptor) @@ -230,20 +230,20 @@ namespace PoweredSoft.DynamicQuery .Aggregate(o, (prev, interceptor) => interceptor.InterceptResultTo(prev)); o = Interceptors - .Where(t => t is IQueryConvertInterceptor) - .Cast>() + .Where(t => t is IQueryConvertInterceptor) + .Cast>() .Aggregate(o, (prev, interceptor) => { - if (prev is T) - return interceptor.InterceptResultTo((T)prev); + if (prev is TSource) + return interceptor.InterceptResultTo((TSource)prev); return o; }); - return o; + return (TRecord)o; } - protected virtual List InterceptSort(ISort sort) + protected virtual List InterceptSort(ISort sort) { var original = new List() { @@ -259,15 +259,15 @@ namespace PoweredSoft.DynamicQuery return ret.ToList(); } - protected virtual void ApplyNoSortInterceptor() + protected virtual void ApplyNoSortInterceptor() { CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor) .Cast() .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); - CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor) - .Cast>() - .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); + CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor) + .Cast>() + .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); } @@ -292,40 +292,40 @@ namespace PoweredSoft.DynamicQuery return ret.Value; } - protected virtual void ApplyFilters() + protected virtual void ApplyFilters() { if (true != Criteria.Filters?.Any()) return; CurrentQueryable = CurrentQueryable.Query(whereBuilder => { - Criteria.Filters.ForEach(filter => ApplyFilter(whereBuilder, filter)); + Criteria.Filters.ForEach(filter => ApplyFilter(whereBuilder, filter)); }); } - protected virtual void ApplyFilter(WhereBuilder whereBuilder, IFilter filter) + protected virtual void ApplyFilter(WhereBuilder whereBuilder, IFilter filter) { - var transformedFilter = InterceptFilter(filter); + var transformedFilter = InterceptFilter(filter); if (transformedFilter is ISimpleFilter) - ApplySimpleFilter(whereBuilder, transformedFilter as ISimpleFilter); + ApplySimpleFilter(whereBuilder, transformedFilter as ISimpleFilter); else if (transformedFilter is ICompositeFilter) - AppleCompositeFilter(whereBuilder, transformedFilter as ICompositeFilter); + AppleCompositeFilter(whereBuilder, transformedFilter as ICompositeFilter); else throw new NotSupportedException(); } - protected virtual void AppleCompositeFilter(WhereBuilder whereBuilder, ICompositeFilter filter) + protected virtual void AppleCompositeFilter(WhereBuilder whereBuilder, ICompositeFilter filter) { - whereBuilder.SubQuery(subWhereBuilder => filter.Filters.ForEach(subFilter => ApplyFilter(subWhereBuilder, subFilter)), filter.And == true); + whereBuilder.SubQuery(subWhereBuilder => filter.Filters.ForEach(subFilter => ApplyFilter(subWhereBuilder, subFilter)), filter.And == true); } - protected virtual void ApplySimpleFilter(WhereBuilder whereBuilder, ISimpleFilter filter) + protected virtual void ApplySimpleFilter(WhereBuilder whereBuilder, ISimpleFilter filter) { var resolvedConditionOperator = ResolveConditionOperatorFrom(filter.Type); whereBuilder.Compare(filter.Path, resolvedConditionOperator, filter.Value, and: filter.And == true); } - protected virtual IFilter InterceptFilter(IFilter filter) + protected virtual IFilter InterceptFilter(IFilter filter) { var ret = Interceptors.Where(t => t is IFilterInterceptor) .Cast() @@ -334,7 +334,7 @@ namespace PoweredSoft.DynamicQuery return ret; } - protected virtual void ApplyIncludeStrategyInterceptors() + protected virtual void ApplyIncludeStrategyInterceptors() { CurrentQueryable = Interceptors .Where(t => t is IIncludeStrategyInterceptor) @@ -342,12 +342,12 @@ namespace PoweredSoft.DynamicQuery .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); CurrentQueryable = Interceptors - .Where(t => t is IIncludeStrategyInterceptor) - .Cast>() - .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); + .Where(t => t is IIncludeStrategyInterceptor) + .Cast>() + .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); } - protected virtual void ApplyBeforeFilterInterceptors() + protected virtual void ApplyBeforeFilterInterceptors() { CurrentQueryable = Interceptors .Where(t => t is IBeforeQueryFilterInterceptor) @@ -355,12 +355,12 @@ namespace PoweredSoft.DynamicQuery .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); CurrentQueryable = Interceptors - .Where(t => t is IBeforeQueryFilterInterceptor) - .Cast>() - .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); + .Where(t => t is IBeforeQueryFilterInterceptor) + .Cast>() + .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); } - protected virtual List RecursiveRegroup(List groupRecords, List> aggregateResults, IGroup group, List> lastLists, List parentGroupResults = null) + protected virtual List> RecursiveRegroup(List groupRecords, List> aggregateResults, IGroup group, List<(List entities, IGroupQueryResult group)> lastLists, List> parentGroupResults = null) { var groupIndex = Criteria.Groups.IndexOf(group); var isLast = Criteria.Groups.Last() == group; @@ -371,13 +371,13 @@ namespace PoweredSoft.DynamicQuery .GroupBy(gk => gk.GetDynamicPropertyValue($"Key_{groupIndex}")) .Select(t => { - var groupResult = new GroupQueryResult(); + var groupResult = new GroupQueryResult(); // group results. - List groupResults; + List> groupResults; if (parentGroupResults == null) - groupResults = new List { groupResult }; + groupResults = new List> { groupResult }; else groupResults = parentGroupResults.Union(new[] { groupResult }).ToList(); @@ -406,41 +406,43 @@ namespace PoweredSoft.DynamicQuery if (isLast) { - var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue>("Records")).ToList(); - groupResult.Data = entities.Cast().ToList(); - lastLists.Add(groupResult.Data); + var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue>("Records")).ToList(); + var tuple = (entities, groupResult); + groupResult.Data = new List(); + lastLists.Add(tuple); } else { - groupResult.Data = RecursiveRegroup(t.ToList(), aggregateResults, Criteria.Groups[groupIndex + 1], lastLists, groupResults); + groupResult.SubGroups = RecursiveRegroup(t.ToList(), aggregateResults, Criteria.Groups[groupIndex + 1], lastLists, groupResults); } return groupResult; }) - .AsEnumerable() + .AsEnumerable>() .ToList(); + return ret; } - protected virtual async Task QueryInterceptToGrouped(List> lists) + protected virtual async Task QueryInterceptToGrouped(List<(List entities, IGroupQueryResult group)> lists) { - var entities = lists.SelectMany(t => t).Cast().ToList(); + var entities = lists.SelectMany(t => t.entities).ToList(); await AfterEntityReadInterceptors(entities); - var pairs = new List>(); + var pairs = new List>(); lists.ForEach(innerList => { - for(var i = 0; i < innerList.Count; i++) + for (var i = 0; i < innerList.entities.Count; i++) { - var entity = (T)innerList[i]; - var convertedObject = InterceptConvertToObject(entity); - innerList[i] = convertedObject; - pairs.Add(Tuple.Create(entity, convertedObject)); + var entity = innerList.entities[i]; + var convertedObject = InterceptConvertToObject(entity); + innerList.group.Data.Add(convertedObject); + pairs.Add(Tuple.Create(entity, convertedObject as object)); } }); - await AfterReadInterceptors(pairs); + await AfterReadInterceptors(pairs); } } } diff --git a/PoweredSoft.DynamicQuery/QueryResult.cs b/PoweredSoft.DynamicQuery/QueryResult.cs index e4db5ab..89a3cb1 100644 --- a/PoweredSoft.DynamicQuery/QueryResult.cs +++ b/PoweredSoft.DynamicQuery/QueryResult.cs @@ -19,25 +19,32 @@ namespace PoweredSoft.DynamicQuery } // part of a result. - public abstract class QueryResult : IQueryResult + public abstract class QueryResult : IQueryResult { public List Aggregates { get; set; } - public List Data { get; set; } + public List Data { get; set; } public bool ShouldSerializeAggregates() => Aggregates != null; } // just result - public class QueryExecutionResult : QueryResult, IQueryExecutionResult + public class QueryExecutionResult : QueryResult, IQueryExecutionResult { public long TotalRecords { get; set; } public long? NumberOfPages { get; set; } } + public class QueryGroupExecutionResult : QueryExecutionResult, IQueryExecutionGroupResult + { + public List> Groups { get; set; } + } + // group result. - public class GroupQueryResult : QueryResult, IGroupQueryResult + public class GroupQueryResult : QueryResult, IGroupQueryResult { public string GroupPath { get; set; } public object GroupValue { get; set; } + public bool HasSubGroups { get; set; } + public List> SubGroups { get; set; } } } diff --git a/PoweredSoft.DynamicQuery/ServiceCollectionExtensions.cs b/PoweredSoft.DynamicQuery/ServiceCollectionExtensions.cs index 03db6e6..910afb3 100644 --- a/PoweredSoft.DynamicQuery/ServiceCollectionExtensions.cs +++ b/PoweredSoft.DynamicQuery/ServiceCollectionExtensions.cs @@ -18,7 +18,7 @@ namespace PoweredSoft.DynamicQuery services.TryAddTransient(); services.TryAddTransient(); services.TryAddTransient(); - services.TryAddTransient(); + //services.TryAddTransient(); return services; } }