diff --git a/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj b/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj index 23ccc15..efb4056 100644 --- a/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj +++ b/PoweredSoft.DynamicQuery.AspNetCore/PoweredSoft.DynamicQuery.AspNetCore.csproj @@ -8,7 +8,7 @@ https://github.com/PoweredSoft/DynamicQuery github powered,soft,dynamic,criteria,query,builder,asp,net,core - 1.0.0$(VersionSuffix) + 2.0.0$(VersionSuffix) https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro PoweredSoft.DynamicQuery.AspNetCore This projects makes it easier to use dynamic query in a asp.net core mvc project. diff --git a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs index 4ce5ab2..6a818ff 100644 --- a/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs +++ b/PoweredSoft.DynamicQuery.Core/IQueryHandler.cs @@ -19,7 +19,6 @@ namespace PoweredSoft.DynamicQuery.Core public interface IQueryHandlerAsync : IInterceptableQueryHandler { - Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)); } diff --git a/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj b/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj index 02e5841..cc4ce77 100644 --- a/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj +++ b/PoweredSoft.DynamicQuery.Core/PoweredSoft.DynamicQuery.Core.csproj @@ -8,7 +8,7 @@ https://github.com/PoweredSoft/DynamicQuery.Core/ github powered,soft,dynamic,criteria,query,builder - 1.0.0$(VersionSuffix) + 2.0.0$(VersionSuffix) https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro PoweredSoft.DynamicQuery.Core core abstractions diff --git a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs index e1915f9..fc74c29 100644 --- a/PoweredSoft.DynamicQuery.Test/AsyncTests.cs +++ b/PoweredSoft.DynamicQuery.Test/AsyncTests.cs @@ -1,141 +1,142 @@ -//using PoweredSoft.Data; -//using PoweredSoft.Data.EntityFrameworkCore; -//using PoweredSoft.DynamicQuery.Core; -//using PoweredSoft.DynamicQuery.Test.Mock; -//using System; -//using System.Collections.Generic; -//using System.Linq; -//using System.Text; -//using System.Threading.Tasks; -//using Xunit; +using PoweredSoft.Data; +using PoweredSoft.Data.EntityFrameworkCore; +using PoweredSoft.DynamicQuery.Core; +using PoweredSoft.DynamicQuery.Extensions; +using PoweredSoft.DynamicQuery.Test.Mock; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Xunit; -//namespace PoweredSoft.DynamicQuery.Test -//{ -// public class AsyncTests -// { -// [Fact] -// public void TestEmptyCriteria() -// { -// MockContextFactory.SeedAndTestContextFor("AsyncTests_TestEmptyCriteria", TestSeeders.SimpleSeedScenario, async ctx => -// { -// var resultShouldMatch = ctx.Items.ToList(); -// var queryable = ctx.Items.AsQueryable(); +namespace PoweredSoft.DynamicQuery.Test +{ + public class AsyncTests + { + [Fact] + public void TestEmptyCriteria() + { + MockContextFactory.SeedAndTestContextFor("AsyncTests_TestEmptyCriteria", TestSeeders.SimpleSeedScenario, async ctx => + { + var resultShouldMatch = ctx.Items.ToList(); + var queryable = ctx.Items.AsQueryable(); -// // query handler that is empty should be the same as running to list. -// var aqf = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); -// var criteria = new QueryCriteria(); -// var queryHandler = new QueryHandlerAsync(aqf); -// var result = await queryHandler.ExecuteAsync(queryable, criteria); -// Assert.Equal(resultShouldMatch, result.Data); -// }); -// } + // query handler that is empty should be the same as running to list. + var aqf = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); + var criteria = new QueryCriteria(); + var queryHandler = new QueryHandlerAsync(aqf); + var result = await queryHandler.ExecuteAsync(queryable, criteria); + Assert.Equal(resultShouldMatch, result.Data); + }); + } -// [Fact] -// public void WithGrouping() -// { -// MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGrouping", TestSeeders.SimpleSeedScenario, async ctx => -// { -// var shouldResults = ctx.OrderItems -// .GroupBy(t => t.Order.CustomerId) -// .Select(t => new -// { -// GroupValue = t.Key, -// Count = t.Count(), -// ItemQuantityAverage = t.Average(t2 => t2.Quantity), -// ItemQuantitySum = t.Sum(t2 => t2.Quantity), -// AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime) -// }) -// .ToList(); + [Fact] + public void WithGrouping() + { + MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGrouping", TestSeeders.SimpleSeedScenario, async ctx => + { + var shouldResults = ctx.OrderItems + .GroupBy(t => t.Order.CustomerId) + .Select(t => new + { + GroupValue = t.Key, + Count = t.Count(), + ItemQuantityAverage = t.Average(t2 => t2.Quantity), + ItemQuantitySum = t.Sum(t2 => t2.Quantity), + AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime) + }) + .ToList(); -// // query handler that is empty should be the same as running to list. -// var criteria = new QueryCriteria() -// { -// Groups = new List -// { -// new Group { Path = "Order.CustomerId" } -// }, -// Aggregates = new List -// { -// new Aggregate { Type = AggregateType.Count }, -// new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, -// new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, -// new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"} -// } -// }; -// var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); -// var queryHandler = new QueryHandlerAsync(asyncService); -// var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); -// var groups = result.Data.Cast().ToList(); + // query handler that is empty should be the same as running to list. + var criteria = new QueryCriteria() + { + Groups = new List + { + new Group { Path = "Order.CustomerId" } + }, + Aggregates = new List + { + new Aggregate { Type = AggregateType.Count }, + new Aggregate { Type = AggregateType.Avg, Path = "Quantity" }, + new Aggregate { Type = AggregateType.Sum, Path = "Quantity" }, + new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"} + } + }; + var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); + var queryHandler = new QueryHandlerAsync(asyncService); + var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); + var groups = result.GroupedResult().Groups; -// // validate group and aggregates of groups. -// Assert.Equal(groups.Count, shouldResults.Count); -// Assert.All(groups, g => -// { -// var index = groups.IndexOf(g); -// var shouldResult = shouldResults[index]; + // validate group and aggregates of groups. + Assert.Equal(groups.Count, shouldResults.Count); + Assert.All(groups, g => + { + var index = groups.IndexOf(g); + var shouldResult = shouldResults[index]; -// // validate the group value. -// Assert.Equal(g.GroupValue, shouldResult.GroupValue); + // validate the group value. + Assert.Equal(g.GroupValue, shouldResult.GroupValue); -// // validate the group aggregates. -// var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count); -// var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); -// var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); -// var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); -// Assert.Equal(shouldResult.Count, aggCount.Value); -// Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); -// Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); -// Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); -// }); -// }); -// } + // validate the group aggregates. + var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count); + var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity"); + var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity"); + var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime"); + Assert.Equal(shouldResult.Count, aggCount.Value); + Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value); + Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value); + Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value); + }); + }); + } -// [Fact] -// public void SimpleFilter() -// { -// MockContextFactory.SeedAndTestContextFor("AsyncTests_SimpleFilter", TestSeeders.SimpleSeedScenario, async ctx => -// { -// var resultShouldMatch = ctx.Items.Where(t => t.Name.EndsWith("Cables")).ToList(); + [Fact] + public void SimpleFilter() + { + MockContextFactory.SeedAndTestContextFor("AsyncTests_SimpleFilter", TestSeeders.SimpleSeedScenario, async ctx => + { + var resultShouldMatch = ctx.Items.Where(t => t.Name.EndsWith("Cables")).ToList(); -// var criteria = new QueryCriteria() -// { -// Filters = new List -// { -// new SimpleFilter -// { -// Path = "Name", -// Type = FilterType.EndsWith, -// Value = "Cables" -// } -// } -// }; + var criteria = new QueryCriteria() + { + Filters = new List + { + new SimpleFilter + { + Path = "Name", + Type = FilterType.EndsWith, + Value = "Cables" + } + } + }; -// var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); -// var queryHandler = new QueryHandlerAsync(asyncService); -// var result = await queryHandler.ExecuteAsync(ctx.Items, criteria); -// Assert.Equal(resultShouldMatch, result.Data); -// }); -// } + var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); + var queryHandler = new QueryHandlerAsync(asyncService); + var result = await queryHandler.ExecuteAsync(ctx.Items, criteria); + Assert.Equal(resultShouldMatch, result.Data); + }); + } -// [Fact] -// public void TestPaging() -// { -// MockContextFactory.SeedAndTestContextFor("AsyncTests_TestPagging", TestSeeders.SimpleSeedScenario, async ctx => -// { -// var resultShouldMatch = ctx.OrderItems.OrderBy(t => t.Id).Skip(5).Take(5).ToList(); + [Fact] + public void TestPaging() + { + MockContextFactory.SeedAndTestContextFor("AsyncTests_TestPagging", TestSeeders.SimpleSeedScenario, async ctx => + { + var resultShouldMatch = ctx.OrderItems.OrderBy(t => t.Id).Skip(5).Take(5).ToList(); -// // query handler that is empty should be the same as running to list. -// var criteria = new QueryCriteria(); -// criteria.Sorts.Add(new Sort("Id")); -// criteria.Page = 2; -// criteria.PageSize = 5; + // query handler that is empty should be the same as running to list. + var criteria = new QueryCriteria(); + criteria.Sorts.Add(new Sort("Id")); + criteria.Page = 2; + criteria.PageSize = 5; -// var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); -// var queryHandler = new QueryHandlerAsync(asyncService); -// var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); -// Assert.Equal(resultShouldMatch, result.Data); -// }); -// } -// } + var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); + var queryHandler = new QueryHandlerAsync(asyncService); + var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); + Assert.Equal(resultShouldMatch, result.Data); + }); + } + } -//} +} diff --git a/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj b/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj index aefa7bb..c7bb118 100644 --- a/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj +++ b/PoweredSoft.DynamicQuery/PoweredSoft.DynamicQuery.csproj @@ -8,7 +8,7 @@ https://github.com/PoweredSoft/DynamicQuery github powered,soft,dynamic,criteria,query,builder - 1.0.0$(VersionSuffix) + 2.0.0$(VersionSuffix) https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;r=g&amp;d=retro PoweredSoft.DynamicQuery dynamic query based on string path very usefull for network requests. diff --git a/PoweredSoft.DynamicQuery/QueryHandler.cs b/PoweredSoft.DynamicQuery/QueryHandler.cs index ecccdb6..bff122a 100644 --- a/PoweredSoft.DynamicQuery/QueryHandler.cs +++ b/PoweredSoft.DynamicQuery/QueryHandler.cs @@ -22,7 +22,7 @@ namespace PoweredSoft.DynamicQuery protected virtual IQueryExecutionResult ExecuteGrouping() { - var result = new QueryGroupExecutionResult(); + var result = new QueryExecutionGroupResult(); // preserve queryable. var queryableAfterFilters = CurrentQueryable; diff --git a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs index d2a6771..9ffe598 100644 --- a/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs +++ b/PoweredSoft.DynamicQuery/QueryHandlerAsync.cs @@ -3,61 +3,52 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Threading.Tasks; +using PoweredSoft.Data; using PoweredSoft.Data.Core; using PoweredSoft.DynamicLinq; using PoweredSoft.DynamicQuery.Core; namespace PoweredSoft.DynamicQuery { - /* + public class QueryHandlerAsync : QueryHandlerBase, IQueryHandlerAsync { - internal MethodInfo ExecuteAsyncGeneric = typeof(QueryHandlerAsync).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).First(t => t.Name == "ExecuteAsync" && t.IsGenericMethod); - public IAsyncQueryableService AsyncQueryableService { get; } - internal Task ExecuteAsyncReflected(CancellationToken cancellationToken) => (Task)ExecuteAsyncGeneric.MakeGenericMethod(QueryableUnderlyingType).Invoke(this, new object[] { cancellationToken }); - public QueryHandlerAsync(IAsyncQueryableService asyncQueryableService) { AsyncQueryableService = asyncQueryableService; } - protected virtual Task ExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) + protected virtual Task> FinalExecuteAsync(CancellationToken cancellationToken = default(CancellationToken)) { - CommonBeforeExecute(); - return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); + CommonBeforeExecute(); + return HasGrouping ? ExecuteAsyncGrouping(cancellationToken) : ExecuteAsyncNoGrouping(cancellationToken); } - public Task ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + protected virtual async Task> ExecuteAsyncGrouping(CancellationToken cancellationToken) { - Reset(queryable, criteria); - return ExecuteAsyncReflected(cancellationToken); - } - - protected virtual async Task ExecuteAsyncGrouping(CancellationToken cancellationToken) - { - var result = new QueryExecutionResult(); + var result = new QueryExecutionGroupResult(); // preserve queryable. var queryableAfterFilters = CurrentQueryable; // async. - result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable)queryableAfterFilters, cancellationToken); + result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable)queryableAfterFilters, cancellationToken); CalculatePageCount(result); // intercept groups in advance to avoid doing it more than once :) - var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); + var finalGroups = Criteria.Groups.Select(g => InterceptGroup(g)).ToList(); // get the aggregates. - var aggregateResults = await FetchAggregatesAsync(finalGroups, cancellationToken); + var aggregateResults = await FetchAggregatesAsync(finalGroups, cancellationToken); // sorting. finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); // apply sorting and paging. - ApplySorting(); - ApplyPaging(); + ApplySorting(); + ApplyPaging(); // create group & select expression. CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); @@ -71,52 +62,52 @@ namespace PoweredSoft.DynamicQuery var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast(), cancellationToken); // now join them into logical collections - var lastLists = new List>(); - result.Data = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); + var lastLists = new List<(List entities, IGroupQueryResult group)>(); + result.Groups = RecursiveRegroup(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); // converted to grouped by. - await QueryInterceptToGrouped(lastLists); + await QueryInterceptToGrouped(lastLists); - result.Aggregates = await CalculateTotalAggregateAsync(queryableAfterFilters, cancellationToken); + result.Aggregates = await CalculateTotalAggregateAsync(queryableAfterFilters, cancellationToken); return result; } - protected async Task ExecuteAsyncNoGrouping(CancellationToken cancellationToken) + protected async Task> ExecuteAsyncNoGrouping(CancellationToken cancellationToken) { - var result = new QueryExecutionResult(); + var result = new QueryExecutionResult(); // after filter queryable - IQueryable afterFilterQueryable = (IQueryable)CurrentQueryable; + IQueryable afterFilterQueryable = (IQueryable)CurrentQueryable; // total records. result.TotalRecords = await AsyncQueryableService.LongCountAsync(afterFilterQueryable, cancellationToken); CalculatePageCount(result); // sorts and paging. - ApplySorting(); - ApplyPaging(); + ApplySorting(); + ApplyPaging(); // data. - var entities = await AsyncQueryableService.ToListAsync(((IQueryable)CurrentQueryable), cancellationToken); - var records = await InterceptConvertTo(entities); + var entities = await AsyncQueryableService.ToListAsync(((IQueryable)CurrentQueryable), cancellationToken); + var records = await InterceptConvertTo(entities); result.Data = records; // aggregates. - result.Aggregates = await CalculateTotalAggregateAsync(afterFilterQueryable, cancellationToken); + result.Aggregates = await CalculateTotalAggregateAsync(afterFilterQueryable, cancellationToken); return result; } - protected virtual async Task> CalculateTotalAggregateAsync(IQueryable queryableAfterFilters, CancellationToken cancellationToken) + protected virtual async Task> CalculateTotalAggregateAsync(IQueryable queryableAfterFilters, CancellationToken cancellationToken) { if (!Criteria.Aggregates.Any()) return null; - IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); + IQueryable selectExpression = CreateTotalAggregateSelectExpression(queryableAfterFilters); var aggregateResult = await AsyncQueryableService.FirstOrDefaultAsync(selectExpression.Cast()); return MaterializeCalculateTotalAggregateResult(aggregateResult); } - protected async virtual Task>> FetchAggregatesAsync(List finalGroups, CancellationToken cancellationToken) + protected async virtual Task>> FetchAggregatesAsync(List finalGroups, CancellationToken cancellationToken) { if (!Criteria.Aggregates.Any()) return null; @@ -125,7 +116,7 @@ namespace PoweredSoft.DynamicQuery var whenAllResult = await Task.WhenAll(finalGroups.Select(fg => { - IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); + IQueryable selectExpression = CreateFetchAggregateSelectExpression(fg, previousGroups); var selectExpressionCasted = selectExpression.Cast(); var aggregateResult = AsyncQueryableService.ToListAsync(selectExpressionCasted, cancellationToken); previousGroups.Add(fg); @@ -135,5 +126,17 @@ namespace PoweredSoft.DynamicQuery var finalResult = whenAllResult.ToList(); return finalResult; } - }*/ + + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + { + Reset(queryable, criteria); + return FinalExecuteAsync(cancellationToken); + } + + public Task> ExecuteAsync(IQueryable queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken)) + { + Reset(queryable, criteria); + return FinalExecuteAsync(cancellationToken); + } + } } diff --git a/PoweredSoft.DynamicQuery/QueryResult.cs b/PoweredSoft.DynamicQuery/QueryResult.cs index 89a3cb1..a8ffc14 100644 --- a/PoweredSoft.DynamicQuery/QueryResult.cs +++ b/PoweredSoft.DynamicQuery/QueryResult.cs @@ -34,7 +34,7 @@ namespace PoweredSoft.DynamicQuery public long? NumberOfPages { get; set; } } - public class QueryGroupExecutionResult : QueryExecutionResult, IQueryExecutionGroupResult + public class QueryExecutionGroupResult : QueryExecutionResult, IQueryExecutionGroupResult { public List> Groups { get; set; } }