execution options.

This commit is contained in:
David Lebee 2019-11-27 20:08:51 -06:00
parent 3d58f0496d
commit 9c0a05b579
15 changed files with 312 additions and 58 deletions

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace PoweredSoft.DynamicQuery.Core
{
public interface IQueryExecutionOptionsInterceptor : IQueryInterceptor
{
IQueryExecutionOptions InterceptQueryExecutionOptions(IQueryable queryable, IQueryExecutionOptions current);
}
}

View File

@ -0,0 +1,8 @@
namespace PoweredSoft.DynamicQuery.Core
{
public interface IQueryExecutionOptions
{
bool GroupByInMemory { get; set; }
bool GroupByInMemoryNullCheck { get; set; }
}
}

View File

@ -15,11 +15,15 @@ namespace PoweredSoft.DynamicQuery.Core
{
IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria);
IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria);
IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options);
IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options);
}
public interface IQueryHandlerAsync : IInterceptableQueryHandler
{
Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken));
Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken));
Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default);
Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default);
Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default);
Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default);
}
}

View File

@ -0,0 +1,8 @@
namespace PoweredSoft.DynamicQuery.Core
{
public class QueryExecutionOptions : IQueryExecutionOptions
{
public bool GroupByInMemory { get; set; } = false;
public bool GroupByInMemoryNullCheck { get; set; } = false;
}
}

View File

@ -1,4 +1,5 @@
using PoweredSoft.DynamicQuery.Core;
using Microsoft.EntityFrameworkCore;
using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Test.Mock;
using System;
using System.Collections.Generic;
@ -14,7 +15,7 @@ namespace PoweredSoft.DynamicQuery.Test
{
public IAggregate InterceptAggregate(IAggregate aggregate) => new Aggregate
{
Path = "Item.Price",
Path = "Price",
Type = AggregateType.Avg
};
}
@ -24,10 +25,12 @@ namespace PoweredSoft.DynamicQuery.Test
{
MockContextFactory.SeedAndTestContextFor("AggregatorInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx =>
{
var expected = ctx.OrderItems.GroupBy(t => true).Select(t => new
{
PriceAtTheTime = t.Average(t2 => t2.Item.Price)
}).First();
var expected = ctx.Items
.GroupBy(t => true)
.Select(t => new
{
PriceAtTheTime = t.Average(t2 => t2.Price)
}).First();
var criteria = new QueryCriteria();
criteria.Aggregates.Add(new Aggregate
@ -37,7 +40,7 @@ namespace PoweredSoft.DynamicQuery.Test
});
var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(new MockAggregateInterceptor());
var result = queryHandler.Execute(ctx.OrderItems, criteria);
var result = queryHandler.Execute(ctx.Items, criteria);
Assert.Equal(expected.PriceAtTheTime, result.Aggregates.First().Value);
});
}

View File

@ -1,4 +1,5 @@
using PoweredSoft.DynamicQuery.Core;
using Microsoft.EntityFrameworkCore;
using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Test.Mock;
using System;
using System.Collections.Generic;
@ -27,10 +28,11 @@ namespace PoweredSoft.DynamicQuery.Test
ItemQuantityAverage = t.Average(t2 => t2.Quantity),
ItemQuantitySum = t.Sum(t2 => t2.Quantity),
AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime),
/* not supported by ef core 3.0
First = t.First(),
FirstOrDefault = t.FirstOrDefault(),
Last = t.Last(),
LastOrDefault = t.LastOrDefault()
LastOrDefault = t.LastOrDefault()*/
})
.First();
@ -45,21 +47,28 @@ namespace PoweredSoft.DynamicQuery.Test
new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"},
new Aggregate { Type = AggregateType.Min, Path = "Quantity"},
new Aggregate { Type = AggregateType.Max, Path = "Quantity" },
/*not support by ef core 3.0
new Aggregate { Type = AggregateType.First },
new Aggregate { Type = AggregateType.FirstOrDefault },
new Aggregate { Type = AggregateType.Last },
new Aggregate { Type = AggregateType.LastOrDefault },
*/
}
};
var queryHandler = new QueryHandler();
var result = queryHandler.Execute(ctx.OrderItems, criteria);
var result = queryHandler.Execute(ctx.OrderItems, criteria, new QueryExecutionOptions
{
GroupByInMemory = true
});
var aggCount = result.Aggregates.First(t => t.Type == AggregateType.Count);
/*
var aggFirst = result.Aggregates.First(t => t.Type == AggregateType.First);
var aggFirstOrDefault = result.Aggregates.First(t => t.Type == AggregateType.FirstOrDefault);
var aggLast = result.Aggregates.First(t => t.Type == AggregateType.Last);
var aggLastOrDefault = result.Aggregates.First(t => t.Type == AggregateType.LastOrDefault);
var aggLastOrDefault = result.Aggregates.First(t => t.Type == AggregateType.LastOrDefault);*/
var aggItemQuantityMin = result.Aggregates.First(t => t.Type == AggregateType.Min && t.Path == "Quantity");
var aggItemQuantityMax = result.Aggregates.First(t => t.Type == AggregateType.Max && t.Path == "Quantity");
@ -68,10 +77,11 @@ namespace PoweredSoft.DynamicQuery.Test
var aggAvgOfPrice = result.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime");
Assert.Equal(shouldResult.Count, aggCount.Value);
/*
Assert.Equal(shouldResult.First?.Id, (aggFirst.Value as OrderItem)?.Id);
Assert.Equal(shouldResult.FirstOrDefault?.Id, (aggFirstOrDefault.Value as OrderItem)?.Id);
Assert.Equal(shouldResult.Last?.Id, (aggLast.Value as OrderItem)?.Id);
Assert.Equal(shouldResult.LastOrDefault?.Id, (aggLastOrDefault.Value as OrderItem)?.Id);
Assert.Equal(shouldResult.LastOrDefault?.Id, (aggLastOrDefault.Value as OrderItem)?.Id);*/
Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value);
Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value);
@ -113,7 +123,11 @@ namespace PoweredSoft.DynamicQuery.Test
};
var queryHandler = new QueryHandler();
var result = queryHandler.Execute(ctx.OrderItems, criteria);
var queryable = ctx.OrderItems.Include(t => t.Order);
var result = queryHandler.Execute(queryable, criteria, new QueryExecutionOptions
{
GroupByInMemory = true
});
var groupedResult = result as IQueryExecutionGroupResult<OrderItem>;
Assert.NotNull(groupedResult);

View File

@ -1,4 +1,5 @@
using PoweredSoft.Data;
using Microsoft.EntityFrameworkCore;
using PoweredSoft.Data;
using PoweredSoft.Data.EntityFrameworkCore;
using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Extensions;
@ -9,6 +10,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Xunit;
using static PoweredSoft.DynamicQuery.Test.GroupInterceptorTests;
namespace PoweredSoft.DynamicQuery.Test
{
@ -65,7 +67,11 @@ namespace PoweredSoft.DynamicQuery.Test
};
var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() });
var queryHandler = new QueryHandlerAsync(asyncService);
var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria);
var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), criteria, new QueryExecutionOptions
{
GroupByInMemory = true
});
var groups = result.GroupedResult().Groups;
// validate group and aggregates of groups.
@ -165,6 +171,68 @@ namespace PoweredSoft.DynamicQuery.Test
Assert.Equal(resultShouldMatch, result.Data);
});
}
[Fact]
public void WithGroupingInterceptorOptions()
{
MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGroupingInterceptorOptions", TestSeeders.SimpleSeedScenario, async ctx =>
{
var shouldResults = ctx.OrderItems
.GroupBy(t => t.Order.CustomerId)
.Select(t => new
{
GroupValue = t.Key,
Count = t.Count(),
ItemQuantityAverage = t.Average(t2 => t2.Quantity),
ItemQuantitySum = t.Sum(t2 => t2.Quantity),
AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime)
})
.ToList();
// query handler that is empty should be the same as running to list.
var criteria = new QueryCriteria()
{
Groups = new List<IGroup>
{
new Group { Path = "Order.CustomerId" }
},
Aggregates = new List<Core.IAggregate>
{
new Aggregate { Type = AggregateType.Count },
new Aggregate { Type = AggregateType.Avg, Path = "Quantity" },
new Aggregate { Type = AggregateType.Sum, Path = "Quantity" },
new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"}
}
};
var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() });
var queryHandler = new QueryHandlerAsync(asyncService);
queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor());
var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), criteria);
var groups = result.GroupedResult().Groups;
// validate group and aggregates of groups.
Assert.Equal(groups.Count, shouldResults.Count);
Assert.All(groups, g =>
{
var index = groups.IndexOf(g);
var shouldResult = shouldResults[index];
// validate the group value.
Assert.Equal(g.GroupValue, shouldResult.GroupValue);
// validate the group aggregates.
var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count);
var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity");
var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity");
var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime");
Assert.Equal(shouldResult.Count, aggCount.Value);
Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value);
Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value);
Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value);
});
});
}
}
}

View File

@ -1,4 +1,5 @@
using PoweredSoft.DynamicQuery.Core;
using Microsoft.EntityFrameworkCore;
using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Extensions;
using PoweredSoft.DynamicQuery.Test.Mock;
using System;
@ -9,7 +10,7 @@ using Xunit;
namespace PoweredSoft.DynamicQuery.Test
{
public class GroupInterceptorTests
public partial class GroupInterceptorTests
{
private class MockGroupInterceptor : IGroupInterceptor
{
@ -37,7 +38,34 @@ namespace PoweredSoft.DynamicQuery.Test
criteria.Groups.Add(new Group { Path = "CustomerFirstName" });
var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(new MockGroupInterceptor());
var result = queryHandler.Execute(ctx.Orders, criteria);
var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria, new QueryExecutionOptions
{
GroupByInMemory = true
});
var groupedResult = result.GroupedResult();
var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList();
Assert.Equal(expected, actual);
});
}
[Fact]
public void WithInterptorSimple()
{
MockContextFactory.SeedAndTestContextFor("GroupInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx =>
{
var expected = ctx.Orders
.OrderBy(t => t.Customer.FirstName)
.GroupBy(t => t.Customer.FirstName)
.Select(t => t.Key)
.ToList();
var criteria = new QueryCriteria();
criteria.Groups.Add(new Group { Path = "CustomerFirstName" });
var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(new MockGroupInterceptor());
queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor());
var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria);
var groupedResult = result.GroupedResult();
var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList();

View File

@ -1,4 +1,5 @@
using PoweredSoft.DynamicQuery.Core;
using Microsoft.EntityFrameworkCore;
using PoweredSoft.DynamicQuery.Core;
using PoweredSoft.DynamicQuery.Extensions;
using PoweredSoft.DynamicQuery.Test.Mock;
using System;
@ -18,23 +19,32 @@ namespace PoweredSoft.DynamicQuery.Test
{
MockContextFactory.SeedAndTestContextFor("GroupTests_Simple", TestSeeders.SimpleSeedScenario, ctx =>
{
var shouldResult = ctx.Orders.OrderBy(t => t.Customer).GroupBy(t => t.Customer).Select(t => new
{
Customer = t.Key,
Orders = t.ToList()
}).ToList();
var shouldResult = ctx.Orders
.OrderBy(t => t.CustomerId)
.ToList()
.GroupBy(t => t.CustomerId)
.Select(t => new
{
CustomerId = t.Key,
Orders = t.ToList()
})
.ToList();
// query handler that is empty should be the same as running to list.
var criteria = new QueryCriteria()
{
Groups = new List<IGroup>
{
new Group { Path = "Customer" }
new Group { Path = "CustomerId" }
}
};
var queryHandler = new QueryHandler();
var result = queryHandler.Execute(ctx.Orders, criteria);
var result = queryHandler.Execute(ctx.Orders, criteria, new QueryExecutionOptions
{
GroupByInMemory = true,
GroupByInMemoryNullCheck = false
});
var groupedResult = result.GroupedResult();
// top level should have same amount of group levels.
@ -43,7 +53,7 @@ namespace PoweredSoft.DynamicQuery.Test
{
var expected = shouldResult[0];
var actual = groupedResult.Groups[0];
Assert.Equal(expected.Customer.Id, (actual.GroupValue as Customer).Id);
Assert.Equal(expected.CustomerId, actual.GroupValue);
var expectedOrderIds = expected.Orders.Select(t => t.Id).ToList();
var actualOrderIds = actual.Data.Cast<Order>().Select(t => t.Id).ToList();
@ -71,7 +81,10 @@ namespace PoweredSoft.DynamicQuery.Test
};
var queryHandler = new QueryHandler();
var result = queryHandler.Execute(ctx.Tickets, criteria);
var result = queryHandler.Execute(ctx.Tickets, criteria, new QueryExecutionOptions
{
GroupByInMemory = true
});
var groupedResult = result.GroupedResult();
@ -106,7 +119,11 @@ namespace PoweredSoft.DynamicQuery.Test
var interceptor = new InterceptorsWithGrouping();
var queryHandler = new QueryHandler();
queryHandler.AddInterceptor(interceptor);
var result = queryHandler.Execute<Ticket, InterceptorWithGroupingFakeModel>(ctx.Tickets, criteria);
var result = queryHandler.Execute<Ticket, InterceptorWithGroupingFakeModel>(ctx.Tickets, criteria, new QueryExecutionOptions
{
GroupByInMemory = true
});
Assert.Equal(4, interceptor.Count);
Assert.True(interceptor.Test);
Assert.True(interceptor.Test2);

View File

@ -14,12 +14,9 @@ namespace PoweredSoft.DynamicQuery.Test.Mock
public static void TestContextFor(string testName, Action<MockContext> action)
{
var options = new DbContextOptionsBuilder<MockContext>()
.ConfigureWarnings(warnings =>
warnings.Ignore(RelationalEventId.QueryClientEvaluationWarning)
)
.UseInMemoryDatabase(databaseName: testName).Options;
using (var ctx = new MockContext(options))
using var ctx = new MockContext(options);
action(ctx);
}

View File

@ -0,0 +1,22 @@
using Microsoft.EntityFrameworkCore.Query.Internal;
using PoweredSoft.DynamicQuery.Core;
using System.Linq;
namespace PoweredSoft.DynamicQuery.Test
{
public partial class GroupInterceptorTests
{
public class MockQueryExecutionOptionsInterceptor : IQueryExecutionOptionsInterceptor
{
public IQueryExecutionOptions InterceptQueryExecutionOptions(IQueryable queryable, IQueryExecutionOptions current)
{
if (queryable.Provider is IAsyncQueryProvider)
{
current.GroupByInMemory = true;
}
return current;
}
}
}
}

View File

@ -8,12 +8,12 @@
<ItemGroup>
<PackageReference Include="Bogus" Version="28.3.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.InMemory" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="3.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.3.0" />
<PackageReference Include="PoweredSoft.Data.EntityFrameworkCore" Version="1.1.3" />
<PackageReference Include="PoweredSoft.Data.EntityFrameworkCore" Version="2.0.0" />
<PackageReference Include="xunit" Version="2.4.1" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.1">
<PrivateAssets>all</PrivateAssets>

View File

@ -43,14 +43,23 @@ namespace PoweredSoft.DynamicQuery
ApplySorting<TSource>();
ApplyPaging<TSource>();
// create group & select expression.
CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")));
if (Options.GroupByInMemory)
CurrentQueryable = CurrentQueryable.ToObjectList().Cast<TSource>().AsQueryable();
CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb =>
{
gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"));
});
CurrentQueryable = CurrentQueryable.Select(sb =>
{
sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}"));
sb.ToList("Records");
});
// loop through the grouped records.
var groupRecords = CurrentQueryable.ToDynamicClassList();
@ -118,13 +127,25 @@ namespace PoweredSoft.DynamicQuery
public IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria)
{
Reset(queryable, criteria);
Reset(queryable, criteria, new QueryExecutionOptions());
return FinalExecute<TSource, TSource>();
}
public IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria)
{
Reset(queryable, criteria);
Reset(queryable, criteria, new QueryExecutionOptions());
return FinalExecute<TSource, TRecord>();
}
public IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options)
{
Reset(queryable, criteria, options);
return FinalExecute<TSource, TSource>();
}
public IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options)
{
Reset(queryable, criteria, options);
return FinalExecute<TSource, TRecord>();
}
}

View File

@ -20,7 +20,7 @@ namespace PoweredSoft.DynamicQuery
AsyncQueryableService = asyncQueryableService;
}
protected virtual Task<IQueryExecutionResult<TRecord>> FinalExecuteAsync<TSource, TRecord>(CancellationToken cancellationToken = default(CancellationToken))
protected virtual Task<IQueryExecutionResult<TRecord>> FinalExecuteAsync<TSource, TRecord>(CancellationToken cancellationToken = default)
{
CommonBeforeExecute<TSource>();
return HasGrouping ? ExecuteAsyncGrouping<TSource, TRecord>(cancellationToken) : ExecuteAsyncNoGrouping<TSource, TRecord>(cancellationToken);
@ -50,16 +50,44 @@ namespace PoweredSoft.DynamicQuery
ApplySorting<TSource>();
ApplyPaging<TSource>();
// create group & select expression.
CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb => finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}")));
CurrentQueryable = CurrentQueryable.Select(sb =>
{
finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}"));
sb.ToList("Records");
});
List<DynamicClass> groupRecords;
// loop through the grouped records.
var groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast<DynamicClass>(), cancellationToken);
if (Options.GroupByInMemory)
{
CurrentQueryable = CurrentQueryable.ToObjectList().Cast<TSource>().AsQueryable();
// create group & select expression.
CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb =>
{
gb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"));
});
CurrentQueryable = CurrentQueryable.Select(sb =>
{
sb.NullChecking(Options.GroupByInMemory ? Options.GroupByInMemoryNullCheck : false);
finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}"));
sb.ToList("Records");
});
// loop through the grouped records.
groupRecords = CurrentQueryable.Cast<DynamicClass>().ToList();
}
else
{
// create group & select expression.
CurrentQueryable = CurrentQueryable.GroupBy(QueryableUnderlyingType, gb =>
{
finalGroups.ForEach((fg, index) => gb.Path(fg.Path, $"Key_{index}"));
});
CurrentQueryable = CurrentQueryable.Select(sb =>
{
finalGroups.ForEach((fg, index) => sb.Key($"Key_{index}", $"Key_{index}"));
sb.ToList("Records");
});
// loop through the grouped records.
groupRecords = await AsyncQueryableService.ToListAsync(CurrentQueryable.Cast<DynamicClass>(), cancellationToken);
}
// now join them into logical collections
var lastLists = new List<(List<TSource> entities, IGroupQueryResult<TRecord> group)>();
@ -127,15 +155,27 @@ namespace PoweredSoft.DynamicQuery
return finalResult;
}
public Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken))
public Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default)
{
Reset(queryable, criteria);
Reset(queryable, criteria, new QueryExecutionOptions());
return FinalExecuteAsync<TSource, TSource>(cancellationToken);
}
public Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken))
public Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default)
{
Reset(queryable, criteria);
Reset(queryable, criteria, new QueryExecutionOptions());
return FinalExecuteAsync<TSource, TRecord>(cancellationToken);
}
public Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default)
{
Reset(queryable, criteria, options);
return FinalExecuteAsync<TSource, TSource>(cancellationToken);
}
public Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default)
{
Reset(queryable, criteria, options);
return FinalExecuteAsync<TSource, TRecord>(cancellationToken);
}
}

View File

@ -20,24 +20,36 @@ namespace PoweredSoft.DynamicQuery
protected IQueryCriteria Criteria { get; set; }
protected IQueryable QueryableAtStart { get; private set; }
protected IQueryable CurrentQueryable { get; set; }
protected IQueryExecutionOptions Options { get; private set; }
protected Type QueryableUnderlyingType => QueryableAtStart.ElementType;
protected bool HasGrouping => Criteria.Groups?.Any() == true;
protected bool HasPaging => Criteria.PageSize.HasValue && Criteria.PageSize > 0;
protected virtual void Reset(IQueryable queryable, IQueryCriteria criteria)
protected virtual void Reset(IQueryable queryable, IQueryCriteria criteria, IQueryExecutionOptions options)
{
Criteria = criteria ?? throw new ArgumentNullException("criteria");
QueryableAtStart = queryable ?? throw new ArgumentNullException("queryable");
CurrentQueryable = QueryableAtStart;
Options = options;
}
protected virtual void CommonBeforeExecute<TSource>()
{
ApplyQueryExecutionOptionIncerceptors();
ApplyIncludeStrategyInterceptors<TSource>();
ApplyBeforeFilterInterceptors<TSource>();
ApplyFilters<TSource>();
}
protected virtual void ApplyQueryExecutionOptionIncerceptors()
{
Options = Interceptors
.Where(t => t is IQueryExecutionOptionsInterceptor)
.Cast<IQueryExecutionOptionsInterceptor>()
.Aggregate(Options, (prev, curr) => curr.InterceptQueryExecutionOptions(CurrentQueryable, prev));
}
public virtual void AddInterceptor(IQueryInterceptor interceptor)
{
if (interceptor == null) throw new ArgumentNullException("interceptor");