Skip to content

Commit 9c0a05b

Browse files
author
David Lebee
committed
execution options.
1 parent 3d58f04 commit 9c0a05b

15 files changed

+312
-58
lines changed
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
6+
namespace PoweredSoft.DynamicQuery.Core
7+
{
8+
public interface IQueryExecutionOptionsInterceptor : IQueryInterceptor
9+
{
10+
IQueryExecutionOptions InterceptQueryExecutionOptions(IQueryable queryable, IQueryExecutionOptions current);
11+
}
12+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace PoweredSoft.DynamicQuery.Core
2+
{
3+
public interface IQueryExecutionOptions
4+
{
5+
bool GroupByInMemory { get; set; }
6+
bool GroupByInMemoryNullCheck { get; set; }
7+
}
8+
}

PoweredSoft.DynamicQuery.Core/IQueryHandler.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,15 @@ public interface IQueryHandler : IInterceptableQueryHandler
1515
{
1616
IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria);
1717
IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria);
18+
IQueryExecutionResult<TSource> Execute<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options);
19+
IQueryExecutionResult<TRecord> Execute<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options);
1820
}
1921

2022
public interface IQueryHandlerAsync : IInterceptableQueryHandler
2123
{
22-
Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken));
23-
Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default(CancellationToken));
24+
Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default);
25+
Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, CancellationToken cancellationToken = default);
26+
Task<IQueryExecutionResult<TSource>> ExecuteAsync<TSource>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default);
27+
Task<IQueryExecutionResult<TRecord>> ExecuteAsync<TSource, TRecord>(IQueryable<TSource> queryable, IQueryCriteria criteria, IQueryExecutionOptions options, CancellationToken cancellationToken = default);
2428
}
2529
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace PoweredSoft.DynamicQuery.Core
2+
{
3+
public class QueryExecutionOptions : IQueryExecutionOptions
4+
{
5+
public bool GroupByInMemory { get; set; } = false;
6+
public bool GroupByInMemoryNullCheck { get; set; } = false;
7+
}
8+
}

PoweredSoft.DynamicQuery.Test/AggregateInterceptorTests.cs

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using PoweredSoft.DynamicQuery.Core;
1+
using Microsoft.EntityFrameworkCore;
2+
using PoweredSoft.DynamicQuery.Core;
23
using PoweredSoft.DynamicQuery.Test.Mock;
34
using System;
45
using System.Collections.Generic;
@@ -14,7 +15,7 @@ private class MockAggregateInterceptor : IAggregateInterceptor
1415
{
1516
public IAggregate InterceptAggregate(IAggregate aggregate) => new Aggregate
1617
{
17-
Path = "Item.Price",
18+
Path = "Price",
1819
Type = AggregateType.Avg
1920
};
2021
}
@@ -24,10 +25,12 @@ public void Simple()
2425
{
2526
MockContextFactory.SeedAndTestContextFor("AggregatorInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx =>
2627
{
27-
var expected = ctx.OrderItems.GroupBy(t => true).Select(t => new
28-
{
29-
PriceAtTheTime = t.Average(t2 => t2.Item.Price)
30-
}).First();
28+
var expected = ctx.Items
29+
.GroupBy(t => true)
30+
.Select(t => new
31+
{
32+
PriceAtTheTime = t.Average(t2 => t2.Price)
33+
}).First();
3134

3235
var criteria = new QueryCriteria();
3336
criteria.Aggregates.Add(new Aggregate
@@ -37,7 +40,7 @@ public void Simple()
3740
});
3841
var queryHandler = new QueryHandler();
3942
queryHandler.AddInterceptor(new MockAggregateInterceptor());
40-
var result = queryHandler.Execute(ctx.OrderItems, criteria);
43+
var result = queryHandler.Execute(ctx.Items, criteria);
4144
Assert.Equal(expected.PriceAtTheTime, result.Aggregates.First().Value);
4245
});
4346
}

PoweredSoft.DynamicQuery.Test/AggregateTests.cs

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using PoweredSoft.DynamicQuery.Core;
1+
using Microsoft.EntityFrameworkCore;
2+
using PoweredSoft.DynamicQuery.Core;
23
using PoweredSoft.DynamicQuery.Test.Mock;
34
using System;
45
using System.Collections.Generic;
@@ -27,10 +28,11 @@ public void WithoutGrouping()
2728
ItemQuantityAverage = t.Average(t2 => t2.Quantity),
2829
ItemQuantitySum = t.Sum(t2 => t2.Quantity),
2930
AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime),
31+
/* not supported by ef core 3.0
3032
First = t.First(),
3133
FirstOrDefault = t.FirstOrDefault(),
3234
Last = t.Last(),
33-
LastOrDefault = t.LastOrDefault()
35+
LastOrDefault = t.LastOrDefault()*/
3436
})
3537
.First();
3638

@@ -45,21 +47,28 @@ public void WithoutGrouping()
4547
new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"},
4648
new Aggregate { Type = AggregateType.Min, Path = "Quantity"},
4749
new Aggregate { Type = AggregateType.Max, Path = "Quantity" },
50+
/*not support by ef core 3.0
4851
new Aggregate { Type = AggregateType.First },
4952
new Aggregate { Type = AggregateType.FirstOrDefault },
5053
new Aggregate { Type = AggregateType.Last },
5154
new Aggregate { Type = AggregateType.LastOrDefault },
55+
*/
5256
}
5357
};
5458

5559
var queryHandler = new QueryHandler();
56-
var result = queryHandler.Execute(ctx.OrderItems, criteria);
60+
var result = queryHandler.Execute(ctx.OrderItems, criteria, new QueryExecutionOptions
61+
{
62+
GroupByInMemory = true
63+
});
5764

5865
var aggCount = result.Aggregates.First(t => t.Type == AggregateType.Count);
66+
67+
/*
5968
var aggFirst = result.Aggregates.First(t => t.Type == AggregateType.First);
6069
var aggFirstOrDefault = result.Aggregates.First(t => t.Type == AggregateType.FirstOrDefault);
6170
var aggLast = result.Aggregates.First(t => t.Type == AggregateType.Last);
62-
var aggLastOrDefault = result.Aggregates.First(t => t.Type == AggregateType.LastOrDefault);
71+
var aggLastOrDefault = result.Aggregates.First(t => t.Type == AggregateType.LastOrDefault);*/
6372

6473
var aggItemQuantityMin = result.Aggregates.First(t => t.Type == AggregateType.Min && t.Path == "Quantity");
6574
var aggItemQuantityMax = result.Aggregates.First(t => t.Type == AggregateType.Max && t.Path == "Quantity");
@@ -68,10 +77,11 @@ public void WithoutGrouping()
6877
var aggAvgOfPrice = result.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime");
6978

7079
Assert.Equal(shouldResult.Count, aggCount.Value);
80+
/*
7181
Assert.Equal(shouldResult.First?.Id, (aggFirst.Value as OrderItem)?.Id);
7282
Assert.Equal(shouldResult.FirstOrDefault?.Id, (aggFirstOrDefault.Value as OrderItem)?.Id);
7383
Assert.Equal(shouldResult.Last?.Id, (aggLast.Value as OrderItem)?.Id);
74-
Assert.Equal(shouldResult.LastOrDefault?.Id, (aggLastOrDefault.Value as OrderItem)?.Id);
84+
Assert.Equal(shouldResult.LastOrDefault?.Id, (aggLastOrDefault.Value as OrderItem)?.Id);*/
7585

7686
Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value);
7787
Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value);
@@ -113,7 +123,11 @@ public void WithGrouping()
113123
};
114124

115125
var queryHandler = new QueryHandler();
116-
var result = queryHandler.Execute(ctx.OrderItems, criteria);
126+
var queryable = ctx.OrderItems.Include(t => t.Order);
127+
var result = queryHandler.Execute(queryable, criteria, new QueryExecutionOptions
128+
{
129+
GroupByInMemory = true
130+
});
117131

118132
var groupedResult = result as IQueryExecutionGroupResult<OrderItem>;
119133
Assert.NotNull(groupedResult);

PoweredSoft.DynamicQuery.Test/AsyncTests.cs

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using PoweredSoft.Data;
1+
using Microsoft.EntityFrameworkCore;
2+
using PoweredSoft.Data;
23
using PoweredSoft.Data.EntityFrameworkCore;
34
using PoweredSoft.DynamicQuery.Core;
45
using PoweredSoft.DynamicQuery.Extensions;
@@ -9,6 +10,7 @@
910
using System.Text;
1011
using System.Threading.Tasks;
1112
using Xunit;
13+
using static PoweredSoft.DynamicQuery.Test.GroupInterceptorTests;
1214

1315
namespace PoweredSoft.DynamicQuery.Test
1416
{
@@ -65,7 +67,11 @@ public void WithGrouping()
6567
};
6668
var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() });
6769
var queryHandler = new QueryHandlerAsync(asyncService);
68-
var result = await queryHandler.ExecuteAsync(ctx.OrderItems, criteria);
70+
var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), criteria, new QueryExecutionOptions
71+
{
72+
GroupByInMemory = true
73+
});
74+
6975
var groups = result.GroupedResult().Groups;
7076

7177
// validate group and aggregates of groups.
@@ -165,6 +171,68 @@ public void TestPaging()
165171
Assert.Equal(resultShouldMatch, result.Data);
166172
});
167173
}
174+
175+
[Fact]
176+
public void WithGroupingInterceptorOptions()
177+
{
178+
MockContextFactory.SeedAndTestContextFor("AsyncTests_WithGroupingInterceptorOptions", TestSeeders.SimpleSeedScenario, async ctx =>
179+
{
180+
var shouldResults = ctx.OrderItems
181+
.GroupBy(t => t.Order.CustomerId)
182+
.Select(t => new
183+
{
184+
GroupValue = t.Key,
185+
Count = t.Count(),
186+
ItemQuantityAverage = t.Average(t2 => t2.Quantity),
187+
ItemQuantitySum = t.Sum(t2 => t2.Quantity),
188+
AvgOfPrice = t.Average(t2 => t2.PriceAtTheTime)
189+
})
190+
.ToList();
191+
192+
// query handler that is empty should be the same as running to list.
193+
var criteria = new QueryCriteria()
194+
{
195+
Groups = new List<IGroup>
196+
{
197+
new Group { Path = "Order.CustomerId" }
198+
},
199+
Aggregates = new List<Core.IAggregate>
200+
{
201+
new Aggregate { Type = AggregateType.Count },
202+
new Aggregate { Type = AggregateType.Avg, Path = "Quantity" },
203+
new Aggregate { Type = AggregateType.Sum, Path = "Quantity" },
204+
new Aggregate { Type = AggregateType.Avg, Path = "PriceAtTheTime"}
205+
}
206+
};
207+
var asyncService = new AsyncQueryableService(new[] { new AsyncQueryableHandlerService() });
208+
var queryHandler = new QueryHandlerAsync(asyncService);
209+
queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor());
210+
var result = await queryHandler.ExecuteAsync(ctx.OrderItems.Include(t => t.Order.Customer), criteria);
211+
212+
var groups = result.GroupedResult().Groups;
213+
214+
// validate group and aggregates of groups.
215+
Assert.Equal(groups.Count, shouldResults.Count);
216+
Assert.All(groups, g =>
217+
{
218+
var index = groups.IndexOf(g);
219+
var shouldResult = shouldResults[index];
220+
221+
// validate the group value.
222+
Assert.Equal(g.GroupValue, shouldResult.GroupValue);
223+
224+
// validate the group aggregates.
225+
var aggCount = g.Aggregates.First(t => t.Type == AggregateType.Count);
226+
var aggItemQuantityAverage = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "Quantity");
227+
var aggItemQuantitySum = g.Aggregates.First(t => t.Type == AggregateType.Sum && t.Path == "Quantity");
228+
var aggAvgOfPrice = g.Aggregates.First(t => t.Type == AggregateType.Avg && t.Path == "PriceAtTheTime");
229+
Assert.Equal(shouldResult.Count, aggCount.Value);
230+
Assert.Equal(shouldResult.ItemQuantityAverage, aggItemQuantityAverage.Value);
231+
Assert.Equal(shouldResult.ItemQuantitySum, aggItemQuantitySum.Value);
232+
Assert.Equal(shouldResult.AvgOfPrice, aggAvgOfPrice.Value);
233+
});
234+
});
235+
}
168236
}
169237

170238
}

PoweredSoft.DynamicQuery.Test/GroupInterceptorTests.cs

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using PoweredSoft.DynamicQuery.Core;
1+
using Microsoft.EntityFrameworkCore;
2+
using PoweredSoft.DynamicQuery.Core;
23
using PoweredSoft.DynamicQuery.Extensions;
34
using PoweredSoft.DynamicQuery.Test.Mock;
45
using System;
@@ -9,7 +10,7 @@
910

1011
namespace PoweredSoft.DynamicQuery.Test
1112
{
12-
public class GroupInterceptorTests
13+
public partial class GroupInterceptorTests
1314
{
1415
private class MockGroupInterceptor : IGroupInterceptor
1516
{
@@ -37,7 +38,34 @@ public void Simple()
3738
criteria.Groups.Add(new Group { Path = "CustomerFirstName" });
3839
var queryHandler = new QueryHandler();
3940
queryHandler.AddInterceptor(new MockGroupInterceptor());
40-
var result = queryHandler.Execute(ctx.Orders, criteria);
41+
var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria, new QueryExecutionOptions
42+
{
43+
GroupByInMemory = true
44+
});
45+
46+
var groupedResult = result.GroupedResult();
47+
var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList();
48+
Assert.Equal(expected, actual);
49+
});
50+
}
51+
52+
[Fact]
53+
public void WithInterptorSimple()
54+
{
55+
MockContextFactory.SeedAndTestContextFor("GroupInterceptorTests_Simple", TestSeeders.SimpleSeedScenario, ctx =>
56+
{
57+
var expected = ctx.Orders
58+
.OrderBy(t => t.Customer.FirstName)
59+
.GroupBy(t => t.Customer.FirstName)
60+
.Select(t => t.Key)
61+
.ToList();
62+
63+
var criteria = new QueryCriteria();
64+
criteria.Groups.Add(new Group { Path = "CustomerFirstName" });
65+
var queryHandler = new QueryHandler();
66+
queryHandler.AddInterceptor(new MockGroupInterceptor());
67+
queryHandler.AddInterceptor(new MockQueryExecutionOptionsInterceptor());
68+
var result = queryHandler.Execute(ctx.Orders.Include(t => t.Customer), criteria);
4169

4270
var groupedResult = result.GroupedResult();
4371
var actual = groupedResult.Groups.Select(t => t.GroupValue).ToList();

PoweredSoft.DynamicQuery.Test/GroupTests.cs

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using PoweredSoft.DynamicQuery.Core;
1+
using Microsoft.EntityFrameworkCore;
2+
using PoweredSoft.DynamicQuery.Core;
23
using PoweredSoft.DynamicQuery.Extensions;
34
using PoweredSoft.DynamicQuery.Test.Mock;
45
using System;
@@ -18,23 +19,32 @@ public void Simple()
1819
{
1920
MockContextFactory.SeedAndTestContextFor("GroupTests_Simple", TestSeeders.SimpleSeedScenario, ctx =>
2021
{
21-
var shouldResult = ctx.Orders.OrderBy(t => t.Customer).GroupBy(t => t.Customer).Select(t => new
22-
{
23-
Customer = t.Key,
24-
Orders = t.ToList()
25-
}).ToList();
22+
var shouldResult = ctx.Orders
23+
.OrderBy(t => t.CustomerId)
24+
.ToList()
25+
.GroupBy(t => t.CustomerId)
26+
.Select(t => new
27+
{
28+
CustomerId = t.Key,
29+
Orders = t.ToList()
30+
})
31+
.ToList();
2632

2733
// query handler that is empty should be the same as running to list.
2834
var criteria = new QueryCriteria()
2935
{
3036
Groups = new List<IGroup>
3137
{
32-
new Group { Path = "Customer" }
38+
new Group { Path = "CustomerId" }
3339
}
3440
};
3541

3642
var queryHandler = new QueryHandler();
37-
var result = queryHandler.Execute(ctx.Orders, criteria);
43+
var result = queryHandler.Execute(ctx.Orders, criteria, new QueryExecutionOptions
44+
{
45+
GroupByInMemory = true,
46+
GroupByInMemoryNullCheck = false
47+
});
3848
var groupedResult = result.GroupedResult();
3949

4050
// top level should have same amount of group levels.
@@ -43,7 +53,7 @@ public void Simple()
4353
{
4454
var expected = shouldResult[0];
4555
var actual = groupedResult.Groups[0];
46-
Assert.Equal(expected.Customer.Id, (actual.GroupValue as Customer).Id);
56+
Assert.Equal(expected.CustomerId, actual.GroupValue);
4757

4858
var expectedOrderIds = expected.Orders.Select(t => t.Id).ToList();
4959
var actualOrderIds = actual.Data.Cast<Order>().Select(t => t.Id).ToList();
@@ -71,7 +81,10 @@ public void GroupComplex()
7181
};
7282

7383
var queryHandler = new QueryHandler();
74-
var result = queryHandler.Execute(ctx.Tickets, criteria);
84+
var result = queryHandler.Execute(ctx.Tickets, criteria, new QueryExecutionOptions
85+
{
86+
GroupByInMemory = true
87+
});
7588

7689
var groupedResult = result.GroupedResult();
7790

@@ -106,7 +119,11 @@ public void InterceptorsWithGrouping()
106119
var interceptor = new InterceptorsWithGrouping();
107120
var queryHandler = new QueryHandler();
108121
queryHandler.AddInterceptor(interceptor);
109-
var result = queryHandler.Execute<Ticket, InterceptorWithGroupingFakeModel>(ctx.Tickets, criteria);
122+
var result = queryHandler.Execute<Ticket, InterceptorWithGroupingFakeModel>(ctx.Tickets, criteria, new QueryExecutionOptions
123+
{
124+
GroupByInMemory = true
125+
});
126+
110127
Assert.Equal(4, interceptor.Count);
111128
Assert.True(interceptor.Test);
112129
Assert.True(interceptor.Test2);

0 commit comments

Comments
 (0)