Skip to content

Commit 2612ec5

Browse files
committed
feat: can use QueryKit on Enumerables
1 parent 3e8f944 commit 2612ec5

4 files changed

Lines changed: 149 additions & 1 deletion

File tree

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
namespace QueryKit.UnitTests;
2+
3+
using FluentAssertions;
4+
using QueryKit.WebApiTestProject.Entities.Recipes;
5+
using SharedTestingHelper.Fakes.Recipes;
6+
7+
public class EnumerableFilteringTests()
8+
{
9+
[Fact]
10+
public async Task can_filter_enumerable()
11+
{
12+
// Arrange
13+
var recipeOne = new FakeRecipeBuilder().Build();
14+
var recipeTwo = new FakeRecipeBuilder().Build();
15+
var listOfRecipes = new List<Recipe> { recipeOne, recipeTwo };
16+
17+
var input = $"""{nameof(Recipe.Title)} == "{recipeOne.Title}" """;
18+
19+
// Act
20+
var filteredRecipes = listOfRecipes.ApplyQueryKitFilter(input).ToList();
21+
22+
// Assert
23+
filteredRecipes.Count.Should().Be(1);
24+
filteredRecipes[0].Id.Should().Be(recipeOne.Id);
25+
}
26+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
namespace QueryKit.UnitTests;
2+
3+
using FluentAssertions;
4+
using QueryKit.WebApiTestProject.Entities;
5+
using SharedTestingHelper.Fakes;
6+
7+
public class EnumerableSortingTests
8+
{
9+
[Fact]
10+
public async Task can_sort_items_with_mixed_order_directions()
11+
{
12+
// Arrange
13+
var personOne = new FakeTestingPersonBuilder()
14+
.WithTitle("alpha")
15+
.WithAge(10)
16+
.WithBirthMonth(BirthMonthEnum.January)
17+
.Build();
18+
var personTwo = new FakeTestingPersonBuilder()
19+
.WithTitle("beta")
20+
.WithAge(100)
21+
.WithBirthMonth(BirthMonthEnum.February)
22+
.Build();
23+
var personThree = new FakeTestingPersonBuilder()
24+
.WithTitle("beta")
25+
.WithAge(50)
26+
.WithBirthMonth(BirthMonthEnum.March)
27+
.Build();
28+
var personFour = new FakeTestingPersonBuilder()
29+
.WithTitle("beta")
30+
.WithAge(20)
31+
.WithBirthMonth(BirthMonthEnum.April)
32+
.Build();
33+
List<TestingPerson> peopleList = [personOne, personTwo, personThree, personFour];
34+
35+
var input = $"Title desc, Age asc, BirthMonth desc";
36+
37+
// Act
38+
var appliedQueryable = peopleList.ApplyQueryKitSort(input);
39+
var people = appliedQueryable.ToList();
40+
41+
// Assert
42+
people.Count.Should().Be(4);
43+
people[0].Id.Should().Be(personFour.Id);
44+
people[1].Id.Should().Be(personThree.Id);
45+
people[2].Id.Should().Be(personTwo.Id);
46+
people[3].Id.Should().Be(personOne.Id);
47+
}
48+
}
Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ namespace QueryKit;
22

33
using Configuration;
44

5-
public static class QueryableExtensions
5+
public static class QueryKitExtensions
66
{
77
public static IQueryable<TEntity> ApplyQueryKit<TEntity>(this IQueryable<TEntity> source, QueryKitData queryKitData)
88
where TEntity : class
@@ -61,4 +61,65 @@ public static IOrderedQueryable<T> ApplyQueryKitSort<T>(this IQueryable<T> query
6161

6262
return queryable.OrderBy(x => x);
6363
}
64+
65+
public static IEnumerable<TEntity> ApplyQueryKit<TEntity>(
66+
this IEnumerable<TEntity> source,
67+
QueryKitData queryKitData)
68+
where TEntity : class
69+
{
70+
var applied = source;
71+
if (!string.IsNullOrWhiteSpace(queryKitData.Filters))
72+
{
73+
applied = applied.ApplyQueryKitFilter(queryKitData.Filters, queryKitData.Configuration);
74+
}
75+
76+
if (!string.IsNullOrWhiteSpace(queryKitData.SortOrder))
77+
{
78+
applied = applied.ApplyQueryKitSort(queryKitData.SortOrder, queryKitData.Configuration);
79+
}
80+
81+
return applied;
82+
}
83+
84+
public static IEnumerable<TEntity> ApplyQueryKitFilter<TEntity>(
85+
this IEnumerable<TEntity> source,
86+
string filter,
87+
IQueryKitConfiguration? config = null)
88+
where TEntity : class
89+
{
90+
if (string.IsNullOrWhiteSpace(filter))
91+
return source;
92+
93+
var expression = FilterParser.ParseFilter<TEntity>(filter, config);
94+
var predicate = expression.Compile();
95+
return source.Where(predicate);
96+
}
97+
98+
public static IOrderedEnumerable<T> ApplyQueryKitSort<T>(
99+
this IEnumerable<T> source,
100+
string sortExpression,
101+
IQueryKitConfiguration? config = null)
102+
{
103+
var sortInfos = SortParser.ParseSort<T>(sortExpression, config);
104+
105+
if (sortInfos.Count == 0 || sortInfos[0].Expression is null)
106+
return source.OrderBy(x => x);
107+
108+
var first = sortInfos[0];
109+
var ordered = first.IsAscending
110+
? source.OrderBy(first.Expression!.Compile())
111+
: source.OrderByDescending(first.Expression!.Compile());
112+
113+
for (var i = 1; i < sortInfos.Count; i++)
114+
{
115+
var info = sortInfos[i];
116+
if (info.Expression is null) continue;
117+
118+
ordered = info.IsAscending
119+
? ordered.ThenBy(info.Expression.Compile())
120+
: ordered.ThenByDescending(info.Expression.Compile());
121+
}
122+
123+
return ordered;
124+
}
64125
}

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -694,6 +694,19 @@ var people = _dbContext.People
694694
.ToList();
695695
```
696696
697+
## Using QueryKit on Enumerables
698+
Since QueryKit is really just a parser for expressions, you can use it on any `IEnumerable<T>` as well. Just be sure to use the `ApplyQueryKitFilter` and `ApplyQueryKitSort` methods off of the enumerable.
699+
700+
For example
701+
```csharp
702+
var recipeOne = new FakeRecipeBuilder().Build();
703+
var recipeTwo = new FakeRecipeBuilder().Build();
704+
var listOfRecipes = new List<Recipe> { recipeOne, recipeTwo };
705+
706+
var input = $"""{nameof(Recipe.Title)} == "{recipeOne.Title}" """;
707+
708+
var filteredRecipes = listOfRecipes.ApplyQueryKitFilter(input).ToList();
709+
````
697710
698711
699712
## Error Handling

0 commit comments

Comments
 (0)