From 9e6dc08e535bb1e83d7cf8268c1494620ebdc05c Mon Sep 17 00:00:00 2001 From: David Lebee Date: Tue, 19 Mar 2019 22:54:15 -0500 Subject: [PATCH 1/7] 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; } } From dc9c7e1674cd731944982823c82737a435722104 Mon Sep 17 00:00:00 2001 From: David Lebee Date: Fri, 22 Mar 2019 15:27:04 -0500 Subject: [PATCH 2/7] ready for version 2 --- ...PoweredSoft.DynamicQuery.AspNetCore.csproj | 2 +- .../IQueryHandler.cs | 1 - .../PoweredSoft.DynamicQuery.Core.csproj | 2 +- PoweredSoft.DynamicQuery.Test/AsyncTests.cs | 255 +++++++++--------- .../PoweredSoft.DynamicQuery.csproj | 2 +- PoweredSoft.DynamicQuery/QueryHandler.cs | 2 +- PoweredSoft.DynamicQuery/QueryHandlerAsync.cs | 79 +++--- PoweredSoft.DynamicQuery/QueryResult.cs | 2 +- 8 files changed, 174 insertions(+), 171 deletions(-) diff --git a/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj b/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj index 23ccc15..efb4056 100644 --- a/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj +++ b/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj @@ -8,7 +8,7 @@ https://github.com/PoweredSoft/DynamicQuery github powered,soft,dynamic,criteria,query,builder,asp,net,core - 1.0.0$(VersionSuffix) + 2.0.0$(VersionSuffix) https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro PoweredSoft.DynamicQuery.AspNetCore This projects makes it easier to use dynamic query in a asp.net core mvc project. diff --git a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs index 4ce5ab2..6a818ff 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs @@ -19,7 +19,6 @@ namespace PoweredSoft.DynamicQuery.Core 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)); } diff --git a/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj b/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj index 02e5841..cc4ce77 100644 --- a/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj +++ b/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj @@ -8,7 +8,7 @@ https://github.com/PoweredSoft/DynamicQuery.Core/ github powered,soft,dynamic,criteria,query,builder - 1.0.0$(VersionSuffix) + 2.0.0$(VersionSuffix) https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro PoweredSoft.DynamicQuery.Core core abstractions diff --git a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs index e1915f9..fc74c29 100644 --- a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs +++ b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs @@ -1,141 +1,142 @@ -//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.Extensions; +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.GroupedResult().Groups; -// // 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/PoweredSoft.DynamicQuery.csproj b/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj index aefa7bb..c7bb118 100644 --- a/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj +++ b/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj @@ -8,7 +8,7 @@ https://github.com/PoweredSoft/DynamicQuery github powered,soft,dynamic,criteria,query,builder - 1.0.0$(VersionSuffix) + 2.0.0$(VersionSuffix) https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro PoweredSoft.DynamicQuery dynamic query based on string path very usefull for network requests. diff --git a/PoweredSoft.DynamicQuery/QueryHandler.cs b/PoweredSoft.DynamicQuery/QueryHandler.cs index ecccdb6..bff122a 100644 --- a/PoweredSoft.DynamicQuery/QueryHandler.cs +++ b/PoweredSoft.DynamicQuery/QueryHandler.cs @@ -22,7 +22,7 @@ namespace PoweredSoft.DynamicQuery protected virtual IQueryExecutionResult ExecuteGrouping() { - var result = new QueryGroupExecutionResult(); + var result = new QueryExecutionGroupResult(); // preserve queryable. var queryableAfterFilters = CurrentQueryable; diff --git a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs index d2a6771..9ffe598 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs @@ -3,61 +3,52 @@ 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 { - 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)) + protected virtual Task> FinalExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { - CommonBeforeExecute(); - return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); + CommonBeforeExecute(); + return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); } - public Task ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + protected virtual async Task> ExecuteAsyncGrouping(CancellationToken cancellationToken) { - Reset(queryable, criteria); - return ExecuteAsyncReflected(cancellationToken); - } - - protected virtual async Task ExecuteAsyncGrouping(CancellationToken cancellationToken) - { - var result = new QueryExecutionResult(); + var result = new QueryExecutionGroupResult(); // preserve queryable. var queryableAfterFilters = CurrentQueryable; // async. - result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable)queryableAfterFilters, cancellationToken); + 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(); + var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); // get the aggregates. - var aggregateResults = await FetchAggregatesAsync(finalGroups, cancellationToken); + 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(); + ApplySorting(); + ApplyPaging(); // create group & select expression. CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); @@ -71,52 +62,52 @@ namespace PoweredSoft.DynamicQuery var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast(), cancellationToken); // now join them into logical collections - var lastLists = new List>(); - result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); + var lastLists = new List<(List entities, IGroupQueryResult group)>(); + result.Groups = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); // converted to grouped by. - await QueryInterceptToGrouped(lastLists); + await QueryInterceptToGrouped(lastLists); - result.Aggregates = await CalculateTotalAggregateAsync(queryableAfterFilters, cancellationToken); + result.Aggregates = await CalculateTotalAggregateAsync(queryableAfterFilters, cancellationToken); return result; } - protected async Task ExecuteAsyncNoGrouping(CancellationToken cancellationToken) + protected async Task> ExecuteAsyncNoGrouping(CancellationToken cancellationToken) { - var result = new QueryExecutionResult(); + var result = new QueryExecutionResult(); // after filter queryable - IQueryable afterFilterQueryable = (IQueryable)CurrentQueryable; + IQueryable afterFilterQueryable = (IQueryable)CurrentQueryable; // total records. result.TotalRecords = await AsyncQueryableService.LongCountAsync(afterFilterQueryable, cancellationToken); CalculatePageCount(result); // sorts and paging. - ApplySorting(); - ApplyPaging(); + ApplySorting(); + ApplyPaging(); // data. - var entities = await AsyncQueryableService.ToListAsync(((IQueryable)CurrentQueryable), cancellationToken); - var records = await InterceptConvertTo(entities); + var entities = await AsyncQueryableService.ToListAsync(((IQueryable)CurrentQueryable), cancellationToken); + var records = await InterceptConvertTo(entities); result.Data = records; // aggregates. - result.Aggregates = await CalculateTotalAggregateAsync(afterFilterQueryable, cancellationToken); + result.Aggregates = await CalculateTotalAggregateAsync(afterFilterQueryable, cancellationToken); return result; } - protected virtual async Task> CalculateTotalAggregateAsync(IQueryable queryableAfterFilters, CancellationToken cancellationToken) + protected virtual async Task> CalculateTotalAggregateAsync(IQueryable queryableAfterFilters, CancellationToken cancellationToken) { if (!Criteria.Aggregates.Any()) return null; - IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); + IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); var aggregateResult = await AsyncQueryableService.FirstOrDefaultAsync(selectExpression.Cast()); return MaterializeCalculateTotalAggregateResult(aggregateResult); } - protected async virtual Task>> FetchAggregatesAsync(List finalGroups, CancellationToken cancellationToken) + protected async virtual Task>> FetchAggregatesAsync(List finalGroups, CancellationToken cancellationToken) { if (!Criteria.Aggregates.Any()) return null; @@ -125,7 +116,7 @@ namespace PoweredSoft.DynamicQuery var whenAllResult = await Task.WhenAll(finalGroups.Select(fg => { - IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); + IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); var selectExpressionCasted = selectExpression.Cast(); var aggregateResult = AsyncQueryableService.ToListAsync(selectExpressionCasted, cancellationToken); previousGroups.Add(fg); @@ -135,5 +126,17 @@ namespace PoweredSoft.DynamicQuery var finalResult = whenAllResult.ToList(); return finalResult; } - }*/ + + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + { + Reset(queryable, criteria); + return FinalExecuteAsync(cancellationToken); + } + + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + { + Reset(queryable, criteria); + return FinalExecuteAsync(cancellationToken); + } + } } diff --git a/PoweredSoft.DynamicQuery/QueryResult.cs b/PoweredSoft.DynamicQuery/QueryResult.cs index 89a3cb1..a8ffc14 100644 --- a/PoweredSoft.DynamicQuery/QueryResult.cs +++ b/PoweredSoft.DynamicQuery/QueryResult.cs @@ -34,7 +34,7 @@ namespace PoweredSoft.DynamicQuery public long? NumberOfPages { get; set; } } - public class QueryGroupExecutionResult : QueryExecutionResult, IQueryExecutionGroupResult + public class QueryExecutionGroupResult : QueryExecutionResult, IQueryExecutionGroupResult { public List> Groups { get; set; } } From 9262708ab8ae19eca7fcc0c2d1ec64b8ed07aedc Mon Sep 17 00:00:00 2001 From: David Lebee Date: Fri, 22 Mar 2019 15:38:13 -0500 Subject: [PATCH 3/7] now had more generic possibilites. --- .../IAfterReadInterceptor.cs | 10 +++++++ .../IQueryConvertInterceptor.cs | 5 ++++ PoweredSoft.DynamicQuery/QueryHandlerBase.cs | 27 +++++++++++++++---- 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs b/PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs index 3fd0aa8..d47b5ee 100644 --- a/PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs +++ b/PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs @@ -24,4 +24,14 @@ namespace PoweredSoft.DynamicQuery.Core { Task AfterReadAsync(List> pairs, CancellationToken cancellationToken = default(CancellationToken)); } + + public interface IAfterReadInterceptor : IQueryInterceptor + { + void AfterRead(List> pairs); + } + + public interface IAfterReadInterceptorAsync : IQueryInterceptor + { + Task AfterReadAsync(List> pairs, CancellationToken cancellationToken = default(CancellationToken)); + } } diff --git a/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs b/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs index df386a8..f204a13 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs @@ -13,4 +13,9 @@ namespace PoweredSoft.DynamicQuery.Core { object InterceptResultTo(T entity); } + + public interface IQueryConvertInterceptor : IQueryInterceptor + { + T2 InterceptResultTo(T entity); + } } diff --git a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs index 0eaa2ec..29da988 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs @@ -190,7 +190,7 @@ namespace PoweredSoft.DynamicQuery for (var i = 0; i < entities.Count; i++) ret.Add(InterceptConvertToObject(entities[i])); - var pairs = entities.Select((t, index) => Tuple.Create(t, (object)ret[index])).ToList(); + var pairs = entities.Select((t, index) => Tuple.Create(t, ret[index])).ToList(); await AfterReadInterceptors(pairs); return ret; @@ -209,16 +209,22 @@ namespace PoweredSoft.DynamicQuery await interceptor.AfterReadEntityAsync(entities); } - protected virtual async Task AfterReadInterceptors(List> pairs) + protected virtual async Task AfterReadInterceptors(List> pairs) { + var objPair = pairs.Select(t => Tuple.Create(t.Item1, (object)t.Item2)).ToList(); + Interceptors .Where(t => t is IAfterReadInterceptor) .Cast>() .ToList() - .ForEach(t => t.AfterRead(pairs)); + .ForEach(t => t.AfterRead(objPair)); var asyncInterceptors = Interceptors.Where(t => t is IAfterReadInterceptorAsync).Cast>(); foreach (var interceptor in asyncInterceptors) + await interceptor.AfterReadAsync(objPair); + + var asyncInterceptors2 = Interceptors.Where(t => t is IAfterReadInterceptorAsync).Cast>(); + foreach (var interceptor in asyncInterceptors2) await interceptor.AfterReadAsync(pairs); } @@ -240,6 +246,17 @@ namespace PoweredSoft.DynamicQuery return o; }); + o = Interceptors + .Where(t => t is IQueryConvertInterceptor) + .Cast>() + .Aggregate(o, (prev, interceptor) => + { + if (prev is TSource) + return interceptor.InterceptResultTo((TSource)prev); + + return o; + }); + return (TRecord)o; } @@ -429,7 +446,7 @@ namespace PoweredSoft.DynamicQuery var entities = lists.SelectMany(t => t.entities).ToList(); await AfterEntityReadInterceptors(entities); - var pairs = new List>(); + var pairs = new List>(); lists.ForEach(innerList => { @@ -438,7 +455,7 @@ namespace PoweredSoft.DynamicQuery var entity = innerList.entities[i]; var convertedObject = InterceptConvertToObject(entity); innerList.group.Data.Add(convertedObject); - pairs.Add(Tuple.Create(entity, convertedObject as object)); + pairs.Add(Tuple.Create(entity, convertedObject)); } }); From 4e00431b89307fb1f4973ff36ad13fd80a74aceb Mon Sep 17 00:00:00 2001 From: David Lebee Date: Fri, 22 Mar 2019 16:07:13 -0500 Subject: [PATCH 4/7] docs for breaking change. --- README.md | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/README.md b/README.md index 36cad35..29c22c4 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ It's a library that allows you to easily query a queryable using a criteria obje It also offers, to intercept the query using **IQueryInterceptor** implementations. +## Breaking Changes + +If you are moving up from v1, the breaking changes details are lower. + + ## Getting Started > Install nuget package to your awesome project. @@ -85,6 +90,25 @@ public async Task Read( Visit: https://github.com/PoweredSoft/DynamicQueryAspNetCoreSample +### Breaking Changes if you are migrating from 1.x + +Response interface, is now generic ```IQueryResult``` which impacts the way to execute the handler. + +#### Grouping results + +Since the results are now generic, it's no longer a List in the response so that changes the result if grouping is requested. + +You have now a property Groups, and HasSubGroups, and SubGroups. + +#### QueryConvertTo Interceptor + +If you are using IQueryConvertTo interceptors, it's new that you must specify the type you are converting to +Ex: +```csharp +IQueryable query = context.Somethings; +var result = handler.Execute(query, criteria); +``` + ## Criteria Criteria must implement the following interfaces From 9ee1286e82067731688831e79d38f5ce9e2b4f13 Mon Sep 17 00:00:00 2001 From: David Lebee Date: Fri, 22 Mar 2019 16:09:44 -0500 Subject: [PATCH 5/7] getting documentations ready. --- PoweredSoft.DynamicQuery.Core/IQueryResult.cs | 2 +- README.md | 30 ++++++++++++++----- 2 files changed, 23 insertions(+), 9 deletions(-) diff --git a/PoweredSoft.DynamicQuery.Core/IQueryResult.cs b/PoweredSoft.DynamicQuery.Core/IQueryResult.cs index aa2fa75..cb7b6d4 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryResult.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryResult.cs @@ -21,7 +21,7 @@ namespace PoweredSoft.DynamicQuery.Core { string GroupPath { get; set; } object GroupValue { get; set; } - bool HasSubGroups { get; set; } + bool HasSubGroups { get; } List> SubGroups { get; set; } } diff --git a/README.md b/README.md index 29c22c4..e266fe9 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ public class Startup ```csharp [HttpGet] -public IQueryExecutionResult Get( +public IQueryExecutionResult Get( [FromServices]YourContext context, [FromServices]IQueryHandler handler, [FromServices]IQueryCriteria criteria, @@ -60,7 +60,7 @@ public IQueryExecutionResult Get( } [HttpPost] -public IQueryExecutionResult Read( +public IQueryExecutionResult Read( [FromServices]YourContext context, [FromServices]IQueryHandler handler, [FromBody]IQueryCriteria criteria) @@ -75,7 +75,7 @@ public IQueryExecutionResult Read( ```csharp [HttpPost] -public async Task Read( +public async Task> Read( [FromServices]YourContext context, [FromServices]IQueryHandlerAsync handler, [FromBody]IQueryCriteria criteria) @@ -138,13 +138,15 @@ var criteria = new QueryCriteria }; var queryHandler = new QueryHandler(); -IQueryExecutionResult result = queryHandler.Execute(someQueryable, criteria); +IQueryExecutionResult result = queryHandler.Execute(someQueryable, criteria); ``` ## Query Result Here is the interfaces that represent the result of query handling execution. +> Changed in 2.x + ```csharp public interface IAggregateResult { @@ -153,23 +155,35 @@ public interface IAggregateResult 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; } + 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; } +} ``` From 10fbe0167411d43ce8bae0e26acbd7f895e01b38 Mon Sep 17 00:00:00 2001 From: David Lebee Date: Fri, 22 Mar 2019 16:12:12 -0500 Subject: [PATCH 6/7] more doc. --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e266fe9..c906577 100644 --- a/README.md +++ b/README.md @@ -218,4 +218,5 @@ IAggregateInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IAgg Interceptor | Interface | Example | Description ----------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------ IQueryConvertInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs) | This interceptor allows you to replace the object that is being returned by the query, by another object instance -IQueryConvertInterceptor<T> | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs#L72) | This interceptor allows you to replace the object that is being returned by the query, by another object instance +IQueryConvertInterceptor<T, T2> | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs#L72) | This interceptor allows you to replace the object that is being returned by the query, by another object instance **(restricts the source)** +IQueryConvertInterceptor<T, T2> | coming soon | coming soon | This interceptor allows you to replace the object that is being returned by the query, by another object instance **(restricts the source & output)** \ No newline at end of file From 7392c252575a70df444c04c6aac771228921dcbc Mon Sep 17 00:00:00 2001 From: David Lebee Date: Fri, 22 Mar 2019 16:17:19 -0500 Subject: [PATCH 7/7] I beleive its ready now. --- .../ConvertibleInterceptorTests.cs | 29 +++++++++++++++++++ PoweredSoft.DynamicQuery/QueryResult.cs | 6 +++- .../ServiceCollectionExtensions.cs | 2 +- README.md | 2 +- 4 files changed, 36 insertions(+), 3 deletions(-) diff --git a/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs b/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs index 002345a..0533d7c 100644 --- a/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs +++ b/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs @@ -55,6 +55,22 @@ namespace PoweredSoft.DynamicQuery.Test } } + private class MockQueryConvertGenericInterceptor2 : + IQueryConvertInterceptor + { + public CustomerModel InterceptResultTo(Customer entity) + { + var customer = entity; + var personModel = new CustomerModel + { + Id = customer.Id, + FirstName = customer.FirstName, + LastName = customer.LastName + }; + return personModel; + } + } + [Fact] public void NonGeneric() { @@ -80,5 +96,18 @@ namespace PoweredSoft.DynamicQuery.Test Assert.All(result.Data, t => Assert.IsType(t)); }); } + + [Fact] + public void Generic2() + { + MockContextFactory.SeedAndTestContextFor("ConvertibleIntereceptorTests_Generic2", TestSeeders.SimpleSeedScenario, ctx => + { + var criteria = new QueryCriteria(); + var queryHandler = new QueryHandler(); + queryHandler.AddInterceptor(new MockQueryConvertGenericInterceptor2()); + var result = queryHandler.Execute(ctx.Customers, criteria); + Assert.All(result.Data, t => Assert.IsType(t)); + }); + } } } diff --git a/PoweredSoft.DynamicQuery/QueryResult.cs b/PoweredSoft.DynamicQuery/QueryResult.cs index a8ffc14..9807aee 100644 --- a/PoweredSoft.DynamicQuery/QueryResult.cs +++ b/PoweredSoft.DynamicQuery/QueryResult.cs @@ -25,6 +25,8 @@ namespace PoweredSoft.DynamicQuery public List Data { get; set; } public bool ShouldSerializeAggregates() => Aggregates != null; + + public bool ShouldSerializeData() => Data != null; } // just result @@ -44,7 +46,9 @@ namespace PoweredSoft.DynamicQuery { public string GroupPath { get; set; } public object GroupValue { get; set; } - public bool HasSubGroups { get; set; } + public bool HasSubGroups => SubGroups != null && SubGroups.Count > 1; public List> SubGroups { get; set; } + + public bool ShouldSerializeSubGroups() => HasSubGroups; } } diff --git a/PoweredSoft.DynamicQuery/ServiceCollectionExtensions.cs b/PoweredSoft.DynamicQuery/ServiceCollectionExtensions.cs index 910afb3..03db6e6 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; } } diff --git a/README.md b/README.md index c906577..86a1210 100644 --- a/README.md +++ b/README.md @@ -219,4 +219,4 @@ Interceptor | Interface ----------------------------------|---------------------------------------------------------------------------------------|-------------------------------------------------------------|------------------------------------------------------------------------------------------------ IQueryConvertInterceptor | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs) | This interceptor allows you to replace the object that is being returned by the query, by another object instance IQueryConvertInterceptor<T, T2> | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs#L72) | This interceptor allows you to replace the object that is being returned by the query, by another object instance **(restricts the source)** -IQueryConvertInterceptor<T, T2> | coming soon | coming soon | This interceptor allows you to replace the object that is being returned by the query, by another object instance **(restricts the source & output)** \ No newline at end of file +IQueryConvertInterceptor<T, T2> | [interface](../master/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs) | [test](../master/PoweredSoft.DynamicQuery.Test/ConvertibleInterceptorTests.cs#L101) | This interceptor allows you to replace the object that is being returned by the query, by another object instance **(restricts the source & output)** \ No newline at end of file