diff --git a/PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs b/PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs new file mode 100644 index 0000000..3fd0aa8 --- /dev/null +++ b/PoweredSoft.DynamicQuery.Core/IAfterReadInterceptor.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace PoweredSoft.DynamicQuery.Core +{ + public interface IAfterReadEntityInterceptor : IQueryInterceptor + { + void AfterReadEntity(List entities); + } + + public interface IAfterReadEntityInterceptorAsync : IQueryInterceptor + { + Task AfterReadEntityAsync(List entities, CancellationToken cancellationToken = default(CancellationToken)); + } + + public interface IAfterReadInterceptor : IQueryInterceptor + { + void AfterRead(List> pairs); + } + + public interface IAfterReadInterceptorAsync : IQueryInterceptor + { + Task AfterReadAsync(List> pairs, CancellationToken cancellationToken = default(CancellationToken)); + } +} diff --git a/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs b/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs index 5ce4ea8..df386a8 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryConvertInterceptor.cs @@ -1,4 +1,8 @@ -namespace PoweredSoft.DynamicQuery.Core +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace PoweredSoft.DynamicQuery.Core { public interface IQueryConvertInterceptor : IQueryInterceptor { diff --git a/PoweredSoft.DynamicQuery.Test/GroupTests.cs b/PoweredSoft.DynamicQuery.Test/GroupTests.cs index 3cbfd58..05efd1b 100644 --- a/PoweredSoft.DynamicQuery.Test/GroupTests.cs +++ b/PoweredSoft.DynamicQuery.Test/GroupTests.cs @@ -4,6 +4,8 @@ using System; using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; +using System.Threading.Tasks; using Xunit; namespace PoweredSoft.DynamicQuery.Test @@ -18,7 +20,7 @@ namespace PoweredSoft.DynamicQuery.Test var shouldResult = ctx.Orders.OrderBy(t => t.Customer).GroupBy(t => t.Customer).Select(t => new { Customer = t.Key, - Orders = t.ToList() + Orders = t.ToList() }).ToList(); // query handler that is empty should be the same as running to list. @@ -35,7 +37,7 @@ namespace PoweredSoft.DynamicQuery.Test // top level should have same amount of group levels. Assert.Equal(result.Data.Count, shouldResult.Count); - for(var i = 0; i < shouldResult.Count; i++) + for (var i = 0; i < shouldResult.Count; i++) { var expected = shouldResult[0]; var actual = ((IGroupQueryResult)result.Data[0]); @@ -79,5 +81,82 @@ namespace PoweredSoft.DynamicQuery.Test Assert.Equal(expected, c); }); } + + [Fact] + public void InterceptorsWithGrouping() + { + MockContextFactory.SeedAndTestContextFor("GroupTests_InterceptorsWithGrouping", TestSeeders.SeedTicketScenario, ctx => + { + var criteria = new QueryCriteria() + { + Groups = new List() + { + new Group { Path = "TicketType" } + }, + Aggregates = new List() + { + new Aggregate { Type = AggregateType.Count } + } + }; + + var interceptor = new InterceptorsWithGrouping(); + var queryHandler = new QueryHandler(); + queryHandler.AddInterceptor(interceptor); + var result = queryHandler.Execute(ctx.Tickets, criteria); + Assert.Equal(4, interceptor.Count); + Assert.True(interceptor.Test); + Assert.True(interceptor.Test2); + Assert.True(interceptor.Test3); + Assert.True(interceptor.Test4); + }); + } + } + + class InterceptorWithGroupingFakeModel + { + + } + + class InterceptorsWithGrouping : + IAfterReadEntityInterceptor, + IAfterReadEntityInterceptorAsync, + IAfterReadInterceptor, + IAfterReadInterceptorAsync, + IQueryConvertInterceptor + { + public int Count { get; set; } = 0; + public bool Test { get; set; } = false; + public bool Test2 { get; set; } = false; + public bool Test3 { get; set; } = false; + public bool Test4 { get; set; } = false; + + public void AfterRead(List> pairs) + { + Test = true; + Count++; + } + + public async Task AfterReadAsync(List> pairs, CancellationToken cancellationToken = default(CancellationToken)) + { + Test2 = true; + Count++; + } + + public void AfterReadEntity(List entities) + { + Test3 = true; + Count++; + } + + public async Task AfterReadEntityAsync(List entities, CancellationToken cancellationToken = default(CancellationToken)) + { + Test4 = true; + Count++; + } + + public object InterceptResultTo(Ticket entity) + { + return new InterceptorWithGroupingFakeModel(); + } } } diff --git a/PoweredSoft.DynamicQuery/QueryHandler.cs b/PoweredSoft.DynamicQuery/QueryHandler.cs index a38ee7b..1e2a5c0 100644 --- a/PoweredSoft.DynamicQuery/QueryHandler.cs +++ b/PoweredSoft.DynamicQuery/QueryHandler.cs @@ -4,6 +4,7 @@ using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text; +using System.Threading.Tasks; using PoweredSoft.DynamicLinq; using PoweredSoft.DynamicLinq.Fluent; using PoweredSoft.DynamicQuery.Core; @@ -57,13 +58,15 @@ namespace PoweredSoft.DynamicQuery var groupRecords = CurrentQueryable.ToDynamicClassList(); // now join them into logical collections - result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First()); + var lastLists = new List>(); + result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); + + // intercept grouped by. + QueryInterceptToGrouped(lastLists).Wait(); result.Aggregates = CalculateTotalAggregate(queryableAfterFilters); return result; } - - protected virtual List CalculateTotalAggregate(IQueryable queryableAfterFilters) { if (!Criteria.Aggregates.Any()) @@ -107,7 +110,7 @@ namespace PoweredSoft.DynamicQuery // data. var entities = ((IQueryable)CurrentQueryable).ToList(); - var records = InterceptConvertTo(entities); + var records = InterceptConvertTo(entities).Result; result.Data = records; // aggregates. diff --git a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs index ca32daf..23ddbb7 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs @@ -70,7 +70,11 @@ namespace PoweredSoft.DynamicQuery var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast(), cancellationToken); // now join them into logical collections - result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First()); + var lastLists = new List>(); + result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); + + // converted to grouped by. + await QueryInterceptToGrouped(lastLists); result.Aggregates = await CalculateTotalAggregateAsync(queryableAfterFilters, cancellationToken); return result; @@ -93,7 +97,7 @@ namespace PoweredSoft.DynamicQuery // data. var entities = await AsyncQueryableService.ToListAsync(((IQueryable)CurrentQueryable), cancellationToken); - var records = InterceptConvertTo(entities); + var records = await InterceptConvertTo(entities); result.Data = records; // aggregates. diff --git a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs index 9225c29..f0f21c4 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerBase.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerBase.cs @@ -182,15 +182,46 @@ namespace PoweredSoft.DynamicQuery return ret; } - protected virtual List InterceptConvertTo(List entities) + protected virtual async Task> InterceptConvertTo(List entities) { + await AfterEntityReadInterceptors(entities); + var objects = entities.Cast().ToList(); for (var i = 0; i < objects.Count; i++) objects[i] = InterceptConvertToObject(objects[i]); + var pairs = entities.Select((t, index) => Tuple.Create(t, objects[index])).ToList(); + await AfterReadInterceptors(pairs); + return objects; } + protected virtual async Task AfterEntityReadInterceptors(List entities) + { + Interceptors + .Where(t => t is IAfterReadEntityInterceptor) + .Cast>() + .ToList() + .ForEach(t => t.AfterReadEntity(entities)); + + var asyncInterceptors = Interceptors.Where(t => t is IAfterReadEntityInterceptorAsync).Cast>(); + foreach (var interceptor in asyncInterceptors) + await interceptor.AfterReadEntityAsync(entities); + } + + protected virtual async Task AfterReadInterceptors(List> pairs) + { + Interceptors + .Where(t => t is IAfterReadInterceptor) + .Cast>() + .ToList() + .ForEach(t => t.AfterRead(pairs)); + + var asyncInterceptors = Interceptors.Where(t => t is IAfterReadInterceptorAsync).Cast>(); + foreach (var interceptor in asyncInterceptors) + await interceptor.AfterReadAsync(pairs); + } + protected virtual object InterceptConvertToObject(object o) { o = Interceptors @@ -329,7 +360,7 @@ namespace PoweredSoft.DynamicQuery .Aggregate((IQueryable)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); } - protected virtual List RecursiveRegroup(List groupRecords, List> aggregateResults, IGroup group, List parentGroupResults = null) + protected virtual List RecursiveRegroup(List groupRecords, List> aggregateResults, IGroup group, List> lastLists, List parentGroupResults = null) { var groupIndex = Criteria.Groups.IndexOf(group); var isLast = Criteria.Groups.Last() == group; @@ -376,11 +407,12 @@ namespace PoweredSoft.DynamicQuery if (isLast) { var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue>("Records")).ToList(); - groupResult.Data = InterceptConvertTo(entities); + groupResult.Data = entities.Cast().ToList(); + lastLists.Add(groupResult.Data); } else { - groupResult.Data = RecursiveRegroup(t.ToList(), aggregateResults, Criteria.Groups[groupIndex + 1], groupResults); + groupResult.Data = RecursiveRegroup(t.ToList(), aggregateResults, Criteria.Groups[groupIndex + 1], lastLists, groupResults); } return groupResult; @@ -389,5 +421,26 @@ namespace PoweredSoft.DynamicQuery .ToList(); return ret; } + + protected virtual async Task QueryInterceptToGrouped(List> lists) + { + var entities = lists.SelectMany(t => t).Cast().ToList(); + await AfterEntityReadInterceptors(entities); + + var pairs = new List>(); + + lists.ForEach(innerList => + { + for(var i = 0; i < innerList.Count; i++) + { + var entity = (T)innerList[i]; + var convertedObject = InterceptConvertToObject(entity); + innerList[i] = convertedObject; + pairs.Add(Tuple.Create(entity, convertedObject)); + } + }); + + await AfterReadInterceptors(pairs); + } } }