Merge branch 'feature/MakeMoreStronglyTyped' into develop

This commit is contained in:
David Lebee 2019-03-22 16:17:46 -05:00
commit ec6a2e1de7
18 changed files with 345 additions and 182 deletions

View File

@ -8,7 +8,7 @@
<RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery</RepositoryUrl> <RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery</RepositoryUrl>
<RepositoryType>github</RepositoryType> <RepositoryType>github</RepositoryType>
<PackageTags>powered,soft,dynamic,criteria,query,builder,asp,net,core</PackageTags> <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;amp;r=g&amp;amp;d=retro</PackageIconUrl> <PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;amp;r=g&amp;amp;d=retro</PackageIconUrl>
<Product>PoweredSoft.DynamicQuery.AspNetCore</Product> <Product>PoweredSoft.DynamicQuery.AspNetCore</Product>
<Description>This projects makes it easier to use dynamic query in a asp.net core mvc project.</Description> <Description>This projects makes it easier to use dynamic query in a asp.net core mvc project.</Description>

View File

@ -24,4 +24,14 @@ namespace PoweredSoft.DynamicQuery.Core
{ {
Task AfterReadAsync(List<Tuple<T, object>> pairs, CancellationToken cancellationToken = default(CancellationToken)); 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));
}
} }

View File

@ -13,4 +13,9 @@ namespace PoweredSoft.DynamicQuery.Core
{ {
object InterceptResultTo(T entity); object InterceptResultTo(T entity);
} }
public interface IQueryConvertInterceptor<T, T2> : IQueryInterceptor
{
T2 InterceptResultTo(T entity);
}
} }

View File

@ -13,11 +13,13 @@ namespace PoweredSoft.DynamicQuery.Core
public interface IQueryHandler : IInterceptableQueryHandler 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 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));
} }
} }

View File

@ -11,23 +11,35 @@ namespace PoweredSoft.DynamicQuery.Core
object Value { get; set; } object Value { get; set; }
} }
public interface IQueryResult public interface IQueryResult<TRecord>
{ {
List<IAggregateResult> Aggregates { get; } 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; } string GroupPath { get; set; }
object GroupValue { 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 TotalRecords { get; set; }
long? NumberOfPages { 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; }
}
} }

View File

@ -8,7 +8,7 @@
<RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery.Core/</RepositoryUrl> <RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery.Core/</RepositoryUrl>
<RepositoryType>github</RepositoryType> <RepositoryType>github</RepositoryType>
<PackageTags>powered,soft,dynamic,criteria,query,builder</PackageTags> <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;amp;r=g&amp;amp;d=retro</PackageIconUrl> <PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;amp;r=g&amp;amp;d=retro</PackageIconUrl>
<Product>PoweredSoft.DynamicQuery.Core</Product> <Product>PoweredSoft.DynamicQuery.Core</Product>
<Description>core abstractions</Description> <Description>core abstractions</Description>

View File

@ -114,7 +114,11 @@ namespace PoweredSoft.DynamicQuery.Test
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
var result = queryHandler.Execute(ctx.OrderItems, criteria); 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. // validate group and aggregates of groups.
Assert.Equal(groups.Count, shouldResults.Count); Assert.Equal(groups.Count, shouldResults.Count);

View File

@ -1,6 +1,7 @@
using PoweredSoft.Data; using PoweredSoft.Data;
using PoweredSoft.Data.EntityFrameworkCore; using PoweredSoft.Data.EntityFrameworkCore;
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Extensions;
using PoweredSoft.DynamicQuery.Test.Mock; using PoweredSoft.DynamicQuery.Test.Mock;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -65,7 +66,7 @@ namespace PoweredSoft.DynamicQuery.Test
var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() }); var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() });
var queryHandler = new QueryHandlerAsync(asyncService); var queryHandler = new QueryHandlerAsync(asyncService);
var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria); 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. // validate group and aggregates of groups.
Assert.Equal(groups.Count, shouldResults.Count); Assert.Equal(groups.Count, shouldResults.Count);

View File

@ -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] [Fact]
public void NonGeneric() public void NonGeneric()
{ {
@ -63,7 +79,7 @@ namespace PoweredSoft.DynamicQuery.Test
var criteria = new QueryCriteria(); var criteria = new QueryCriteria();
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(new MockQueryConvertInterceptor()); 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)); Assert.All(result.Data, t => Assert.IsType<CustomerModel>(t));
}); });
} }
@ -76,7 +92,20 @@ namespace PoweredSoft.DynamicQuery.Test
var criteria = new QueryCriteria(); var criteria = new QueryCriteria();
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(new MockQueryConvertGenericInterceptor()); 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)); Assert.All(result.Data, t => Assert.IsType<CustomerModel>(t));
}); });
} }

View File

@ -1,4 +1,5 @@
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Extensions;
using PoweredSoft.DynamicQuery.Test.Mock; using PoweredSoft.DynamicQuery.Test.Mock;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -37,7 +38,9 @@ namespace PoweredSoft.DynamicQuery.Test
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(new MockGroupInterceptor()); queryHandler.AddInterceptor(new MockGroupInterceptor());
var result = queryHandler.Execute(ctx.Orders, criteria); 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); Assert.Equal(expected, actual);
}); });
} }

View File

@ -1,4 +1,5 @@
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Extensions;
using PoweredSoft.DynamicQuery.Test.Mock; using PoweredSoft.DynamicQuery.Test.Mock;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
@ -34,13 +35,14 @@ namespace PoweredSoft.DynamicQuery.Test
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
var result = queryHandler.Execute(ctx.Orders, criteria); var result = queryHandler.Execute(ctx.Orders, criteria);
var groupedResult = result.GroupedResult();
// top level should have same amount of group levels. // 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++) for (var i = 0; i < shouldResult.Count; i++)
{ {
var expected = shouldResult[0]; 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); Assert.Equal(expected.Customer.Id, (actual.GroupValue as Customer).Id);
var expectedOrderIds = expected.Orders.Select(t => t.Id).ToList(); var expectedOrderIds = expected.Orders.Select(t => t.Id).ToList();
@ -71,13 +73,15 @@ namespace PoweredSoft.DynamicQuery.Test
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
var result = queryHandler.Execute(ctx.Tickets, criteria); 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); Assert.NotNull(firstGroup);
var secondGroup = result.Data[1] as IGroupQueryResult; var secondGroup = groupedResult.Groups.Skip(1).FirstOrDefault();
Assert.NotNull(secondGroup); Assert.NotNull(secondGroup);
var expected = ctx.Tickets.Select(t => t.TicketType).Distinct().Count(); 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); Assert.Equal(expected, c);
}); });
} }
@ -102,7 +106,7 @@ namespace PoweredSoft.DynamicQuery.Test
var interceptor = new InterceptorsWithGrouping(); var interceptor = new InterceptorsWithGrouping();
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(interceptor); 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.Equal(4, interceptor.Count);
Assert.True(interceptor.Test); Assert.True(interceptor.Test);
Assert.True(interceptor.Test2); Assert.True(interceptor.Test2);

View 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");
}
}
}

View File

@ -8,7 +8,7 @@
<RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery</RepositoryUrl> <RepositoryUrl>https://github.com/PoweredSoft/DynamicQuery</RepositoryUrl>
<RepositoryType>github</RepositoryType> <RepositoryType>github</RepositoryType>
<PackageTags>powered,soft,dynamic,criteria,query,builder</PackageTags> <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;amp;r=g&amp;amp;d=retro</PackageIconUrl> <PackageIconUrl>https://secure.gravatar.com/avatar/4e32f73820c16718909a06c2927f1f8b?s=512&amp;amp;r=g&amp;amp;d=retro</PackageIconUrl>
<Product>PoweredSoft.DynamicQuery</Product> <Product>PoweredSoft.DynamicQuery</Product>
<Description>dynamic query based on string path very usefull for network requests.</Description> <Description>dynamic query based on string path very usefull for network requests.</Description>

View File

@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Reflection; using System.Reflection;
using System.Text; using System.Text;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using PoweredSoft.DynamicLinq; using PoweredSoft.DynamicLinq;
using PoweredSoft.DynamicLinq.Fluent; using PoweredSoft.DynamicLinq.Fluent;
@ -13,19 +14,15 @@ namespace PoweredSoft.DynamicQuery
{ {
public class QueryHandler : QueryHandlerBase, IQueryHandler public class QueryHandler : QueryHandlerBase, IQueryHandler
{ {
internal MethodInfo ExecuteGeneric = typeof(QueryHandler).GetMethods(BindingFlags.Instance | BindingFlags.NonPublic).First(t => t.Name == "Execute" && t.IsGenericMethod); protected virtual IQueryExecutionResult<TRecord> FinalExecute<TSource, TRecord>()
internal IQueryExecutionResult ExecuteReflected() => (IQueryExecutionResult)ExecuteGeneric.MakeGenericMethod(QueryableUnderlyingType).Invoke(this, new object[] { });
protected virtual IQueryExecutionResult Execute<T>()
{ {
CommonBeforeExecute<T>(); CommonBeforeExecute<TSource>();
return HasGrouping ? ExecuteGrouping<T>() : ExecuteNoGrouping<T>(); return HasGrouping ? ExecuteGrouping<TSource, TRecord>() : ExecuteNoGrouping<TSource, TRecord>();
} }
protected virtual IQueryExecutionResult<TRecord> ExecuteGrouping<TSource, TRecord>()
protected virtual IQueryExecutionResult ExecuteGrouping<T>()
{ {
var result = new QueryExecutionResult(); var result = new QueryExecutionGroupResult<TRecord>();
// preserve queryable. // preserve queryable.
var queryableAfterFilters = CurrentQueryable; var queryableAfterFilters = CurrentQueryable;
@ -34,17 +31,17 @@ namespace PoweredSoft.DynamicQuery
CalculatePageCount(result); CalculatePageCount(result);
// intercept groups in advance to avoid doing it more than once :) // 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. // get the aggregates.
var aggregateResults = FetchAggregates<T>(finalGroups); var aggregateResults = FetchAggregates<TSource>(finalGroups);
// sorting. // sorting.
finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending)));
// apply sorting and paging. // apply sorting and paging.
ApplySorting<T>(); ApplySorting<TSource>();
ApplyPaging<T>(); ApplyPaging<TSource>();
// create group & select expression. // create group & select expression.
CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); 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(); var groupRecords = CurrentQueryable.ToDynamicClassList();
// now join them into logical collections // now join them into logical collections
var lastLists = new List<List<object>>(); var lastLists = new List<(List<TSource> source, IGroupQueryResult<TRecord> group)>();
result.Data = RecursiveRegroup<T>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); result.Groups = RecursiveRegroup<TSource, TRecord>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists);
// intercept grouped by. // intercept grouped by.
QueryInterceptToGrouped<T>(lastLists).Wait(); QueryInterceptToGrouped<TSource, TRecord>(lastLists).Wait();
result.Aggregates = CalculateTotalAggregate<T>(queryableAfterFilters); result.Aggregates = CalculateTotalAggregate<TSource>(queryableAfterFilters);
return result; return result;
} }
protected virtual List<IAggregateResult> CalculateTotalAggregate<T>(IQueryable queryableAfterFilters) protected virtual List<IAggregateResult> CalculateTotalAggregate<TSource>(IQueryable queryableAfterFilters)
{ {
if (!Criteria.Aggregates.Any()) if (!Criteria.Aggregates.Any())
return null; return null;
IQueryable selectExpression = CreateTotalAggregateSelectExpression<T>(queryableAfterFilters); IQueryable selectExpression = CreateTotalAggregateSelectExpression<TSource>(queryableAfterFilters);
var aggregateResult = selectExpression.ToDynamicClassList().FirstOrDefault(); var aggregateResult = selectExpression.ToDynamicClassList().FirstOrDefault();
return MaterializeCalculateTotalAggregateResult(aggregateResult); 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()) if (!Criteria.Aggregates.Any())
return null; return null;
@ -85,7 +82,7 @@ namespace PoweredSoft.DynamicQuery
var previousGroups = new List<IGroup>(); var previousGroups = new List<IGroup>();
var ret = finalGroups.Select(fg => var ret = finalGroups.Select(fg =>
{ {
IQueryable selectExpression = CreateFetchAggregateSelectExpression<T>(fg, previousGroups); IQueryable selectExpression = CreateFetchAggregateSelectExpression<TSource>(fg, previousGroups);
var aggregateResult = selectExpression.ToDynamicClassList(); var aggregateResult = selectExpression.ToDynamicClassList();
previousGroups.Add(fg); previousGroups.Add(fg);
return aggregateResult; return aggregateResult;
@ -93,9 +90,9 @@ namespace PoweredSoft.DynamicQuery
return ret; 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 // after filter queryable
var afterFilterQueryable = CurrentQueryable; var afterFilterQueryable = CurrentQueryable;
@ -105,25 +102,30 @@ namespace PoweredSoft.DynamicQuery
CalculatePageCount(result); CalculatePageCount(result);
// sorts and paging. // sorts and paging.
ApplySorting<T>(); ApplySorting<TSource>();
ApplyPaging<T>(); ApplyPaging<TSource>();
// data. // data.
var entities = ((IQueryable<T>)CurrentQueryable).ToList(); var entities = ((IQueryable<TSource>)CurrentQueryable).ToList();
var records = InterceptConvertTo<T>(entities).Result; var records = InterceptConvertTo<TSource, TRecord>(entities).Result;
result.Data = records; result.Data = records;
// aggregates. // aggregates.
result.Aggregates = CalculateTotalAggregate<T>(afterFilterQueryable); result.Aggregates = CalculateTotalAggregate<TSource>(afterFilterQueryable);
return result; return result;
} }
public virtual IQueryExecutionResult Execute(IQueryable queryable, IQueryCriteria criteria) public IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria)
{ {
Reset(queryable, 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>();
} }
} }
} }

View File

@ -3,60 +3,52 @@ using System.Linq;
using System.Reflection; using System.Reflection;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using PoweredSoft.Data;
using PoweredSoft.Data.Core; using PoweredSoft.Data.Core;
using PoweredSoft.DynamicLinq; using PoweredSoft.DynamicLinq;
using PoweredSoft.DynamicQuery.Core; using PoweredSoft.DynamicQuery.Core;
namespace PoweredSoft.DynamicQuery namespace PoweredSoft.DynamicQuery
{ {
public class QueryHandlerAsync : QueryHandlerBase, IQueryHandlerAsync 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; } public IAsyncQueryableService AsyncQueryableService { get; }
internal Task<IQueryExecutionResult> ExecuteAsyncReflected(CancellationToken cancellationToken) => (Task<IQueryExecutionResult>)ExecuteAsyncGeneric.MakeGenericMethod(QueryableUnderlyingType).Invoke(this, new object[] { cancellationToken });
public QueryHandlerAsync(IAsyncQueryableService asyncQueryableService) public QueryHandlerAsync(IAsyncQueryableService asyncQueryableService)
{ {
AsyncQueryableService = 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>(); CommonBeforeExecute<TSource>();
return HasGrouping ? ExecuteAsyncGrouping<T>(cancellationToken) : ExecuteAsyncNoGrouping<T>(cancellationToken); 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); var result = new QueryExecutionGroupResult<TRecord>();
return ExecuteAsyncReflected(cancellationToken);
}
protected virtual async Task<IQueryExecutionResult> ExecuteAsyncGrouping<T>(CancellationToken cancellationToken)
{
var result = new QueryExecutionResult();
// preserve queryable. // preserve queryable.
var queryableAfterFilters = CurrentQueryable; var queryableAfterFilters = CurrentQueryable;
// async. // async.
result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable<T>)queryableAfterFilters, cancellationToken); result.TotalRecords = await this.AsyncQueryableService.LongCountAsync((IQueryable<TSource>)queryableAfterFilters, cancellationToken);
CalculatePageCount(result); CalculatePageCount(result);
// intercept groups in advance to avoid doing it more than once :) // 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. // get the aggregates.
var aggregateResults = await FetchAggregatesAsync<T>(finalGroups, cancellationToken); var aggregateResults = await FetchAggregatesAsync<TSource>(finalGroups, cancellationToken);
// sorting. // sorting.
finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending))); finalGroups.ForEach(fg => Criteria.Sorts.Insert(0, new Sort(fg.Path, fg.Ascending)));
// apply sorting and paging. // apply sorting and paging.
ApplySorting<T>(); ApplySorting<TSource>();
ApplyPaging<T>(); ApplyPaging<TSource>();
// create group & select expression. // create group & select expression.
CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"))); 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); var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast<DynamicClass>(), cancellationToken);
// now join them into logical collections // now join them into logical collections
var lastLists = new List<List<object>>(); var lastLists = new List<(List<TSource> entities, IGroupQueryResult<TRecord> group)>();
result.Data = RecursiveRegroup<T>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists); result.Groups = RecursiveRegroup<TSource, TRecord>(groupRecords, aggregateResults, Criteria.Groups.First(), lastLists);
// converted to grouped by. // 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; 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 // after filter queryable
IQueryable<T> afterFilterQueryable = (IQueryable<T>)CurrentQueryable; IQueryable<TSource> afterFilterQueryable = (IQueryable<TSource>)CurrentQueryable;
// total records. // total records.
result.TotalRecords = await AsyncQueryableService.LongCountAsync(afterFilterQueryable, cancellationToken); result.TotalRecords = await AsyncQueryableService.LongCountAsync(afterFilterQueryable, cancellationToken);
CalculatePageCount(result); CalculatePageCount(result);
// sorts and paging. // sorts and paging.
ApplySorting<T>(); ApplySorting<TSource>();
ApplyPaging<T>(); ApplyPaging<TSource>();
// data. // data.
var entities = await AsyncQueryableService.ToListAsync(((IQueryable<T>)CurrentQueryable), cancellationToken); var entities = await AsyncQueryableService.ToListAsync(((IQueryable<TSource>)CurrentQueryable), cancellationToken);
var records = await InterceptConvertTo<T>(entities); var records = await InterceptConvertTo<TSource, TRecord>(entities);
result.Data = records; result.Data = records;
// aggregates. // aggregates.
result.Aggregates = await CalculateTotalAggregateAsync<T>(afterFilterQueryable, cancellationToken); result.Aggregates = await CalculateTotalAggregateAsync<TSource>(afterFilterQueryable, cancellationToken);
return result; 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()) if (!Criteria.Aggregates.Any())
return null; return null;
IQueryable selectExpression = CreateTotalAggregateSelectExpression<T>(queryableAfterFilters); IQueryable selectExpression = CreateTotalAggregateSelectExpression<TSource>(queryableAfterFilters);
var aggregateResult = await AsyncQueryableService.FirstOrDefaultAsync(selectExpression.Cast<DynamicClass>()); var aggregateResult = await AsyncQueryableService.FirstOrDefaultAsync(selectExpression.Cast<DynamicClass>());
return MaterializeCalculateTotalAggregateResult(aggregateResult); 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()) if (!Criteria.Aggregates.Any())
return null; return null;
@ -124,7 +116,7 @@ namespace PoweredSoft.DynamicQuery
var whenAllResult = await Task.WhenAll(finalGroups.Select(fg => 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 selectExpressionCasted = selectExpression.Cast<DynamicClass>();
var aggregateResult = AsyncQueryableService.ToListAsync(selectExpressionCasted, cancellationToken); var aggregateResult = AsyncQueryableService.ToListAsync(selectExpressionCasted, cancellationToken);
previousGroups.Add(fg); previousGroups.Add(fg);
@ -134,5 +126,17 @@ namespace PoweredSoft.DynamicQuery
var finalResult = whenAllResult.ToList(); var finalResult = whenAllResult.ToList();
return finalResult; 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);
}
} }
} }

View File

@ -31,11 +31,11 @@ namespace PoweredSoft.DynamicQuery
CurrentQueryable = QueryableAtStart; CurrentQueryable = QueryableAtStart;
} }
protected virtual void CommonBeforeExecute<T>() protected virtual void CommonBeforeExecute<TSource>()
{ {
ApplyIncludeStrategyInterceptors<T>(); ApplyIncludeStrategyInterceptors<TSource>();
ApplyBeforeFilterInterceptors<T>(); ApplyBeforeFilterInterceptors<TSource>();
ApplyFilters<T>(); ApplyFilters<TSource>();
} }
public virtual void AddInterceptor(IQueryInterceptor interceptor) public virtual void AddInterceptor(IQueryInterceptor interceptor)
@ -46,7 +46,7 @@ namespace PoweredSoft.DynamicQuery
Interceptors.Add(interceptor); Interceptors.Add(interceptor);
} }
protected virtual IGroup InterceptGroup<T>(IGroup group) protected virtual IGroup InterceptGroup<TSource>(IGroup group)
{ {
var ret = Interceptors var ret = Interceptors
.Where(t => t is IGroupInterceptor) .Where(t => t is IGroupInterceptor)
@ -57,28 +57,28 @@ namespace PoweredSoft.DynamicQuery
} }
protected virtual void ApplyPaging<T>() protected virtual void ApplyPaging<TSource>()
{ {
if (!HasPaging) if (!HasPaging)
return; return;
var q = (IQueryable<T>) CurrentQueryable; var q = (IQueryable<TSource>) CurrentQueryable;
var skip = ((Criteria.Page ?? 1) - 1) * Criteria.PageSize.Value; var skip = ((Criteria.Page ?? 1) - 1) * Criteria.PageSize.Value;
CurrentQueryable = q.Skip(skip).Take(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) if (Criteria.Sorts?.Any() != true)
{ {
ApplyNoSortInterceptor<T>(); ApplyNoSortInterceptor<TSource>();
return; return;
} }
bool isAppending = false; bool isAppending = false;
Criteria.Sorts.ForEach(sort => Criteria.Sorts.ForEach(sort =>
{ {
var transformedSort = InterceptSort<T>(sort); var transformedSort = InterceptSort<TSource>(sort);
if (transformedSort.Count == 0) if (transformedSort.Count == 0)
return; 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 groupIndex = groupResults.Count - 1;
var aggregateLevel = aggregateResults[groupIndex]; var aggregateLevel = aggregateResults[groupIndex];
@ -108,7 +108,7 @@ namespace PoweredSoft.DynamicQuery
return ret; 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 => var groupExpression = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb =>
{ {
@ -124,7 +124,7 @@ namespace PoweredSoft.DynamicQuery
sb.Key($"Key_{++groupKeyIndex}", $"Key_{groupKeyIndex}"); sb.Key($"Key_{++groupKeyIndex}", $"Key_{groupKeyIndex}");
Criteria.Aggregates.ForEach((a, ai) => Criteria.Aggregates.ForEach((a, ai) =>
{ {
var fa = InterceptAggregate<T>(a); var fa = InterceptAggregate<TSource>(a);
var selectType = ResolveSelectFrom(fa.Type); var selectType = ResolveSelectFrom(fa.Type);
sb.Aggregate(fa.Path, selectType, $"Agg_{ai}"); sb.Aggregate(fa.Path, selectType, $"Agg_{ai}");
}); });
@ -147,14 +147,14 @@ namespace PoweredSoft.DynamicQuery
return ret; return ret;
} }
protected virtual IQueryable CreateTotalAggregateSelectExpression<T>(IQueryable queryableAfterFilters) protected virtual IQueryable CreateTotalAggregateSelectExpression<TSource>(IQueryable queryableAfterFilters)
{ {
var groupExpression = queryableAfterFilters.EmptyGroupBy(QueryableUnderlyingType); var groupExpression = queryableAfterFilters.EmptyGroupBy(QueryableUnderlyingType);
var selectExpression = groupExpression.Select(sb => var selectExpression = groupExpression.Select(sb =>
{ {
Criteria.Aggregates.ForEach((a, index) => Criteria.Aggregates.ForEach((a, index) =>
{ {
var fa = InterceptAggregate<T>(a); var fa = InterceptAggregate<TSource>(a);
var selectType = ResolveSelectFrom(fa.Type); var selectType = ResolveSelectFrom(fa.Type);
sb.Aggregate(fa.Path, selectType, $"Agg_{index}"); sb.Aggregate(fa.Path, selectType, $"Agg_{index}");
}); });
@ -162,7 +162,7 @@ namespace PoweredSoft.DynamicQuery
return selectExpression; return selectExpression;
} }
protected virtual void CalculatePageCount(IQueryExecutionResult result) protected virtual void CalculatePageCount(IQueryExecutionResultPaging result)
{ {
if (!HasPaging) if (!HasPaging)
return; return;
@ -173,7 +173,7 @@ namespace PoweredSoft.DynamicQuery
result.NumberOfPages = result.TotalRecords / Criteria.PageSize + (result.TotalRecords % Criteria.PageSize != 0 ? 1 : 0); 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 var ret = Interceptors
.Where(t => t is IAggregateInterceptor) .Where(t => t is IAggregateInterceptor)
@ -182,47 +182,53 @@ namespace PoweredSoft.DynamicQuery
return ret; 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); await AfterEntityReadInterceptors(entities);
var objects = entities.Cast<object>().ToList(); var ret = new List<TRecord>();
for (var i = 0; i < objects.Count; i++) for (var i = 0; i < entities.Count; i++)
objects[i] = InterceptConvertToObject<T>(objects[i]); ret.Add(InterceptConvertToObject<TSource, TRecord>(entities[i]));
var pairs = entities.Select((t, index) => Tuple.Create(t, objects[index])).ToList(); var pairs = entities.Select((t, index) => Tuple.Create(t, ret[index])).ToList();
await AfterReadInterceptors<T>(pairs); 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 Interceptors
.Where(t => t is IAfterReadEntityInterceptor<T>) .Where(t => t is IAfterReadEntityInterceptor<TSource>)
.Cast<IAfterReadEntityInterceptor<T>>() .Cast<IAfterReadEntityInterceptor<TSource>>()
.ToList() .ToList()
.ForEach(t => t.AfterReadEntity(entities)); .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) foreach (var interceptor in asyncInterceptors)
await interceptor.AfterReadEntityAsync(entities); 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 var objPair = pairs.Select(t => Tuple.Create(t.Item1, (object)t.Item2)).ToList();
.Where(t => t is IAfterReadInterceptor<T>)
.Cast<IAfterReadInterceptor<T>>()
.ToList()
.ForEach(t => t.AfterRead(pairs));
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) 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); await interceptor.AfterReadAsync(pairs);
} }
protected virtual object InterceptConvertToObject<T>(object o) protected virtual TRecord InterceptConvertToObject<TSource, TRecord>(object o)
{ {
o = Interceptors o = Interceptors
.Where(t => t is IQueryConvertInterceptor) .Where(t => t is IQueryConvertInterceptor)
@ -230,20 +236,31 @@ namespace PoweredSoft.DynamicQuery
.Aggregate(o, (prev, interceptor) => interceptor.InterceptResultTo(prev)); .Aggregate(o, (prev, interceptor) => interceptor.InterceptResultTo(prev));
o = Interceptors o = Interceptors
.Where(t => t is IQueryConvertInterceptor<T>) .Where(t => t is IQueryConvertInterceptor<TSource>)
.Cast<IQueryConvertInterceptor<T>>() .Cast<IQueryConvertInterceptor<TSource>>()
.Aggregate(o, (prev, interceptor) => .Aggregate(o, (prev, interceptor) =>
{ {
if (prev is T) if (prev is TSource)
return interceptor.InterceptResultTo((T)prev); return interceptor.InterceptResultTo((TSource)prev);
return o; 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>() var original = new List<ISort>()
{ {
@ -259,15 +276,15 @@ namespace PoweredSoft.DynamicQuery
return ret.ToList(); return ret.ToList();
} }
protected virtual void ApplyNoSortInterceptor<T>() protected virtual void ApplyNoSortInterceptor<TSource>()
{ {
CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor) CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor)
.Cast<INoSortInterceptor>() .Cast<INoSortInterceptor>()
.Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev));
CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor<T>) CurrentQueryable = Interceptors.Where(t => t is INoSortInterceptor<TSource>)
.Cast<INoSortInterceptor<T>>() .Cast<INoSortInterceptor<TSource>>()
.Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev)); .Aggregate((IQueryable<TSource>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptNoSort(Criteria, prev));
} }
@ -292,40 +309,40 @@ namespace PoweredSoft.DynamicQuery
return ret.Value; return ret.Value;
} }
protected virtual void ApplyFilters<T>() protected virtual void ApplyFilters<TSource>()
{ {
if (true != Criteria.Filters?.Any()) if (true != Criteria.Filters?.Any())
return; return;
CurrentQueryable = CurrentQueryable.Query(whereBuilder => 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) if (transformedFilter is ISimpleFilter)
ApplySimpleFilter<T>(whereBuilder, transformedFilter as ISimpleFilter); ApplySimpleFilter<TSource>(whereBuilder, transformedFilter as ISimpleFilter);
else if (transformedFilter is ICompositeFilter) else if (transformedFilter is ICompositeFilter)
AppleCompositeFilter<T>(whereBuilder, transformedFilter as ICompositeFilter); AppleCompositeFilter<TSource>(whereBuilder, transformedFilter as ICompositeFilter);
else else
throw new NotSupportedException(); 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); var resolvedConditionOperator = ResolveConditionOperatorFrom(filter.Type);
whereBuilder.Compare(filter.Path, resolvedConditionOperator, filter.Value, and: filter.And == true); 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) var ret = Interceptors.Where(t => t is IFilterInterceptor)
.Cast<IFilterInterceptor>() .Cast<IFilterInterceptor>()
@ -334,7 +351,7 @@ namespace PoweredSoft.DynamicQuery
return ret; return ret;
} }
protected virtual void ApplyIncludeStrategyInterceptors<T>() protected virtual void ApplyIncludeStrategyInterceptors<TSource>()
{ {
CurrentQueryable = Interceptors CurrentQueryable = Interceptors
.Where(t => t is IIncludeStrategyInterceptor) .Where(t => t is IIncludeStrategyInterceptor)
@ -342,12 +359,12 @@ namespace PoweredSoft.DynamicQuery
.Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev));
CurrentQueryable = Interceptors CurrentQueryable = Interceptors
.Where(t => t is IIncludeStrategyInterceptor<T>) .Where(t => t is IIncludeStrategyInterceptor<TSource>)
.Cast<IIncludeStrategyInterceptor<T>>() .Cast<IIncludeStrategyInterceptor<TSource>>()
.Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev)); .Aggregate((IQueryable<TSource>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptIncludeStrategy(Criteria, prev));
} }
protected virtual void ApplyBeforeFilterInterceptors<T>() protected virtual void ApplyBeforeFilterInterceptors<TSource>()
{ {
CurrentQueryable = Interceptors CurrentQueryable = Interceptors
.Where(t => t is IBeforeQueryFilterInterceptor) .Where(t => t is IBeforeQueryFilterInterceptor)
@ -355,12 +372,12 @@ namespace PoweredSoft.DynamicQuery
.Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); .Aggregate(CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev));
CurrentQueryable = Interceptors CurrentQueryable = Interceptors
.Where(t => t is IBeforeQueryFilterInterceptor<T>) .Where(t => t is IBeforeQueryFilterInterceptor<TSource>)
.Cast<IBeforeQueryFilterInterceptor<T>>() .Cast<IBeforeQueryFilterInterceptor<TSource>>()
.Aggregate((IQueryable<T>)CurrentQueryable, (prev, interceptor) => interceptor.InterceptBeforeFiltering(Criteria, prev)); .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 groupIndex = Criteria.Groups.IndexOf(group);
var isLast = Criteria.Groups.Last() == group; var isLast = Criteria.Groups.Last() == group;
@ -371,13 +388,13 @@ namespace PoweredSoft.DynamicQuery
.GroupBy(gk => gk.GetDynamicPropertyValue($"Key_{groupIndex}")) .GroupBy(gk => gk.GetDynamicPropertyValue($"Key_{groupIndex}"))
.Select(t => .Select(t =>
{ {
var groupResult = new GroupQueryResult(); var groupResult = new GroupQueryResult<TRecord>();
// group results. // group results.
List<IGroupQueryResult> groupResults; List<IGroupQueryResult<TRecord>> groupResults;
if (parentGroupResults == null) if (parentGroupResults == null)
groupResults = new List<IGroupQueryResult> { groupResult }; groupResults = new List<IGroupQueryResult<TRecord>> { groupResult };
else else
groupResults = parentGroupResults.Union(new[] { groupResult }).ToList(); groupResults = parentGroupResults.Union(new[] { groupResult }).ToList();
@ -406,41 +423,43 @@ namespace PoweredSoft.DynamicQuery
if (isLast) if (isLast)
{ {
var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue<List<T>>("Records")).ToList(); var entities = t.SelectMany(t2 => t2.GetDynamicPropertyValue<List<TSource>>("Records")).ToList();
groupResult.Data = entities.Cast<object>().ToList(); var tuple = (entities, groupResult);
lastLists.Add(groupResult.Data); groupResult.Data = new List<TRecord>();
lastLists.Add(tuple);
} }
else 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; return groupResult;
}) })
.AsEnumerable<object>() .AsEnumerable<IGroupQueryResult<TRecord>>()
.ToList(); .ToList();
return ret; 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); await AfterEntityReadInterceptors(entities);
var pairs = new List<Tuple<T, object>>(); var pairs = new List<Tuple<TSource, TRecord>>();
lists.ForEach(innerList => 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 entity = innerList.entities[i];
var convertedObject = InterceptConvertToObject<T>(entity); var convertedObject = InterceptConvertToObject<TSource, TRecord>(entity);
innerList[i] = convertedObject; innerList.group.Data.Add(convertedObject);
pairs.Add(Tuple.Create(entity, convertedObject)); pairs.Add(Tuple.Create(entity, convertedObject));
} }
}); });
await AfterReadInterceptors<T>(pairs); await AfterReadInterceptors<TSource, TRecord>(pairs);
} }
} }
} }

View File

@ -19,25 +19,36 @@ namespace PoweredSoft.DynamicQuery
} }
// part of a result. // part of a result.
public abstract class QueryResult : IQueryResult public abstract class QueryResult<TRecord> : IQueryResult<TRecord>
{ {
public List<IAggregateResult> Aggregates { get; set; } 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 ShouldSerializeAggregates() => Aggregates != null;
public bool ShouldSerializeData() => Data != null;
} }
// just result // just result
public class QueryExecutionResult : QueryResult, IQueryExecutionResult public class QueryExecutionResult<TRecord> : QueryResult<TRecord>, IQueryExecutionResult<TRecord>
{ {
public long TotalRecords { get; set; } public long TotalRecords { get; set; }
public long? NumberOfPages { get; set; } public long? NumberOfPages { get; set; }
} }
public class QueryExecutionGroupResult<TRecord> : QueryExecutionResult<TRecord>, IQueryExecutionGroupResult<TRecord>
{
public List<IGroupQueryResult<TRecord>> Groups { get; set; }
}
// group result. // group result.
public class GroupQueryResult : QueryResult, IGroupQueryResult public class GroupQueryResult<TRecord> : QueryResult<TRecord>, IGroupQueryResult<TRecord>
{ {
public string GroupPath { get; set; } public string GroupPath { get; set; }
public object GroupValue { 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;
} }
} }

View File

@ -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. 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 ## Getting Started
> Install nuget package to your awesome project. > Install nuget package to your awesome project.
@ -40,7 +45,7 @@ public class Startup
```csharp ```csharp
[HttpGet] [HttpGet]
public IQueryExecutionResult Get( public IQueryExecutionResult<OfSomething> Get(
[FromServices]YourContext context, [FromServices]YourContext context,
[FromServices]IQueryHandler handler, [FromServices]IQueryHandler handler,
[FromServices]IQueryCriteria criteria, [FromServices]IQueryCriteria criteria,
@ -55,7 +60,7 @@ public IQueryExecutionResult Get(
} }
[HttpPost] [HttpPost]
public IQueryExecutionResult Read( public IQueryExecutionResult<OfSomething> Read(
[FromServices]YourContext context, [FromServices]YourContext context,
[FromServices]IQueryHandler handler, [FromServices]IQueryHandler handler,
[FromBody]IQueryCriteria criteria) [FromBody]IQueryCriteria criteria)
@ -70,7 +75,7 @@ public IQueryExecutionResult Read(
```csharp ```csharp
[HttpPost] [HttpPost]
public async Task<IQueryExecutionResult> Read( public async Task<IQueryExecutionResult<OfSomething>> Read(
[FromServices]YourContext context, [FromServices]YourContext context,
[FromServices]IQueryHandlerAsync handler, [FromServices]IQueryHandlerAsync handler,
[FromBody]IQueryCriteria criteria) [FromBody]IQueryCriteria criteria)
@ -85,6 +90,25 @@ public async Task<IQueryExecutionResult> Read(
Visit: https://github.com/PoweredSoft/DynamicQueryAspNetCoreSample 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
Criteria must implement the following interfaces Criteria must implement the following interfaces
@ -114,13 +138,15 @@ var criteria = new QueryCriteria
}; };
var queryHandler = new QueryHandler(); var queryHandler = new QueryHandler();
IQueryExecutionResult result = queryHandler.Execute(someQueryable, criteria); IQueryExecutionResult<OfSomeQueryableType> result = queryHandler.Execute(someQueryable, criteria);
``` ```
## Query Result ## Query Result
Here is the interfaces that represent the result of query handling execution. Here is the interfaces that represent the result of query handling execution.
> Changed in 2.x
```csharp ```csharp
public interface IAggregateResult public interface IAggregateResult
{ {
@ -129,23 +155,35 @@ public interface IAggregateResult
object Value { get; set; } object Value { get; set; }
} }
public interface IQueryResult public interface IQueryResult<TRecord>
{ {
List<IAggregateResult> Aggregates { get; } 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; } string GroupPath { get; set; }
object GroupValue { 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 TotalRecords { get; set; }
long? NumberOfPages { 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 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 | [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&lt;T&gt; | [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&lt;T, T2&gt; | [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&lt;T, T2&gt; | [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)**