From 9c0a05b579aeb34204bc5bcd68f05dfdd7fa4d04 Mon Sep 17 00:00:00 2001 From: David Lebee Date: Wed, 27 Nov 2019 20:08:51 -0600 Subject: [PATCH] execution options. --- .../IInterceptQueryExecutionOptions.cs | 12 ++++ .../IQueryExecutionOptions.cs | 8 +++ .../IQueryHandler.cs | 8 ++- .../QueryExecutionOptions.cs | 8 +++ .../AggregateInterceptorTests.cs | 17 +++-- .../AggregateTests.cs | 26 +++++-- PoweredSoft.DynamicQuery.Test/AsyncTests.cs | 72 ++++++++++++++++++- .../GroupInterceptorTests.cs | 34 ++++++++- PoweredSoft.DynamicQuery.Test/GroupTests.cs | 39 +++++++--- .../Mock/MockContextFactory.cs | 5 +- .../MockQueryExecutionOptionsInterceptor.cs | 22 ++++++ .../PoweredSoft.DynamicQuery.Test.csproj | 8 +-- PoweredSoft.DynamicQuery/QueryHandler.cs | 29 ++++++-- PoweredSoft.DynamicQuery/QueryHandlerAsync.cs | 68 ++++++++++++++---- PoweredSoft.DynamicQuery/QueryHandlerBase.cs | 14 +++- 15 files changed, 312 insertions(+), 58 deletions(-) create mode 100644 PoweredSoft.DynamicQuery.Core/IInterceptQueryExecutionOptions.cs create mode 100644 PoweredSoft.DynamicQuery.Core/IQueryExecutionOptions.cs create mode 100644 PoweredSoft.DynamicQuery.Core/QueryExecutionOptions.cs create mode 100644 PoweredSoft.DynamicQuery.Test/MockQueryExecutionOptionsInterceptor.cs diff --git a/PoweredSoft.DynamicQuery.Core/IInterceptQueryExecutionOptions.cs b/PoweredSoft.DynamicQuery.Core/IInterceptQueryExecutionOptions.cs new file mode 100644 index 0000000..3924c56 --- /dev/null +++ b/PoweredSoft.DynamicQuery.Core/IInterceptQueryExecutionOptions.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace PoweredSoft.DynamicQuery.Core +{ + public interface IQueryExecutionOptionsInterceptor : IQueryInterceptor + { + IQueryExecutionOptions InterceptQueryExecutionOptions(IQueryable queryable, IQueryExecutionOptions current); + } +} diff --git a/PoweredSoft.DynamicQuery.Core/IQueryExecutionOptions.cs b/PoweredSoft.DynamicQuery.Core/IQueryExecutionOptions.cs new file mode 100644 index 0000000..af77aa7 --- /dev/null +++ b/PoweredSoft.DynamicQuery.Core/IQueryExecutionOptions.cs @@ -0,0 +1,8 @@ +namespace PoweredSoft.DynamicQuery.Core +{ + public interface IQueryExecutionOptions + { + bool GroupByInMemory { get; set; } + bool GroupByInMemoryNullCheck { get; set; } + } +} diff --git a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs index 6a818ff..032176e 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs @@ -15,11 +15,15 @@ namespace PoweredSoft.DynamicQuery.Core { IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); + IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options); + IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options); } 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); + Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default); + Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default); + Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default); } } diff --git a/PoweredSoft.DynamicQuery.Core/QueryExecutionOptions.cs b/PoweredSoft.DynamicQuery.Core/QueryExecutionOptions.cs new file mode 100644 index 0000000..4ee0266 --- /dev/null +++ b/PoweredSoft.DynamicQuery.Core/QueryExecutionOptions.cs @@ -0,0 +1,8 @@ +namespace PoweredSoft.DynamicQuery.Core +{ + public class QueryExecutionOptions : IQueryExecutionOptions + { + public bool GroupByInMemory { get; set; } = false; + public bool GroupByInMemoryNullCheck { get; set; } = false; + } +} diff --git a/PoweredSoft.DynamicQuery.Test/AggregateInterceptorTests.cs b/PoweredSoft.DynamicQuery.Test/AggregateInterceptorTests.cs index eefbfa7..423f0a3 100644 --- a/PoweredSoft.DynamicQuery.Test/AggregateInterceptorTests.cs +++ b/PoweredSoft.DynamicQuery.Test/AggregateInterceptorTests.cs @@ -1,4 +1,5 @@ -using PoweredSoft.DynamicQuery.Core; +using Microsoft.EntityFrameworkCore; +using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Test.Mock; using System; using System.Collections.Generic; @@ -14,7 +15,7 @@ namespace PoweredSoft.DynamicQuery.Test { public IAggregate InterceptAggregate(IAggregate aggregate) => new Aggregate { - Path = "Item.Price", + Path = "Price", Type = AggregateType.Avg }; } @@ -24,10 +25,12 @@ namespace PoweredSoft.DynamicQuery.Test { MockContextFactory.SeedAndTestContextFor("AggregatorInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx => { - var expected = ctx.OrderItems.GroupBy(t => true).Select(t => new - { - PriceAtTheTime = t.Average(t2 => t2.Item.Price) - }).First(); + var expected = ctx.Items + .GroupBy(t => true) + .Select(t => new + { + PriceAtTheTime = t.Average(t2 => t2.Price) + }).First(); var criteria = new QueryCriteria(); criteria.Aggregates.Add(new Aggregate @@ -37,7 +40,7 @@ namespace PoweredSoft.DynamicQuery.Test }); var queryHandler = new QueryHandler(); queryHandler.AddInterceptor(new MockAggregateInterceptor()); - var result = queryHandler.Execute(ctx.OrderItems, criteria); + var result = queryHandler.Execute(ctx.Items, criteria); Assert.Equal(expected.PriceAtTheTime, result.Aggregates.First().Value); }); } diff --git a/PoweredSoft.DynamicQuery.Test/AggregateTests.cs b/PoweredSoft.DynamicQuery.Test/AggregateTests.cs index cb36f65..d1df1af 100644 --- a/PoweredSoft.DynamicQuery.Test/AggregateTests.cs +++ b/PoweredSoft.DynamicQuery.Test/AggregateTests.cs @@ -1,4 +1,5 @@ -using PoweredSoft.DynamicQuery.Core; +using Microsoft.EntityFrameworkCore; +using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Test.Mock; using System; using System.Collections.Generic; @@ -27,10 +28,11 @@ namespace PoweredSoft.DynamicQuery.Test ItemQuantityAverage = t.Average(t2 => t2.Quantity), ItemQuantitySum = t.Sum(t2 => t2.Quantity), AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime), + /* not supported by ef core 3.0 First = t.First(), FirstOrDefault = t.FirstOrDefault(), Last = t.Last(), - LastOrDefault = t.LastOrDefault() + LastOrDefault = t.LastOrDefault()*/ }) .First(); @@ -45,21 +47,28 @@ namespace PoweredSoft.DynamicQuery.Test new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"}, new Aggregate { Type = AggregateType.Min, Path = "Quantity"}, new Aggregate { Type = AggregateType.Max, Path = "Quantity" }, + /*not support by ef core 3.0 new Aggregate { Type = AggregateType.First }, new Aggregate { Type = AggregateType.FirstOrDefault }, new Aggregate { Type = AggregateType.Last }, new Aggregate { Type = AggregateType.LastOrDefault }, + */ } }; var queryHandler = new QueryHandler(); - var result = queryHandler.Execute(ctx.OrderItems, criteria); + var result = queryHandler.Execute(ctx.OrderItems, criteria, new QueryExecutionOptions + { + GroupByInMemory = true + }); var aggCount = result.Aggregates.First(t => t.Type == AggregateType.Count); + + /* var aggFirst = result.Aggregates.First(t => t.Type == AggregateType.First); var aggFirstOrDefault = result.Aggregates.First(t => t.Type == AggregateType.FirstOrDefault); var aggLast = result.Aggregates.First(t => t.Type == AggregateType.Last); - var aggLastOrDefault = result.Aggregates.First(t => t.Type == AggregateType.LastOrDefault); + var aggLastOrDefault = result.Aggregates.First(t => t.Type == AggregateType.LastOrDefault);*/ var aggItemQuantityMin = result.Aggregates.First(t => t.Type == AggregateType.Min && t.Path == "Quantity"); var aggItemQuantityMax = result.Aggregates.First(t => t.Type == AggregateType.Max && t.Path == "Quantity"); @@ -68,10 +77,11 @@ namespace PoweredSoft.DynamicQuery.Test var aggAvgOfPrice = result.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); Assert.Equal(shouldResult.Count, aggCount.Value); + /* Assert.Equal(shouldResult.First?.Id, (aggFirst.Value as OrderItem)?.Id); Assert.Equal(shouldResult.FirstOrDefault?.Id, (aggFirstOrDefault.Value as OrderItem)?.Id); Assert.Equal(shouldResult.Last?.Id, (aggLast.Value as OrderItem)?.Id); - Assert.Equal(shouldResult.LastOrDefault?.Id, (aggLastOrDefault.Value as OrderItem)?.Id); + Assert.Equal(shouldResult.LastOrDefault?.Id, (aggLastOrDefault.Value as OrderItem)?.Id);*/ Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); @@ -113,7 +123,11 @@ namespace PoweredSoft.DynamicQuery.Test }; var queryHandler = new QueryHandler(); - var result = queryHandler.Execute(ctx.OrderItems, criteria); + var queryable = ctx.OrderItems.Include(t => t.Order); + var result = queryHandler.Execute(queryable, criteria, new QueryExecutionOptions + { + GroupByInMemory = true + }); var groupedResult = result as IQueryExecutionGroupResult; Assert.NotNull(groupedResult); diff --git a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs index a5b5429..e1e5f68 100644 --- a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs +++ b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs @@ -1,4 +1,5 @@ -using PoweredSoft.Data; +using Microsoft.EntityFrameworkCore; +using PoweredSoft.Data; using PoweredSoft.Data.EntityFrameworkCore; using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Extensions; @@ -9,6 +10,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Xunit; +using static PoweredSoft.DynamicQuery.Test.GroupInterceptorTests; namespace PoweredSoft.DynamicQuery.Test { @@ -65,7 +67,11 @@ namespace PoweredSoft.DynamicQuery.Test }; var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); var queryHandler = new QueryHandlerAsync(asyncService); - var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); + var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), criteria, new QueryExecutionOptions + { + GroupByInMemory = true + }); + var groups = result.GroupedResult().Groups; // validate group and aggregates of groups. @@ -165,6 +171,68 @@ namespace PoweredSoft.DynamicQuery.Test Assert.Equal(resultShouldMatch, result.Data); }); } + + [Fact] + public void WithGroupingInterceptorOptions() + { + MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGroupingInterceptorOptions", 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); + queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor()); + var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), 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 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); + }); + }); + } } } diff --git a/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs b/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs index 42e3d06..bf3fb8a 100644 --- a/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs +++ b/PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs @@ -1,4 +1,5 @@ -using PoweredSoft.DynamicQuery.Core; +using Microsoft.EntityFrameworkCore; +using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Extensions; using PoweredSoft.DynamicQuery.Test.Mock; using System; @@ -9,7 +10,7 @@ using Xunit; namespace PoweredSoft.DynamicQuery.Test { - public class GroupInterceptorTests + public partial class GroupInterceptorTests { private class MockGroupInterceptor : IGroupInterceptor { @@ -37,7 +38,34 @@ namespace PoweredSoft.DynamicQuery.Test criteria.Groups.Add(new Group { Path = "CustomerFirstName" }); var queryHandler = new QueryHandler(); queryHandler.AddInterceptor(new MockGroupInterceptor()); - var result = queryHandler.Execute(ctx.Orders, criteria); + var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria, new QueryExecutionOptions + { + GroupByInMemory = true + }); + + var groupedResult = result.GroupedResult(); + var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList(); + Assert.Equal(expected, actual); + }); + } + + [Fact] + public void WithInterptorSimple() + { + MockContextFactory.SeedAndTestContextFor("GroupInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx => + { + var expected = ctx.Orders + .OrderBy(t => t.Customer.FirstName) + .GroupBy(t => t.Customer.FirstName) + .Select(t => t.Key) + .ToList(); + + var criteria = new QueryCriteria(); + criteria.Groups.Add(new Group { Path = "CustomerFirstName" }); + var queryHandler = new QueryHandler(); + queryHandler.AddInterceptor(new MockGroupInterceptor()); + queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor()); + var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria); var groupedResult = result.GroupedResult(); var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList(); diff --git a/PoweredSoft.DynamicQuery.Test/GroupTests.cs b/PoweredSoft.DynamicQuery.Test/GroupTests.cs index 5bb87a5..591930b 100644 --- a/PoweredSoft.DynamicQuery.Test/GroupTests.cs +++ b/PoweredSoft.DynamicQuery.Test/GroupTests.cs @@ -1,4 +1,5 @@ -using PoweredSoft.DynamicQuery.Core; +using Microsoft.EntityFrameworkCore; +using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Extensions; using PoweredSoft.DynamicQuery.Test.Mock; using System; @@ -18,23 +19,32 @@ namespace PoweredSoft.DynamicQuery.Test { MockContextFactory.SeedAndTestContextFor("GroupTests_Simple", TestSeeders.SimpleSeedScenario, ctx => { - var shouldResult = ctx.Orders.OrderBy(t => t.Customer).GroupBy(t => t.Customer).Select(t => new - { - Customer = t.Key, - Orders = t.ToList() - }).ToList(); + var shouldResult = ctx.Orders + .OrderBy(t => t.CustomerId) + .ToList() + .GroupBy(t => t.CustomerId) + .Select(t => new + { + CustomerId = t.Key, + Orders = t.ToList() + }) + .ToList(); // query handler that is empty should be the same as running to list. var criteria = new QueryCriteria() { Groups = new List { - new Group { Path = "Customer" } + new Group { Path = "CustomerId" } } }; var queryHandler = new QueryHandler(); - var result = queryHandler.Execute(ctx.Orders, criteria); + var result = queryHandler.Execute(ctx.Orders, criteria, new QueryExecutionOptions + { + GroupByInMemory = true, + GroupByInMemoryNullCheck = false + }); var groupedResult = result.GroupedResult(); // top level should have same amount of group levels. @@ -43,7 +53,7 @@ namespace PoweredSoft.DynamicQuery.Test { var expected = shouldResult[0]; var actual = groupedResult.Groups[0]; - Assert.Equal(expected.Customer.Id, (actual.GroupValue as Customer).Id); + Assert.Equal(expected.CustomerId, actual.GroupValue); var expectedOrderIds = expected.Orders.Select(t => t.Id).ToList(); var actualOrderIds = actual.Data.Cast().Select(t => t.Id).ToList(); @@ -71,7 +81,10 @@ namespace PoweredSoft.DynamicQuery.Test }; var queryHandler = new QueryHandler(); - var result = queryHandler.Execute(ctx.Tickets, criteria); + var result = queryHandler.Execute(ctx.Tickets, criteria, new QueryExecutionOptions + { + GroupByInMemory = true + }); var groupedResult = result.GroupedResult(); @@ -106,7 +119,11 @@ 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, new QueryExecutionOptions + { + GroupByInMemory = true + }); + Assert.Equal(4, interceptor.Count); Assert.True(interceptor.Test); Assert.True(interceptor.Test2); diff --git a/PoweredSoft.DynamicQuery.Test/Mock/MockContextFactory.cs b/PoweredSoft.DynamicQuery.Test/Mock/MockContextFactory.cs index 7be51b0..443ea7d 100644 --- a/PoweredSoft.DynamicQuery.Test/Mock/MockContextFactory.cs +++ b/PoweredSoft.DynamicQuery.Test/Mock/MockContextFactory.cs @@ -14,12 +14,9 @@ namespace PoweredSoft.DynamicQuery.Test.Mock public static void TestContextFor(string testName, Action action) { var options = new DbContextOptionsBuilder() - .ConfigureWarnings(warnings => - warnings.Ignore(RelationalEventId.QueryClientEvaluationWarning) - ) .UseInMemoryDatabase(databaseName: testName).Options; - using (var ctx = new MockContext(options)) + using var ctx = new MockContext(options); action(ctx); } diff --git a/PoweredSoft.DynamicQuery.Test/MockQueryExecutionOptionsInterceptor.cs b/PoweredSoft.DynamicQuery.Test/MockQueryExecutionOptionsInterceptor.cs new file mode 100644 index 0000000..6e1d2c8 --- /dev/null +++ b/PoweredSoft.DynamicQuery.Test/MockQueryExecutionOptionsInterceptor.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Query.Internal; +using PoweredSoft.DynamicQuery.Core; +using System.Linq; + +namespace PoweredSoft.DynamicQuery.Test +{ + public partial class GroupInterceptorTests + { + public class MockQueryExecutionOptionsInterceptor : IQueryExecutionOptionsInterceptor + { + public IQueryExecutionOptions InterceptQueryExecutionOptions(IQueryable queryable, IQueryExecutionOptions current) + { + if (queryable.Provider is IAsyncQueryProvider) + { + current.GroupByInMemory = true; + } + + return current; + } + } + } +} diff --git a/PoweredSoft.DynamicQuery.Test/PoweredSoft.DynamicQuery.Test.csproj b/PoweredSoft.DynamicQuery.Test/PoweredSoft.DynamicQuery.Test.csproj index 580c432..180e2a4 100644 --- a/PoweredSoft.DynamicQuery.Test/PoweredSoft.DynamicQuery.Test.csproj +++ b/PoweredSoft.DynamicQuery.Test/PoweredSoft.DynamicQuery.Test.csproj @@ -8,12 +8,12 @@ - - - + + + - + all diff --git a/PoweredSoft.DynamicQuery/QueryHandler.cs b/PoweredSoft.DynamicQuery/QueryHandler.cs index 67dfbc1..3aab5e1 100644 --- a/PoweredSoft.DynamicQuery/QueryHandler.cs +++ b/PoweredSoft.DynamicQuery/QueryHandler.cs @@ -43,14 +43,23 @@ namespace PoweredSoft.DynamicQuery ApplySorting(); ApplyPaging(); - // create group & select expression. - CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); + if (Options.GroupByInMemory) + CurrentQueryable = CurrentQueryable.ToObjectList().Cast().AsQueryable(); + + CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => + { + gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); + finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")); + }); + CurrentQueryable = CurrentQueryable.Select(sb => { + sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}")); sb.ToList("Records"); }); + // loop through the grouped records. var groupRecords = CurrentQueryable.ToDynamicClassList(); @@ -118,13 +127,25 @@ namespace PoweredSoft.DynamicQuery public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) { - Reset(queryable, criteria); + Reset(queryable, criteria, new QueryExecutionOptions()); return FinalExecute(); } public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) { - Reset(queryable, criteria); + Reset(queryable, criteria, new QueryExecutionOptions()); + return FinalExecute(); + } + + public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options) + { + Reset(queryable, criteria, options); + return FinalExecute(); + } + + public IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options) + { + Reset(queryable, criteria, options); return FinalExecute(); } } diff --git a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs index f9d5689..69c1a5f 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs @@ -20,7 +20,7 @@ namespace PoweredSoft.DynamicQuery AsyncQueryableService = asyncQueryableService; } - protected virtual Task> FinalExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) + protected virtual Task> FinalExecuteAsync(CancellationToken cancellationToken = default) { CommonBeforeExecute(); return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); @@ -50,16 +50,44 @@ namespace PoweredSoft.DynamicQuery ApplySorting(); ApplyPaging(); - // create group & select expression. - CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); - CurrentQueryable = CurrentQueryable.Select(sb => - { - finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}")); - sb.ToList("Records"); - }); + List groupRecords; - // loop through the grouped records. - var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast(), cancellationToken); + if (Options.GroupByInMemory) + { + CurrentQueryable = CurrentQueryable.ToObjectList().Cast().AsQueryable(); + + // create group & select expression. + CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => + { + gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); + finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")); + }); + CurrentQueryable = CurrentQueryable.Select(sb => + { + sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false); + finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}")); + sb.ToList("Records"); + }); + + // loop through the grouped records. + groupRecords = CurrentQueryable.Cast().ToList(); + } + else + { + // create group & select expression. + CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => + { + finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")); + }); + CurrentQueryable = CurrentQueryable.Select(sb => + { + finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}")); + sb.ToList("Records"); + }); + + // loop through the grouped records. + groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast(), cancellationToken); + } // now join them into logical collections var lastLists = new List<(List entities, IGroupQueryResult group)>(); @@ -127,15 +155,27 @@ namespace PoweredSoft.DynamicQuery return finalResult; } - public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) { - Reset(queryable, criteria); + Reset(queryable, criteria, new QueryExecutionOptions()); return FinalExecuteAsync(cancellationToken); } - public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) { - Reset(queryable, criteria); + Reset(queryable, criteria, new QueryExecutionOptions()); + return FinalExecuteAsync(cancellationToken); + } + + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) + { + Reset(queryable, criteria, options); + return FinalExecuteAsync(cancellationToken); + } + + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) + { + Reset(queryable, criteria, options); return FinalExecuteAsync(cancellationToken); } } diff --git a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs index 56efa21..4d5a271 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs @@ -20,24 +20,36 @@ namespace PoweredSoft.DynamicQuery protected IQueryCriteria Criteria { get; set; } protected IQueryable QueryableAtStart { get; private set; } protected IQueryable CurrentQueryable { get; set; } + protected IQueryExecutionOptions Options { get; private set; } + protected Type QueryableUnderlyingType => QueryableAtStart.ElementType; protected bool HasGrouping => Criteria.Groups?.Any() == true; protected bool HasPaging => Criteria.PageSize.HasValue && Criteria.PageSize > 0; - protected virtual void Reset(IQueryable queryable, IQueryCriteria criteria) + protected virtual void Reset(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options) { Criteria = criteria ?? throw new ArgumentNullException("criteria"); QueryableAtStart = queryable ?? throw new ArgumentNullException("queryable"); CurrentQueryable = QueryableAtStart; + Options = options; } protected virtual void CommonBeforeExecute() { + ApplyQueryExecutionOptionIncerceptors(); ApplyIncludeStrategyInterceptors(); ApplyBeforeFilterInterceptors(); ApplyFilters(); } + protected virtual void ApplyQueryExecutionOptionIncerceptors() + { + Options = Interceptors + .Where(t => t is IQueryExecutionOptionsInterceptor) + .Cast() + .Aggregate(Options, (prev, curr) => curr.InterceptQueryExecutionOptions(CurrentQueryable, prev)); + } + public virtual void AddInterceptor(IQueryInterceptor interceptor) { if (interceptor == null) throw new ArgumentNullException("interceptor");