Merge branch 'release/2.x-ready'
This commit is contained in:
		
						commit
						17e9434722
					
				| @ -8,7 +8,7 @@ | ||||
|     <RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery</RepositoryUrl> | ||||
|     <RepositoryType>github</RepositoryType> | ||||
|     <PackageTags>powered,soft,dynamic,criteria,query,builder,asp,net,core</PackageTags> | ||||
|     <Version>1.0.0$(VersionSuffix)</Version> | ||||
|     <Version>2.0.0$(VersionSuffix)</Version> | ||||
|     <PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro</PackageIconUrl> | ||||
|     <Product>PoweredSoft.DynamicQuery.AspNetCore</Product> | ||||
|     <Description>This projects makes it easier to use dynamic query in a asp.net core mvc project.</Description> | ||||
|  | ||||
| @ -24,4 +24,14 @@ namespace PoweredSoft.DynamicQuery.Core | ||||
|     { | ||||
|         Task AfterReadAsync(List<Tuple<T, object>> pairs, CancellationToken cancellationToken = default(CancellationToken)); | ||||
|     } | ||||
| 
 | ||||
|     public interface IAfterReadInterceptor<T, T2> : IQueryInterceptor | ||||
|     { | ||||
|         void AfterRead(List<Tuple<T, T2>> pairs); | ||||
|     } | ||||
| 
 | ||||
|     public interface IAfterReadInterceptorAsync<T, T2> : IQueryInterceptor | ||||
|     { | ||||
|         Task AfterReadAsync(List<Tuple<T, T2>> pairs, CancellationToken cancellationToken = default(CancellationToken)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,4 +13,9 @@ namespace PoweredSoft.DynamicQuery.Core | ||||
|     { | ||||
|         object InterceptResultTo(T entity); | ||||
|     } | ||||
| 
 | ||||
|     public interface IQueryConvertInterceptor<T, T2> : IQueryInterceptor | ||||
|     { | ||||
|         T2 InterceptResultTo(T entity); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -13,11 +13,13 @@ namespace PoweredSoft.DynamicQuery.Core | ||||
| 
 | ||||
|     public interface IQueryHandler : IInterceptableQueryHandler | ||||
|     { | ||||
|         IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria); | ||||
|         IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria); | ||||
|         IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria); | ||||
|     } | ||||
| 
 | ||||
|     public interface IQueryHandlerAsync : IInterceptableQueryHandler | ||||
|     { | ||||
|         Task<IQueryExecutionResult> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); | ||||
|         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)); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -11,23 +11,35 @@ namespace PoweredSoft.DynamicQuery.Core | ||||
|         object Value { get; set; } | ||||
|     } | ||||
| 
 | ||||
|     public interface IQueryResult | ||||
|     public interface IQueryResult<TRecord> | ||||
|     { | ||||
|         List<IAggregateResult> Aggregates { get; } | ||||
|         List<object> Data { get; } | ||||
|         List<TRecord> Data { get; } | ||||
|     } | ||||
| 
 | ||||
|     public interface IGroupQueryResult : IQueryResult | ||||
|     public interface IGroupQueryResult<TRecord> : IQueryResult<TRecord> | ||||
|     { | ||||
|         string GroupPath { get; set; } | ||||
|         object GroupValue { get; set; } | ||||
|         bool HasSubGroups { get; } | ||||
|         List<IGroupQueryResult<TRecord>> SubGroups { get; set; } | ||||
|     } | ||||
| 
 | ||||
|     public interface IQueryExecutionResult : IQueryResult | ||||
|     public interface IQueryExecutionResultPaging | ||||
|     { | ||||
|         long TotalRecords { get; set; } | ||||
|         long? NumberOfPages { get; set; } | ||||
|     } | ||||
| 
 | ||||
|     public interface IQueryExecutionResult<TRecord> : IQueryResult<TRecord>, IQueryExecutionResultPaging | ||||
|     { | ||||
| 
 | ||||
|     } | ||||
| 
 | ||||
|     public interface IQueryExecutionGroupResult<TRecord> : IQueryExecutionResult<TRecord> | ||||
|     { | ||||
|         List<IGroupQueryResult<TRecord>> Groups { get; set; } | ||||
|     } | ||||
| 
 | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -8,7 +8,7 @@ | ||||
|     <RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery.Core/</RepositoryUrl> | ||||
|     <RepositoryType>github</RepositoryType> | ||||
|     <PackageTags>powered,soft,dynamic,criteria,query,builder</PackageTags> | ||||
|     <Version>1.0.0$(VersionSuffix)</Version> | ||||
|     <Version>2.0.0$(VersionSuffix)</Version> | ||||
|     <PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro</PackageIconUrl> | ||||
|     <Product>PoweredSoft.DynamicQuery.Core</Product> | ||||
|     <Description>core abstractions</Description> | ||||
|  | ||||
| @ -114,7 +114,11 @@ namespace PoweredSoft.DynamicQuery.Test | ||||
| 
 | ||||
|                 var queryHandler = new QueryHandler(); | ||||
|                 var result = queryHandler.Execute(ctx.OrderItems, criteria); | ||||
|                 var groups = result.Data.Cast<IGroupQueryResult>().ToList(); | ||||
| 
 | ||||
|                 var groupedResult = result as IQueryExecutionGroupResult<OrderItem>; | ||||
|                 Assert.NotNull(groupedResult); | ||||
| 
 | ||||
|                 var groups = groupedResult.Groups; | ||||
| 
 | ||||
|                 // validate group and aggregates of groups. | ||||
|                 Assert.Equal(groups.Count, shouldResults.Count); | ||||
|  | ||||
| @ -1,6 +1,7 @@ | ||||
| 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; | ||||
| @ -65,7 +66,7 @@ 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 groups = result.Data.Cast<IGroupQueryResult>().ToList(); | ||||
|                 var groups = result.GroupedResult().Groups; | ||||
| 
 | ||||
|                 // validate group and aggregates of groups. | ||||
|                 Assert.Equal(groups.Count, shouldResults.Count); | ||||
|  | ||||
| @ -55,6 +55,22 @@ namespace PoweredSoft.DynamicQuery.Test | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         private class MockQueryConvertGenericInterceptor2 : | ||||
|             IQueryConvertInterceptor<Customer, CustomerModel> | ||||
|         { | ||||
|             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() | ||||
|         { | ||||
| @ -63,7 +79,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<Customer, CustomerModel>(ctx.Customers, criteria); | ||||
|                 Assert.All(result.Data, t => Assert.IsType<CustomerModel>(t)); | ||||
|             }); | ||||
|         } | ||||
| @ -76,7 +92,20 @@ 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<Customer, CustomerModel>(ctx.Customers, criteria); | ||||
|                 Assert.All(result.Data, t => Assert.IsType<CustomerModel>(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<Customer, CustomerModel>(ctx.Customers, criteria); | ||||
|                 Assert.All(result.Data, t => Assert.IsType<CustomerModel>(t)); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
| @ -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<IGroupQueryResult>().Select(t => t.GroupValue).ToList(); | ||||
| 
 | ||||
|                 var groupedResult = result.GroupedResult(); | ||||
|                 var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList(); | ||||
|                 Assert.Equal(expected, actual); | ||||
|             }); | ||||
|         } | ||||
|  | ||||
| @ -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<IGroupQueryResult>().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<Ticket, InterceptorWithGroupingFakeModel>(ctx.Tickets, criteria); | ||||
|                 Assert.Equal(4, interceptor.Count); | ||||
|                 Assert.True(interceptor.Test); | ||||
|                 Assert.True(interceptor.Test2); | ||||
|  | ||||
							
								
								
									
										18
									
								
								PoweredSoft.DynamicQuery/Extensions/GroupResultExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								PoweredSoft.DynamicQuery/Extensions/GroupResultExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @ -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<TRecord> GroupedResult<TRecord>(this IQueryExecutionResult<TRecord> source) | ||||
|         { | ||||
|             if (source is IQueryExecutionGroupResult<TRecord> ret) | ||||
|                 return ret; | ||||
| 
 | ||||
|             throw new Exception("this result is not a grouped result");                 | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @ -8,7 +8,7 @@ | ||||
|     <RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery</RepositoryUrl> | ||||
|     <RepositoryType>github</RepositoryType> | ||||
|     <PackageTags>powered,soft,dynamic,criteria,query,builder</PackageTags> | ||||
|     <Version>1.0.0$(VersionSuffix)</Version> | ||||
|     <Version>2.0.0$(VersionSuffix)</Version> | ||||
|     <PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro</PackageIconUrl> | ||||
|     <Product>PoweredSoft.DynamicQuery</Product> | ||||
|     <Description>dynamic query based on string path very usefull for network requests.</Description> | ||||
|  | ||||
| @ -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<T>() | ||||
|         protected virtual IQueryExecutionResult<TRecord> FinalExecute<TSource, TRecord>() | ||||
|         { | ||||
|             CommonBeforeExecute<T>(); | ||||
|             return HasGrouping ? ExecuteGrouping<T>() : ExecuteNoGrouping<T>(); | ||||
|             CommonBeforeExecute<TSource>(); | ||||
|             return HasGrouping ? ExecuteGrouping<TSource, TRecord>() : ExecuteNoGrouping<TSource, TRecord>(); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         protected virtual IQueryExecutionResult ExecuteGrouping<T>() | ||||
|         protected virtual IQueryExecutionResult<TRecord> ExecuteGrouping<TSource, TRecord>() | ||||
|         { | ||||
|             var result = new QueryExecutionResult(); | ||||
|             var result = new QueryExecutionGroupResult<TRecord>(); | ||||
| 
 | ||||
|             // 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<T>(g)).ToList(); | ||||
|             var finalGroups = Criteria.Groups.Select(g => InterceptGroup<TSource>(g)).ToList(); | ||||
| 
 | ||||
|             // get the aggregates. | ||||
|             var aggregateResults = FetchAggregates<T>(finalGroups); | ||||
|             var aggregateResults = FetchAggregates<TSource>(finalGroups); | ||||
| 
 | ||||
|             // sorting. | ||||
|             finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); | ||||
| 
 | ||||
|             // apply sorting and paging. | ||||
|             ApplySorting<T>(); | ||||
|             ApplyPaging<T>(); | ||||
|             ApplySorting<TSource>(); | ||||
|             ApplyPaging<TSource>(); | ||||
| 
 | ||||
|             // 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<List<object>>(); | ||||
|             result.Data = RecursiveRegroup<T>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); | ||||
|             var lastLists = new List<(List<TSource> source, IGroupQueryResult<TRecord> group)>(); | ||||
|             result.Groups = RecursiveRegroup<TSource, TRecord>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); | ||||
| 
 | ||||
|             // intercept grouped by. | ||||
|             QueryInterceptToGrouped<T>(lastLists).Wait(); | ||||
|             QueryInterceptToGrouped<TSource, TRecord>(lastLists).Wait(); | ||||
| 
 | ||||
|             result.Aggregates = CalculateTotalAggregate<T>(queryableAfterFilters); | ||||
|             result.Aggregates = CalculateTotalAggregate<TSource>(queryableAfterFilters); | ||||
|             return result; | ||||
|         } | ||||
|         protected virtual List<IAggregateResult> CalculateTotalAggregate<T>(IQueryable queryableAfterFilters) | ||||
|         protected virtual List<IAggregateResult> CalculateTotalAggregate<TSource>(IQueryable queryableAfterFilters) | ||||
|         { | ||||
|             if (!Criteria.Aggregates.Any()) | ||||
|                 return null; | ||||
| 
 | ||||
|             IQueryable selectExpression = CreateTotalAggregateSelectExpression<T>(queryableAfterFilters); | ||||
|             IQueryable selectExpression = CreateTotalAggregateSelectExpression<TSource>(queryableAfterFilters); | ||||
|             var aggregateResult = selectExpression.ToDynamicClassList().FirstOrDefault(); | ||||
|             return MaterializeCalculateTotalAggregateResult(aggregateResult); | ||||
|         } | ||||
|          | ||||
|         protected virtual List<List<DynamicClass>> FetchAggregates<T>(List<IGroup> finalGroups) | ||||
|         protected virtual List<List<DynamicClass>> FetchAggregates<TSource>(List<IGroup> finalGroups) | ||||
|         { | ||||
|             if (!Criteria.Aggregates.Any()) | ||||
|                 return null; | ||||
| @ -85,7 +82,7 @@ namespace PoweredSoft.DynamicQuery | ||||
|             var previousGroups = new List<IGroup>(); | ||||
|             var ret = finalGroups.Select(fg => | ||||
|             { | ||||
|                 IQueryable selectExpression = CreateFetchAggregateSelectExpression<T>(fg, previousGroups); | ||||
|                 IQueryable selectExpression = CreateFetchAggregateSelectExpression<TSource>(fg, previousGroups); | ||||
|                 var aggregateResult = selectExpression.ToDynamicClassList(); | ||||
|                 previousGroups.Add(fg); | ||||
|                 return aggregateResult; | ||||
| @ -93,9 +90,9 @@ namespace PoweredSoft.DynamicQuery | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual IQueryExecutionResult ExecuteNoGrouping<T>() | ||||
|         protected virtual IQueryExecutionResult<TRecord> ExecuteNoGrouping<TSource, TRecord>() | ||||
|         { | ||||
|             var result = new QueryExecutionResult(); | ||||
|             var result = new QueryExecutionResult<TRecord>(); | ||||
| 
 | ||||
|             // after filter queryable | ||||
|             var afterFilterQueryable = CurrentQueryable; | ||||
| @ -105,25 +102,30 @@ namespace PoweredSoft.DynamicQuery | ||||
|             CalculatePageCount(result); | ||||
| 
 | ||||
|             // sorts and paging. | ||||
|             ApplySorting<T>(); | ||||
|             ApplyPaging<T>(); | ||||
|             ApplySorting<TSource>(); | ||||
|             ApplyPaging<TSource>(); | ||||
| 
 | ||||
|             // data. | ||||
|             var entities = ((IQueryable<T>)CurrentQueryable).ToList(); | ||||
|             var records = InterceptConvertTo<T>(entities).Result; | ||||
|             var entities = ((IQueryable<TSource>)CurrentQueryable).ToList(); | ||||
|             var records = InterceptConvertTo<TSource, TRecord>(entities).Result; | ||||
|             result.Data = records; | ||||
| 
 | ||||
|             // aggregates. | ||||
|             result.Aggregates = CalculateTotalAggregate<T>(afterFilterQueryable); | ||||
|             result.Aggregates = CalculateTotalAggregate<TSource>(afterFilterQueryable); | ||||
| 
 | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         public virtual IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) | ||||
|         public IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             return ExecuteReflected(); | ||||
|             return FinalExecute<TSource, TSource>(); | ||||
|         } | ||||
| 
 | ||||
|         public IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             return FinalExecute<TSource, TRecord>(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -3,60 +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<IQueryExecutionResult> ExecuteAsyncReflected(CancellationToken cancellationToken) => (Task<IQueryExecutionResult>)ExecuteAsyncGeneric.MakeGenericMethod(QueryableUnderlyingType).Invoke(this, new object[] { cancellationToken }); | ||||
| 
 | ||||
|         public QueryHandlerAsync(IAsyncQueryableService asyncQueryableService) | ||||
|         { | ||||
|             AsyncQueryableService = asyncQueryableService; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual Task<IQueryExecutionResult> ExecuteAsync<T>(CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         protected virtual Task<IQueryExecutionResult<TRecord>> FinalExecuteAsync<TSource, TRecord>(CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         { | ||||
|             CommonBeforeExecute<T>(); | ||||
|             return HasGrouping ? ExecuteAsyncGrouping<T>(cancellationToken) : ExecuteAsyncNoGrouping<T>(cancellationToken); | ||||
|             CommonBeforeExecute<TSource>(); | ||||
|             return HasGrouping ? ExecuteAsyncGrouping<TSource, TRecord>(cancellationToken) : ExecuteAsyncNoGrouping<TSource, TRecord>(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public Task<IQueryExecutionResult> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         protected virtual async Task<IQueryExecutionResult<TRecord>> ExecuteAsyncGrouping<TSource, TRecord>(CancellationToken cancellationToken) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             return ExecuteAsyncReflected(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual async Task<IQueryExecutionResult> ExecuteAsyncGrouping<T>(CancellationToken cancellationToken) | ||||
|         { | ||||
|             var result = new QueryExecutionResult(); | ||||
|             var result = new QueryExecutionGroupResult<TRecord>(); | ||||
| 
 | ||||
|             // preserve queryable. | ||||
|             var queryableAfterFilters = CurrentQueryable; | ||||
| 
 | ||||
|             // async. | ||||
|             result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable<T>)queryableAfterFilters, cancellationToken); | ||||
|             result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable<TSource>)queryableAfterFilters, cancellationToken); | ||||
|             CalculatePageCount(result); | ||||
| 
 | ||||
|             // intercept groups in advance to avoid doing it more than once :) | ||||
|             var finalGroups = Criteria.Groups.Select(g => InterceptGroup<T>(g)).ToList(); | ||||
|             var finalGroups = Criteria.Groups.Select(g => InterceptGroup<TSource>(g)).ToList(); | ||||
| 
 | ||||
|             // get the aggregates. | ||||
|             var aggregateResults = await FetchAggregatesAsync<T>(finalGroups, cancellationToken); | ||||
|             var aggregateResults = await FetchAggregatesAsync<TSource>(finalGroups, cancellationToken); | ||||
| 
 | ||||
|             // sorting. | ||||
|             finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); | ||||
| 
 | ||||
|             // apply sorting and paging. | ||||
|             ApplySorting<T>(); | ||||
|             ApplyPaging<T>(); | ||||
|             ApplySorting<TSource>(); | ||||
|             ApplyPaging<TSource>(); | ||||
| 
 | ||||
|             // create group & select expression. | ||||
|             CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); | ||||
| @ -70,52 +62,52 @@ namespace PoweredSoft.DynamicQuery | ||||
|             var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast<DynamicClass>(), cancellationToken); | ||||
| 
 | ||||
|             // now join them into logical collections | ||||
|             var lastLists = new List<List<object>>(); | ||||
|             result.Data = RecursiveRegroup<T>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); | ||||
|             var lastLists = new List<(List<TSource> entities, IGroupQueryResult<TRecord> group)>(); | ||||
|             result.Groups = RecursiveRegroup<TSource, TRecord>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); | ||||
| 
 | ||||
|             // converted to grouped by. | ||||
|             await QueryInterceptToGrouped<T>(lastLists); | ||||
|             await QueryInterceptToGrouped<TSource, TRecord>(lastLists); | ||||
| 
 | ||||
|             result.Aggregates = await CalculateTotalAggregateAsync<T>(queryableAfterFilters, cancellationToken); | ||||
|             result.Aggregates = await CalculateTotalAggregateAsync<TSource>(queryableAfterFilters, cancellationToken); | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         protected async Task<IQueryExecutionResult> ExecuteAsyncNoGrouping<T>(CancellationToken cancellationToken) | ||||
|         protected async Task<IQueryExecutionResult<TRecord>> ExecuteAsyncNoGrouping<TSource, TRecord>(CancellationToken cancellationToken) | ||||
|         { | ||||
|             var result = new QueryExecutionResult(); | ||||
|             var result = new QueryExecutionResult<TRecord>(); | ||||
| 
 | ||||
|             // after filter queryable | ||||
|             IQueryable<T> afterFilterQueryable = (IQueryable<T>)CurrentQueryable; | ||||
|             IQueryable<TSource> afterFilterQueryable = (IQueryable<TSource>)CurrentQueryable; | ||||
| 
 | ||||
|             // total records. | ||||
|             result.TotalRecords = await AsyncQueryableService.LongCountAsync(afterFilterQueryable, cancellationToken); | ||||
|             CalculatePageCount(result); | ||||
| 
 | ||||
|             // sorts and paging. | ||||
|             ApplySorting<T>(); | ||||
|             ApplyPaging<T>(); | ||||
|             ApplySorting<TSource>(); | ||||
|             ApplyPaging<TSource>(); | ||||
| 
 | ||||
|             // data. | ||||
|             var entities = await AsyncQueryableService.ToListAsync(((IQueryable<T>)CurrentQueryable), cancellationToken); | ||||
|             var records = await InterceptConvertTo<T>(entities); | ||||
|             var entities = await AsyncQueryableService.ToListAsync(((IQueryable<TSource>)CurrentQueryable), cancellationToken); | ||||
|             var records = await InterceptConvertTo<TSource, TRecord>(entities); | ||||
|             result.Data = records; | ||||
| 
 | ||||
|             // aggregates. | ||||
|             result.Aggregates = await CalculateTotalAggregateAsync<T>(afterFilterQueryable, cancellationToken); | ||||
|             result.Aggregates = await CalculateTotalAggregateAsync<TSource>(afterFilterQueryable, cancellationToken); | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual async Task<List<IAggregateResult>> CalculateTotalAggregateAsync<T>(IQueryable queryableAfterFilters, CancellationToken cancellationToken) | ||||
|         protected virtual async Task<List<IAggregateResult>> CalculateTotalAggregateAsync<TSource>(IQueryable queryableAfterFilters, CancellationToken cancellationToken) | ||||
|         { | ||||
|             if (!Criteria.Aggregates.Any()) | ||||
|                 return null; | ||||
| 
 | ||||
|             IQueryable selectExpression = CreateTotalAggregateSelectExpression<T>(queryableAfterFilters); | ||||
|             IQueryable selectExpression = CreateTotalAggregateSelectExpression<TSource>(queryableAfterFilters); | ||||
|             var aggregateResult = await AsyncQueryableService.FirstOrDefaultAsync(selectExpression.Cast<DynamicClass>()); | ||||
|             return MaterializeCalculateTotalAggregateResult(aggregateResult); | ||||
|         } | ||||
| 
 | ||||
|         protected async virtual Task<List<List<DynamicClass>>> FetchAggregatesAsync<T>(List<IGroup> finalGroups, CancellationToken cancellationToken) | ||||
|         protected async virtual Task<List<List<DynamicClass>>> FetchAggregatesAsync<TSource>(List<IGroup> finalGroups, CancellationToken cancellationToken) | ||||
|         { | ||||
|             if (!Criteria.Aggregates.Any()) | ||||
|                 return null; | ||||
| @ -124,7 +116,7 @@ namespace PoweredSoft.DynamicQuery | ||||
| 
 | ||||
|             var whenAllResult = await Task.WhenAll(finalGroups.Select(fg => | ||||
|             { | ||||
|                 IQueryable selectExpression = CreateFetchAggregateSelectExpression<T>(fg, previousGroups); | ||||
|                 IQueryable selectExpression = CreateFetchAggregateSelectExpression<TSource>(fg, previousGroups); | ||||
|                 var selectExpressionCasted = selectExpression.Cast<DynamicClass>(); | ||||
|                 var aggregateResult = AsyncQueryableService.ToListAsync(selectExpressionCasted, cancellationToken); | ||||
|                 previousGroups.Add(fg); | ||||
| @ -134,5 +126,17 @@ namespace PoweredSoft.DynamicQuery | ||||
|             var finalResult = whenAllResult.ToList(); | ||||
|             return finalResult; | ||||
|         } | ||||
| 
 | ||||
|         public Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             return FinalExecuteAsync<TSource, TSource>(cancellationToken); | ||||
|         } | ||||
| 
 | ||||
|         public Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) | ||||
|         { | ||||
|             Reset(queryable, criteria); | ||||
|             return FinalExecuteAsync<TSource, TRecord>(cancellationToken); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -31,11 +31,11 @@ namespace PoweredSoft.DynamicQuery | ||||
|             CurrentQueryable = QueryableAtStart; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void CommonBeforeExecute<T>() | ||||
|         protected virtual void CommonBeforeExecute<TSource>() | ||||
|         { | ||||
|             ApplyIncludeStrategyInterceptors<T>(); | ||||
|             ApplyBeforeFilterInterceptors<T>(); | ||||
|             ApplyFilters<T>(); | ||||
|             ApplyIncludeStrategyInterceptors<TSource>(); | ||||
|             ApplyBeforeFilterInterceptors<TSource>(); | ||||
|             ApplyFilters<TSource>(); | ||||
|         } | ||||
| 
 | ||||
|         public virtual void AddInterceptor(IQueryInterceptor interceptor) | ||||
| @ -46,7 +46,7 @@ namespace PoweredSoft.DynamicQuery | ||||
|                 Interceptors.Add(interceptor); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual IGroup InterceptGroup<T>(IGroup group) | ||||
|         protected virtual IGroup InterceptGroup<TSource>(IGroup group) | ||||
|         { | ||||
|             var ret = Interceptors | ||||
|                 .Where(t => t is IGroupInterceptor) | ||||
| @ -57,28 +57,28 @@ namespace PoweredSoft.DynamicQuery | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
|         protected virtual void ApplyPaging<T>() | ||||
|         protected virtual void ApplyPaging<TSource>() | ||||
|         { | ||||
|             if (!HasPaging) | ||||
|                 return; | ||||
| 
 | ||||
|             var q = (IQueryable<T>) CurrentQueryable; | ||||
|             var q = (IQueryable<TSource>) CurrentQueryable; | ||||
|             var skip = ((Criteria.Page ?? 1) - 1) * Criteria.PageSize.Value; | ||||
|             CurrentQueryable = q.Skip(skip).Take(Criteria.PageSize.Value); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplySorting<T>() | ||||
|         protected virtual void ApplySorting<TSource>() | ||||
|         { | ||||
|             if (Criteria.Sorts?.Any() != true) | ||||
|             { | ||||
|                 ApplyNoSortInterceptor<T>(); | ||||
|                 ApplyNoSortInterceptor<TSource>(); | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             bool isAppending = false; | ||||
|             Criteria.Sorts.ForEach(sort => | ||||
|             { | ||||
|                 var transformedSort = InterceptSort<T>(sort); | ||||
|                 var transformedSort = InterceptSort<TSource>(sort); | ||||
|                 if (transformedSort.Count == 0) | ||||
|                     return; | ||||
| 
 | ||||
| @ -90,7 +90,7 @@ namespace PoweredSoft.DynamicQuery | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         protected DynamicClass FindMatchingAggregateResult(List<List<DynamicClass>> aggregateResults, List<IGroup> groups, List<IGroupQueryResult> groupResults) | ||||
|         protected DynamicClass FindMatchingAggregateResult<TRecord>(List<List<DynamicClass>> aggregateResults, List<IGroup> groups, List<IGroupQueryResult<TRecord>> groupResults) | ||||
|         { | ||||
|             var groupIndex = groupResults.Count - 1; | ||||
|             var aggregateLevel = aggregateResults[groupIndex]; | ||||
| @ -108,7 +108,7 @@ namespace PoweredSoft.DynamicQuery | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual IQueryable CreateFetchAggregateSelectExpression<T>(IGroup finalGroup, List<IGroup> previousGroups) | ||||
|         protected virtual IQueryable CreateFetchAggregateSelectExpression<TSource>(IGroup finalGroup, List<IGroup> 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<T>(a); | ||||
|                     var fa = InterceptAggregate<TSource>(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<T>(IQueryable queryableAfterFilters) | ||||
|         protected virtual IQueryable CreateTotalAggregateSelectExpression<TSource>(IQueryable queryableAfterFilters) | ||||
|         { | ||||
|             var groupExpression = queryableAfterFilters.EmptyGroupBy(QueryableUnderlyingType); | ||||
|             var selectExpression = groupExpression.Select(sb => | ||||
|             { | ||||
|                 Criteria.Aggregates.ForEach((a, index) => | ||||
|                 { | ||||
|                     var fa = InterceptAggregate<T>(a); | ||||
|                     var fa = InterceptAggregate<TSource>(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<T>(IAggregate aggregate) | ||||
|         protected virtual IAggregate InterceptAggregate<TSource>(IAggregate aggregate) | ||||
|         { | ||||
|             var ret = Interceptors | ||||
|                 .Where(t => t is IAggregateInterceptor) | ||||
| @ -182,47 +182,53 @@ namespace PoweredSoft.DynamicQuery | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual async Task<List<object>> InterceptConvertTo<T>(List<T> entities) | ||||
|         protected virtual async Task<List<TRecord>> InterceptConvertTo<TSource, TRecord>(List<TSource> entities) | ||||
|         { | ||||
|             await AfterEntityReadInterceptors(entities); | ||||
| 
 | ||||
|             var objects = entities.Cast<object>().ToList(); | ||||
|             for (var i = 0; i < objects.Count; i++) | ||||
|                 objects[i] = InterceptConvertToObject<T>(objects[i]); | ||||
|             var ret = new List<TRecord>(); | ||||
|             for (var i = 0; i < entities.Count; i++) | ||||
|                 ret.Add(InterceptConvertToObject<TSource, TRecord>(entities[i])); | ||||
| 
 | ||||
|             var pairs = entities.Select((t, index) => Tuple.Create(t, objects[index])).ToList(); | ||||
|             await AfterReadInterceptors<T>(pairs); | ||||
|             var pairs = entities.Select((t, index) => Tuple.Create(t, ret[index])).ToList(); | ||||
|             await AfterReadInterceptors<TSource, TRecord>(pairs); | ||||
| 
 | ||||
|             return objects; | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual async Task AfterEntityReadInterceptors<T>(List<T> entities) | ||||
|         protected virtual async Task AfterEntityReadInterceptors<TSource>(List<TSource> entities) | ||||
|         { | ||||
|             Interceptors | ||||
|                 .Where(t => t is IAfterReadEntityInterceptor<T>) | ||||
|                 .Cast<IAfterReadEntityInterceptor<T>>() | ||||
|                 .Where(t => t is IAfterReadEntityInterceptor<TSource>) | ||||
|                 .Cast<IAfterReadEntityInterceptor<TSource>>() | ||||
|                 .ToList() | ||||
|                 .ForEach(t => t.AfterReadEntity(entities)); | ||||
| 
 | ||||
|             var asyncInterceptors = Interceptors.Where(t => t is IAfterReadEntityInterceptorAsync<T>).Cast<IAfterReadEntityInterceptorAsync<T>>(); | ||||
|             var asyncInterceptors = Interceptors.Where(t => t is IAfterReadEntityInterceptorAsync<TSource>).Cast<IAfterReadEntityInterceptorAsync<TSource>>(); | ||||
|             foreach (var interceptor in asyncInterceptors) | ||||
|                 await interceptor.AfterReadEntityAsync(entities); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual async Task AfterReadInterceptors<T>(List<Tuple<T, object>> pairs) | ||||
|         protected virtual async Task AfterReadInterceptors<TSource, TRecord>(List<Tuple<TSource, TRecord>> pairs) | ||||
|         { | ||||
|             Interceptors | ||||
|                 .Where(t => t is IAfterReadInterceptor<T>) | ||||
|                 .Cast<IAfterReadInterceptor<T>>() | ||||
|                 .ToList() | ||||
|                 .ForEach(t => t.AfterRead(pairs)); | ||||
|             var objPair = pairs.Select(t => Tuple.Create(t.Item1, (object)t.Item2)).ToList(); | ||||
| 
 | ||||
|             var asyncInterceptors = Interceptors.Where(t => t is IAfterReadInterceptorAsync<T>).Cast<IAfterReadInterceptorAsync<T>>(); | ||||
|             Interceptors | ||||
|                 .Where(t => t is IAfterReadInterceptor<TSource>) | ||||
|                 .Cast<IAfterReadInterceptor<TSource>>() | ||||
|                 .ToList() | ||||
|                 .ForEach(t => t.AfterRead(objPair)); | ||||
| 
 | ||||
|             var asyncInterceptors = Interceptors.Where(t => t is IAfterReadInterceptorAsync<TSource>).Cast<IAfterReadInterceptorAsync<TSource>>(); | ||||
|             foreach (var interceptor in asyncInterceptors) | ||||
|                 await interceptor.AfterReadAsync(objPair); | ||||
| 
 | ||||
|             var asyncInterceptors2 = Interceptors.Where(t => t is IAfterReadInterceptorAsync<TSource, TRecord>).Cast<IAfterReadInterceptorAsync<TSource, TRecord>>(); | ||||
|             foreach (var interceptor in asyncInterceptors2) | ||||
|                 await interceptor.AfterReadAsync(pairs); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual object InterceptConvertToObject<T>(object o) | ||||
|         protected virtual TRecord InterceptConvertToObject<TSource, TRecord>(object o) | ||||
|         { | ||||
|             o = Interceptors | ||||
|                 .Where(t => t is IQueryConvertInterceptor) | ||||
| @ -230,20 +236,31 @@ namespace PoweredSoft.DynamicQuery | ||||
|                 .Aggregate(o, (prev, interceptor) => interceptor.InterceptResultTo(prev)); | ||||
| 
 | ||||
|             o = Interceptors | ||||
|                 .Where(t => t is IQueryConvertInterceptor<T>) | ||||
|                 .Cast<IQueryConvertInterceptor<T>>() | ||||
|                 .Where(t => t is IQueryConvertInterceptor<TSource>) | ||||
|                 .Cast<IQueryConvertInterceptor<TSource>>() | ||||
|                 .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; | ||||
|             o = Interceptors | ||||
|                .Where(t => t is IQueryConvertInterceptor<TSource, TRecord>) | ||||
|                .Cast<IQueryConvertInterceptor<TSource, TRecord>>() | ||||
|                .Aggregate(o, (prev, interceptor) => | ||||
|                { | ||||
|                    if (prev is TSource) | ||||
|                        return interceptor.InterceptResultTo((TSource)prev); | ||||
| 
 | ||||
|                    return o; | ||||
|                }); | ||||
| 
 | ||||
|             return (TRecord)o; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual List<ISort> InterceptSort<T>(ISort sort) | ||||
|         protected virtual List<ISort> InterceptSort<TSource>(ISort sort) | ||||
|         { | ||||
|             var original = new List<ISort>() | ||||
|             { | ||||
| @ -259,15 +276,15 @@ namespace PoweredSoft.DynamicQuery | ||||
|             return ret.ToList(); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplyNoSortInterceptor<T>() | ||||
|         protected virtual void ApplyNoSortInterceptor<TSource>() | ||||
|         { | ||||
|             CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor) | ||||
|                 .Cast<INoSortInterceptor>() | ||||
|                 .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); | ||||
| 
 | ||||
|             CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor<T>) | ||||
|                 .Cast<INoSortInterceptor<T>>() | ||||
|                 .Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); | ||||
|             CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor<TSource>) | ||||
|                 .Cast<INoSortInterceptor<TSource>>() | ||||
|                 .Aggregate((IQueryable<TSource>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); | ||||
|         } | ||||
| 
 | ||||
| 
 | ||||
| @ -292,40 +309,40 @@ namespace PoweredSoft.DynamicQuery | ||||
|             return ret.Value; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplyFilters<T>() | ||||
|         protected virtual void ApplyFilters<TSource>() | ||||
|         { | ||||
|             if (true != Criteria.Filters?.Any()) | ||||
|                 return; | ||||
| 
 | ||||
|             CurrentQueryable = CurrentQueryable.Query(whereBuilder => | ||||
|             { | ||||
|                 Criteria.Filters.ForEach(filter => ApplyFilter<T>(whereBuilder, filter)); | ||||
|                 Criteria.Filters.ForEach(filter => ApplyFilter<TSource>(whereBuilder, filter)); | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplyFilter<T>(WhereBuilder whereBuilder, IFilter filter) | ||||
|         protected virtual void ApplyFilter<TSource>(WhereBuilder whereBuilder, IFilter filter) | ||||
|         { | ||||
|             var transformedFilter = InterceptFilter<T>(filter); | ||||
|             var transformedFilter = InterceptFilter<TSource>(filter); | ||||
|             if (transformedFilter is ISimpleFilter) | ||||
|                 ApplySimpleFilter<T>(whereBuilder, transformedFilter as ISimpleFilter); | ||||
|                 ApplySimpleFilter<TSource>(whereBuilder, transformedFilter as ISimpleFilter); | ||||
|             else if (transformedFilter is ICompositeFilter) | ||||
|                 AppleCompositeFilter<T>(whereBuilder, transformedFilter as ICompositeFilter); | ||||
|                 AppleCompositeFilter<TSource>(whereBuilder, transformedFilter as ICompositeFilter); | ||||
|             else | ||||
|                 throw new NotSupportedException(); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void AppleCompositeFilter<T>(WhereBuilder whereBuilder, ICompositeFilter filter) | ||||
|         protected virtual void AppleCompositeFilter<TSource>(WhereBuilder whereBuilder, ICompositeFilter filter) | ||||
|         { | ||||
|             whereBuilder.SubQuery(subWhereBuilder => filter.Filters.ForEach(subFilter => ApplyFilter<T>(subWhereBuilder, subFilter)), filter.And == true); | ||||
|             whereBuilder.SubQuery(subWhereBuilder => filter.Filters.ForEach(subFilter => ApplyFilter<TSource>(subWhereBuilder, subFilter)), filter.And == true); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplySimpleFilter<T>(WhereBuilder whereBuilder, ISimpleFilter filter) | ||||
|         protected virtual void ApplySimpleFilter<TSource>(WhereBuilder whereBuilder, ISimpleFilter filter) | ||||
|         { | ||||
|             var resolvedConditionOperator = ResolveConditionOperatorFrom(filter.Type); | ||||
|             whereBuilder.Compare(filter.Path, resolvedConditionOperator, filter.Value, and: filter.And == true); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual IFilter InterceptFilter<T>(IFilter filter) | ||||
|         protected virtual IFilter InterceptFilter<TSource>(IFilter filter) | ||||
|         { | ||||
|             var ret = Interceptors.Where(t => t is IFilterInterceptor) | ||||
|                 .Cast<IFilterInterceptor>() | ||||
| @ -334,7 +351,7 @@ namespace PoweredSoft.DynamicQuery | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplyIncludeStrategyInterceptors<T>() | ||||
|         protected virtual void ApplyIncludeStrategyInterceptors<TSource>() | ||||
|         { | ||||
|             CurrentQueryable = Interceptors | ||||
|                 .Where(t => t is IIncludeStrategyInterceptor) | ||||
| @ -342,12 +359,12 @@ namespace PoweredSoft.DynamicQuery | ||||
|                 .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); | ||||
| 
 | ||||
|             CurrentQueryable = Interceptors | ||||
|                 .Where(t => t is IIncludeStrategyInterceptor<T>) | ||||
|                 .Cast<IIncludeStrategyInterceptor<T>>() | ||||
|                 .Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); | ||||
|                 .Where(t => t is IIncludeStrategyInterceptor<TSource>) | ||||
|                 .Cast<IIncludeStrategyInterceptor<TSource>>() | ||||
|                 .Aggregate((IQueryable<TSource>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual void ApplyBeforeFilterInterceptors<T>() | ||||
|         protected virtual void ApplyBeforeFilterInterceptors<TSource>() | ||||
|         { | ||||
|             CurrentQueryable = Interceptors | ||||
|                 .Where(t => t is IBeforeQueryFilterInterceptor) | ||||
| @ -355,12 +372,12 @@ namespace PoweredSoft.DynamicQuery | ||||
|                 .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); | ||||
| 
 | ||||
|             CurrentQueryable = Interceptors | ||||
|                 .Where(t => t is IBeforeQueryFilterInterceptor<T>) | ||||
|                 .Cast<IBeforeQueryFilterInterceptor<T>>() | ||||
|                 .Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); | ||||
|                 .Where(t => t is IBeforeQueryFilterInterceptor<TSource>) | ||||
|                 .Cast<IBeforeQueryFilterInterceptor<TSource>>() | ||||
|                 .Aggregate((IQueryable<TSource>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); | ||||
|         } | ||||
| 
 | ||||
|         protected virtual List<object> RecursiveRegroup<T>(List<DynamicClass> groupRecords, List<List<DynamicClass>> aggregateResults, IGroup group, List<List<object>> lastLists, List<IGroupQueryResult> parentGroupResults = null) | ||||
|         protected virtual List<IGroupQueryResult<TRecord>> RecursiveRegroup<TSource, TRecord>(List<DynamicClass> groupRecords, List<List<DynamicClass>> aggregateResults, IGroup group, List<(List<TSource> entities, IGroupQueryResult<TRecord> group)> lastLists, List<IGroupQueryResult<TRecord>> parentGroupResults = null) | ||||
|         { | ||||
|             var groupIndex = Criteria.Groups.IndexOf(group); | ||||
|             var isLast = Criteria.Groups.Last() == group; | ||||
| @ -371,13 +388,13 @@ namespace PoweredSoft.DynamicQuery | ||||
|                 .GroupBy(gk => gk.GetDynamicPropertyValue($"Key_{groupIndex}")) | ||||
|                 .Select(t => | ||||
|                 { | ||||
|                     var groupResult = new GroupQueryResult(); | ||||
|                     var groupResult = new GroupQueryResult<TRecord>(); | ||||
| 
 | ||||
|                     // group results. | ||||
| 
 | ||||
|                     List<IGroupQueryResult> groupResults; | ||||
|                     List<IGroupQueryResult<TRecord>> groupResults; | ||||
|                     if (parentGroupResults == null) | ||||
|                         groupResults = new List<IGroupQueryResult> { groupResult }; | ||||
|                         groupResults = new List<IGroupQueryResult<TRecord>> { groupResult }; | ||||
|                     else | ||||
|                         groupResults = parentGroupResults.Union(new[] { groupResult }).ToList(); | ||||
| 
 | ||||
| @ -406,41 +423,43 @@ namespace PoweredSoft.DynamicQuery | ||||
| 
 | ||||
|                     if (isLast) | ||||
|                     { | ||||
|                         var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue<List<T>>("Records")).ToList(); | ||||
|                         groupResult.Data = entities.Cast<object>().ToList(); | ||||
|                         lastLists.Add(groupResult.Data); | ||||
|                         var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue<List<TSource>>("Records")).ToList(); | ||||
|                         var tuple = (entities, groupResult); | ||||
|                         groupResult.Data = new List<TRecord>(); | ||||
|                         lastLists.Add(tuple); | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         groupResult.Data = RecursiveRegroup<T>(t.ToList(), aggregateResults, Criteria.Groups[groupIndex + 1], lastLists, groupResults); | ||||
|                         groupResult.SubGroups = RecursiveRegroup<TSource, TRecord>(t.ToList(), aggregateResults, Criteria.Groups[groupIndex + 1], lastLists, groupResults); | ||||
|                     } | ||||
| 
 | ||||
|                     return groupResult; | ||||
|                 }) | ||||
|                 .AsEnumerable<object>() | ||||
|                 .AsEnumerable<IGroupQueryResult<TRecord>>() | ||||
|                 .ToList(); | ||||
| 
 | ||||
|             return ret; | ||||
|         } | ||||
| 
 | ||||
|         protected virtual async Task QueryInterceptToGrouped<T>(List<List<object>> lists) | ||||
|         protected virtual async Task QueryInterceptToGrouped<TSource, TRecord>(List<(List<TSource> entities, IGroupQueryResult<TRecord> group)> lists) | ||||
|         { | ||||
|             var entities = lists.SelectMany(t => t).Cast<T>().ToList(); | ||||
|             var entities = lists.SelectMany(t => t.entities).ToList(); | ||||
|             await AfterEntityReadInterceptors(entities); | ||||
| 
 | ||||
|             var pairs = new List<Tuple<T, object>>(); | ||||
|             var pairs = new List<Tuple<TSource, TRecord>>(); | ||||
| 
 | ||||
|             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<T>(entity); | ||||
|                     innerList[i] = convertedObject; | ||||
|                     var entity = innerList.entities[i]; | ||||
|                     var convertedObject = InterceptConvertToObject<TSource, TRecord>(entity); | ||||
|                     innerList.group.Data.Add(convertedObject); | ||||
|                     pairs.Add(Tuple.Create(entity, convertedObject)); | ||||
|                 } | ||||
|             }); | ||||
| 
 | ||||
|             await AfterReadInterceptors<T>(pairs); | ||||
|             await AfterReadInterceptors<TSource, TRecord>(pairs); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -19,25 +19,36 @@ namespace PoweredSoft.DynamicQuery | ||||
|     } | ||||
| 
 | ||||
|     // part of a result. | ||||
|     public abstract class QueryResult : IQueryResult | ||||
|     public abstract class QueryResult<TRecord> : IQueryResult<TRecord> | ||||
|     { | ||||
|         public List<IAggregateResult> Aggregates { get; set; } | ||||
|         public List<object> Data { get; set; } | ||||
|         public List<TRecord> Data { get; set; } | ||||
| 
 | ||||
|         public bool ShouldSerializeAggregates() => Aggregates != null; | ||||
| 
 | ||||
|         public bool ShouldSerializeData() => Data != null; | ||||
|     } | ||||
| 
 | ||||
|     // just result | ||||
|     public class QueryExecutionResult : QueryResult, IQueryExecutionResult | ||||
|     public class QueryExecutionResult<TRecord> : QueryResult<TRecord>, IQueryExecutionResult<TRecord> | ||||
|     { | ||||
|         public long TotalRecords { get; set; } | ||||
|         public long? NumberOfPages { get; set; } | ||||
|     } | ||||
| 
 | ||||
|     public class QueryExecutionGroupResult<TRecord> : QueryExecutionResult<TRecord>, IQueryExecutionGroupResult<TRecord> | ||||
|     { | ||||
|         public List<IGroupQueryResult<TRecord>> Groups { get; set; } | ||||
|     } | ||||
| 
 | ||||
|     // group result. | ||||
|     public class GroupQueryResult : QueryResult, IGroupQueryResult | ||||
|     public class GroupQueryResult<TRecord> : QueryResult<TRecord>, IGroupQueryResult<TRecord> | ||||
|     { | ||||
|         public string GroupPath { get; set; } | ||||
|         public object GroupValue { get; set; } | ||||
|         public bool HasSubGroups => SubGroups != null && SubGroups.Count > 1; | ||||
|         public List<IGroupQueryResult<TRecord>> SubGroups { get; set; } | ||||
| 
 | ||||
|         public bool ShouldSerializeSubGroups() => HasSubGroups; | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										57
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										57
									
								
								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. | ||||
| @ -40,7 +45,7 @@ public class Startup | ||||
| ```csharp | ||||
| 
 | ||||
| [HttpGet] | ||||
| public IQueryExecutionResult Get( | ||||
| public IQueryExecutionResult<OfSomething> Get( | ||||
|             [FromServices]YourContext context,  | ||||
|             [FromServices]IQueryHandler handler,  | ||||
|             [FromServices]IQueryCriteria criteria, | ||||
| @ -55,7 +60,7 @@ public IQueryExecutionResult Get( | ||||
| } | ||||
| 
 | ||||
| [HttpPost] | ||||
| public IQueryExecutionResult Read( | ||||
| public IQueryExecutionResult<OfSomething> Read( | ||||
|     [FromServices]YourContext context,  | ||||
|     [FromServices]IQueryHandler handler, | ||||
|     [FromBody]IQueryCriteria criteria) | ||||
| @ -70,7 +75,7 @@ public IQueryExecutionResult Read( | ||||
| 
 | ||||
| ```csharp | ||||
| [HttpPost] | ||||
| public async Task<IQueryExecutionResult> Read( | ||||
| public async Task<IQueryExecutionResult<OfSomething>> Read( | ||||
|     [FromServices]YourContext context,  | ||||
|     [FromServices]IQueryHandlerAsync handler, | ||||
|     [FromBody]IQueryCriteria criteria) | ||||
| @ -85,6 +90,25 @@ public async Task<IQueryExecutionResult> Read( | ||||
| 
 | ||||
| Visit: https://github.com/PoweredSoft/DynamicQueryAspNetCoreSample | ||||
| 
 | ||||
| ### Breaking Changes if you are migrating from 1.x | ||||
| 
 | ||||
| Response interface, is now generic ```IQueryResult<T>``` which impacts the way to execute the handler. | ||||
| 
 | ||||
| #### Grouping results | ||||
| 
 | ||||
| Since the results are now generic, it's no longer a List<object> 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<OfSomething> query = context.Somethings; | ||||
| var result = handler.Execute<OfSomething, OfSomethingElse>(query, criteria); | ||||
| ``` | ||||
| 
 | ||||
| ## Criteria | ||||
| 
 | ||||
| Criteria must implement the following interfaces | ||||
| @ -114,13 +138,15 @@ var criteria = new QueryCriteria | ||||
| }; | ||||
| 
 | ||||
| var queryHandler = new QueryHandler(); | ||||
| IQueryExecutionResult result = queryHandler.Execute(someQueryable, criteria); | ||||
| IQueryExecutionResult<OfSomeQueryableType> 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 | ||||
| { | ||||
| @ -129,23 +155,35 @@ public interface IAggregateResult | ||||
|     object Value { get; set; } | ||||
| } | ||||
| 
 | ||||
| public interface IQueryResult | ||||
| public interface IQueryResult<TRecord> | ||||
| { | ||||
|     List<IAggregateResult> Aggregates { get; } | ||||
|     List<object> Data { get; } | ||||
|     List<TRecord> Data { get; } | ||||
| } | ||||
| 
 | ||||
| public interface IGroupQueryResult : IQueryResult | ||||
| public interface IGroupQueryResult<TRecord> : IQueryResult<TRecord> | ||||
| { | ||||
|     string GroupPath { get; set; } | ||||
|     object GroupValue { get; set; } | ||||
|     bool HasSubGroups { get; } | ||||
|     List<IGroupQueryResult<TRecord>> SubGroups { get; set; } | ||||
| } | ||||
| 
 | ||||
| public interface IQueryExecutionResult : IQueryResult | ||||
| public interface IQueryExecutionResultPaging | ||||
| { | ||||
|     long TotalRecords { get; set; } | ||||
|     long? NumberOfPages { get; set; } | ||||
| } | ||||
| 
 | ||||
| public interface IQueryExecutionResult<TRecord> : IQueryResult<TRecord>, IQueryExecutionResultPaging | ||||
| { | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| public interface IQueryExecutionGroupResult<TRecord> : IQueryExecutionResult<TRecord> | ||||
| { | ||||
|     List<IGroupQueryResult<TRecord>> Groups { get; set; } | ||||
| } | ||||
| ``` | ||||
| 
 | ||||
| 
 | ||||
| @ -180,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> | [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)** | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user