Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using AutoQuery;
using AutoQuery.Abstractions;
using AutoQuery.Extensions;
using AutoQueryApiDemo.Models;

namespace AutoQueryApiDemo.Configurations;

public class TestUserQueryConfiguration : IFilterQueryConfiguration<TestUserQueryOptions, User>
{
public void Configure(FilterQueryBuilder<TestUserQueryOptions, User> builder)
{
builder.Property(q => q.FilterEmail, d => d.Email)
.HasEqual();
}
}
18 changes: 18 additions & 0 deletions sample/AutoQueryApiDemo/Models/TestUserQueryOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using AutoQuery.Abstractions;
using Microsoft.AspNetCore.Mvc;

namespace AutoQueryApiDemo.Models;

public class TestUserQueryOptions : IQueryPagedOptions
{
[FromQuery(Name = "filter[email]")]
public string? FilterEmail { get; set; }
[FromQuery(Name = "fields")]
public string? Fields { get; set; }
[FromQuery(Name = "sort")]
public string? Sort { get; set; }
[FromQuery(Name = "page")]
public int? Page { get; set; }
[FromQuery(Name = "pageSize")]
public int? PageSize { get; set; }
}
63 changes: 41 additions & 22 deletions src/AutoQuery/Extensions/QueryExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -96,29 +96,48 @@ public static IQueryable<T> ApplySort<T>(this IQueryable<T> query, IQueryOptions
if (string.IsNullOrWhiteSpace(queryOption.Sort))
return query;

var sort = queryOption.Sort;
var descending = sort.StartsWith("-");
var sortBy = descending ? sort.Substring(1) : sort;
var cacheKey = $"{typeof(T).FullName}_{sortBy}";
var propertyInfo = s_PropertyCache.GetOrAdd(cacheKey, t => typeof(T).GetProperty(sortBy, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance));
if (propertyInfo == null)
return query;
var sortFields = queryOption.Sort.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var isFirstSort = true;

foreach (var sort in sortFields)
{
if (string.IsNullOrWhiteSpace(sort))
continue;

var descending = sort.StartsWith("-");
var sortBy = descending ? sort[1..] : sort;
var cacheKey = $"{typeof(T).FullName}_{sortBy}";
var propertyInfo = s_PropertyCache.GetOrAdd(cacheKey, _ => typeof(T).GetProperty(sortBy, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance));

if (propertyInfo == null)
continue;

var parameter = Expression.Parameter(typeof(T), "entity");
var property = Expression.Property(parameter, propertyInfo);
var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(delegateType, property, parameter);

var parameter = Expression.Parameter(typeof(T), "entity");
var property = Expression.Property(parameter, propertyInfo);
var delegateType = typeof(Func<,>).MakeGenericType(typeof(T), propertyInfo.PropertyType);
var lambda = Expression.Lambda(delegateType, property, parameter);

string methodName = descending ? "OrderByDescending" : "OrderBy";
var resultExpression = Expression.Call(
typeof(Queryable),
methodName,
[typeof(T), propertyInfo.PropertyType],
query.Expression,
lambda
);

return query.Provider.CreateQuery<T>(resultExpression);
string methodName = (isFirstSort, descending) switch
{
(true, true) => "OrderByDescending",
(true, false) => "OrderBy",
(false, true) => "ThenByDescending",
(false, false) => "ThenBy"
};

var resultExpression = Expression.Call(
typeof(Queryable),
methodName,
[typeof(T), propertyInfo.PropertyType],
query.Expression,
lambda
);

query = query.Provider.CreateQuery<T>(resultExpression);
isFirstSort = false;
}

return query;
}

/// <summary>
Expand Down
45 changes: 43 additions & 2 deletions test/AutoQuery.Tests/Extensions/QueryExtensionsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void ApplyQueryPagedResult_ShouldApplyFilterSelectorAndPaging(List<TestDa

[Theory]
[ClassData(typeof(ApplySortTestData))]
public void ApplySort_ShouldSortData(List<TestData> data, TestQueryOptions queryOptions, string expectedFirstName)
public void ApplySort_ShouldSortData(List<TestData> data, TestQueryOptions queryOptions, int expectedFirstId, string expectedFirstName)
{
// Arrange
var queryableData = data.AsQueryable();
Expand All @@ -88,6 +88,7 @@ public void ApplySort_ShouldSortData(List<TestData> data, TestQueryOptions query
var result = queryableData.ApplySort(queryOptions);

// Assert
Assert.Equal(expectedFirstId, result.First().Id);
Assert.Equal(expectedFirstName, result.First().Name);
}

Expand Down Expand Up @@ -289,7 +290,8 @@ public IEnumerator<object[]> GetEnumerator()
new TestData { Id = 1, Name = "B" },
new TestData { Id = 2, Name = "A" }
},
new TestQueryOptions {Sort = "Name"},
new TestQueryOptions { Sort = "Name" },
2,
"A"
};
yield return new object[]
Expand All @@ -300,6 +302,7 @@ public IEnumerator<object[]> GetEnumerator()
new TestData { Id = 2, Name = "A" }
},
new TestQueryOptions { Sort = "-Name" },
1,
"B"
};
yield return new object[]
Expand All @@ -310,6 +313,7 @@ public IEnumerator<object[]> GetEnumerator()
new TestData { Id = 4, Name = "D" }
},
new TestQueryOptions { Sort = "Name" },
3,
"C"
};
yield return new object[]
Expand All @@ -320,8 +324,45 @@ public IEnumerator<object[]> GetEnumerator()
new TestData { Id = 6, Name = "F" }
},
new TestQueryOptions { Sort = "-Name" },
6,
"F"
};
yield return new object[]
{
new List<TestData>
{
new TestData { Id = 2, Name = "A" },
new TestData { Id = 1, Name = "A" },
new TestData { Id = 3, Name = "B" }
},
new TestQueryOptions { Sort = "Name,Id" },
1,
"A"
};
yield return new object[]
{
new List<TestData>
{
new TestData { Id = 1, Name = "A" },
new TestData { Id = 2, Name = "A" },
new TestData { Id = 3, Name = "B" }
},
new TestQueryOptions { Sort = "Name,-Id" },
2,
"A"
};
yield return new object[]
{
new List<TestData>
{
new TestData { Id = 1, Name = "C" },
new TestData { Id = 2, Name = "B" },
new TestData { Id = 2, Name = "A" }
},
new TestQueryOptions { Sort = "-Id,Name" },
2,
"A"
};
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
Expand Down
Loading