Merge pull request #7 from PoweredSoft/feature/execution-options
execution options.
This commit is contained in:
		
						commit
						5be4b7e3ac
					
				| @ -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); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										8
									
								
								PoweredSoft.DynamicQuery.Core/IQueryExecutionOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								PoweredSoft.DynamicQuery.Core/IQueryExecutionOptions.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| namespace PoweredSoft.DynamicQuery.Core | ||||
| { | ||||
|     public interface IQueryExecutionOptions | ||||
|     { | ||||
|         bool GroupByInMemory { get; set; } | ||||
|         bool GroupByInMemoryNullCheck { get; set; } | ||||
|     } | ||||
| } | ||||
| @ -15,11 +15,15 @@ namespace PoweredSoft.DynamicQuery.Core | ||||
|     { | ||||
|         IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria); | ||||
|         IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria); | ||||
|         IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options); | ||||
|         IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options); | ||||
|     } | ||||
| 
 | ||||
|     public interface IQueryHandlerAsync : IInterceptableQueryHandler | ||||
|     { | ||||
|         Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); | ||||
|         Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); | ||||
|         Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default); | ||||
|         Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default); | ||||
|         Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default); | ||||
|         Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default); | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										8
									
								
								PoweredSoft.DynamicQuery.Core/QueryExecutionOptions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								PoweredSoft.DynamicQuery.Core/QueryExecutionOptions.cs
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| namespace PoweredSoft.DynamicQuery.Core | ||||
| { | ||||
|     public class QueryExecutionOptions : IQueryExecutionOptions | ||||
|     { | ||||
|         public bool GroupByInMemory { get; set; } = false; | ||||
|         public bool GroupByInMemoryNullCheck { get; set; } = false; | ||||
|     } | ||||
| } | ||||
| @ -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); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
| @ -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<OrderItem>; | ||||
|                 Assert.NotNull(groupedResult); | ||||
|  | ||||
| @ -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<IGroup> | ||||
|                     { | ||||
|                         new Group { Path = "Order.CustomerId" } | ||||
|                     }, | ||||
|                     Aggregates = new List<Core.IAggregate> | ||||
|                     { | ||||
|                         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); | ||||
|                 }); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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(); | ||||
|  | ||||
| @ -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<IGroup> | ||||
|                     { | ||||
|                         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<Order>().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<Ticket, InterceptorWithGroupingFakeModel>(ctx.Tickets, criteria); | ||||
|                 var result = queryHandler.Execute<Ticket, InterceptorWithGroupingFakeModel>(ctx.Tickets, criteria, new QueryExecutionOptions | ||||
|                 { | ||||
|                     GroupByInMemory = true | ||||
|                 }); | ||||
| 
 | ||||
|                 Assert.Equal(4, interceptor.Count); | ||||
|                 Assert.True(interceptor.Test); | ||||
|                 Assert.True(interceptor.Test2); | ||||
|  | ||||
| @ -14,12 +14,9 @@ namespace PoweredSoft.DynamicQuery.Test.Mock | ||||
|         public static void TestContextFor(string testName, Action<MockContext> action) | ||||
|         { | ||||
|             var options = new DbContextOptionsBuilder<MockContext>() | ||||
|                 .ConfigureWarnings(warnings =>  | ||||
|                     warnings.Ignore(RelationalEventId.QueryClientEvaluationWarning) | ||||
|                 ) | ||||
|                 .UseInMemoryDatabase(databaseName: testName).Options; | ||||
| 
 | ||||
|             using (var ctx = new MockContext(options)) | ||||
|             using var ctx = new MockContext(options); | ||||
|                 action(ctx); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -8,12 +8,12 @@ | ||||
| 
 | ||||
|   <ItemGroup> | ||||
|     <PackageReference Include="Bogus" Version="28.3.2" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.6" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.2.6" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.2.6" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.0.0" /> | ||||
|     <PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.0.0" /> | ||||
|     <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" /> | ||||
|     <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" /> | ||||
|     <PackageReference Include="PoweredSoft.Data.EntityFrameworkCore" Version="1.1.3" /> | ||||
|     <PackageReference Include="PoweredSoft.Data.EntityFrameworkCore" Version="2.0.0" /> | ||||
|     <PackageReference Include="xunit" Version="2.4.1" /> | ||||
|     <PackageReference Include="xunit.runner.visualstudio" Version="2.4.1"> | ||||
|       <PrivateAssets>all</PrivateAssets> | ||||
|  | ||||
| @ -43,14 +43,23 @@ namespace PoweredSoft.DynamicQuery | ||||
|             ApplySorting<TSource>(); | ||||
|             ApplyPaging<TSource>(); | ||||
| 
 | ||||
|             // 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<TSource>().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<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             Reset(queryable, criteria, new QueryExecutionOptions()); | ||||
|             return FinalExecute<TSource, TSource>(); | ||||
|         } | ||||
| 
 | ||||
|         public IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             Reset(queryable, criteria, new QueryExecutionOptions()); | ||||
|             return FinalExecute<TSource, TRecord>(); | ||||
|         } | ||||
| 
 | ||||
|         public IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options) | ||||
|         { | ||||
|             Reset(queryable, criteria, options); | ||||
|             return FinalExecute<TSource, TSource>(); | ||||
|         } | ||||
| 
 | ||||
|         public IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options) | ||||
|         { | ||||
|             Reset(queryable, criteria, options); | ||||
|             return FinalExecute<TSource, TRecord>(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -20,7 +20,7 @@ namespace PoweredSoft.DynamicQuery | ||||
|             AsyncQueryableService = asyncQueryableService; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual Task<IQueryExecutionResult<TRecord>> FinalExecuteAsync<TSource, TRecord>(CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         protected virtual Task<IQueryExecutionResult<TRecord>> FinalExecuteAsync<TSource, TRecord>(CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             CommonBeforeExecute<TSource>(); | ||||
|             return HasGrouping ? ExecuteAsyncGrouping<TSource, TRecord>(cancellationToken) : ExecuteAsyncNoGrouping<TSource, TRecord>(cancellationToken); | ||||
| @ -50,16 +50,44 @@ namespace PoweredSoft.DynamicQuery | ||||
|             ApplySorting<TSource>(); | ||||
|             ApplyPaging<TSource>(); | ||||
| 
 | ||||
|             // 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<DynamicClass> groupRecords; | ||||
| 
 | ||||
|             // loop through the grouped records. | ||||
|             var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast<DynamicClass>(), cancellationToken); | ||||
|             if (Options.GroupByInMemory) | ||||
|             { | ||||
|                 CurrentQueryable = CurrentQueryable.ToObjectList().Cast<TSource>().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<DynamicClass>().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<DynamicClass>(), cancellationToken); | ||||
|             } | ||||
| 
 | ||||
|             // now join them into logical collections | ||||
|             var lastLists = new List<(List<TSource> entities, IGroupQueryResult<TRecord> group)>(); | ||||
| @ -127,15 +155,27 @@ namespace PoweredSoft.DynamicQuery | ||||
|             return finalResult; | ||||
|         } | ||||
| 
 | ||||
|         public Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         public Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             Reset(queryable, criteria, new QueryExecutionOptions()); | ||||
|             return FinalExecuteAsync<TSource, TSource>(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         public Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             Reset(queryable, criteria, new QueryExecutionOptions()); | ||||
|             return FinalExecuteAsync<TSource, TRecord>(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             Reset(queryable, criteria, options); | ||||
|             return FinalExecuteAsync<TSource, TSource>(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default) | ||||
|         { | ||||
|             Reset(queryable, criteria, options); | ||||
|             return FinalExecuteAsync<TSource, TRecord>(cancellationToken); | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @ -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<TSource>() | ||||
|         { | ||||
|             ApplyQueryExecutionOptionIncerceptors(); | ||||
|             ApplyIncludeStrategyInterceptors<TSource>(); | ||||
|             ApplyBeforeFilterInterceptors<TSource>(); | ||||
|             ApplyFilters<TSource>(); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplyQueryExecutionOptionIncerceptors() | ||||
|         { | ||||
|             Options = Interceptors | ||||
|                 .Where(t => t is IQueryExecutionOptionsInterceptor) | ||||
|                 .Cast<IQueryExecutionOptionsInterceptor>() | ||||
|                 .Aggregate(Options, (prev, curr) => curr.InterceptQueryExecutionOptions(CurrentQueryable, prev)); | ||||
|         } | ||||
| 
 | ||||
|         public virtual void AddInterceptor(IQueryInterceptor interceptor) | ||||
|         { | ||||
|             if (interceptor == null) throw new ArgumentNullException("interceptor"); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user