Skip to content

Commit b2fdba6

Browse files
author
Bart Koelman
authored
Merge pull request #755 from bart-degreed/fix-nested-sort
Fixed: sorting on multiple attributes
2 parents 4339cc4 + b8dec12 commit b2fdba6

File tree

8 files changed

+102
-16
lines changed

8 files changed

+102
-16
lines changed

src/Examples/JsonApiDotNetCoreExample/Models/Person.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,9 @@ public string FirstName
4343
[Attr]
4444
public Gender Gender { get; set; }
4545

46+
[Attr]
47+
public string Category { get; set; }
48+
4649
[HasMany]
4750
public ISet<TodoItem> TodoItems { get; set; }
4851

src/JsonApiDotNetCore/Data/DefaultResourceRepository.cs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,24 @@ public virtual IQueryable<TResource> Filter(IQueryable<TResource> entities, Filt
8787
}
8888

8989
/// <inheritdoc />
90-
public virtual IQueryable<TResource> Sort(IQueryable<TResource> entities, SortQueryContext sortQueryContext)
90+
public virtual IQueryable<TResource> Sort(IQueryable<TResource> entities, IReadOnlyCollection<SortQueryContext> sortQueryContexts)
9191
{
92-
_logger.LogTrace($"Entering {nameof(Sort)}({nameof(entities)}, {nameof(sortQueryContext)}).");
92+
_logger.LogTrace($"Entering {nameof(Sort)}({nameof(entities)}, {nameof(sortQueryContexts)}).");
9393

94-
return entities.Sort(sortQueryContext);
94+
if (!sortQueryContexts.Any())
95+
{
96+
return entities;
97+
}
98+
99+
var primarySort = sortQueryContexts.First();
100+
var entitiesSorted = entities.Sort(primarySort);
101+
102+
foreach (var secondarySort in sortQueryContexts.Skip(1))
103+
{
104+
entitiesSorted = entitiesSorted.Sort(secondarySort);
105+
}
106+
107+
return entitiesSorted;
95108
}
96109

97110
/// <inheritdoc />

src/JsonApiDotNetCore/Data/IResourceReadRepository.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public interface IResourceReadRepository<TResource, in TId>
4343
/// <summary>
4444
/// Apply a sort to the provided queryable
4545
/// </summary>
46-
IQueryable<TResource> Sort(IQueryable<TResource> entities, SortQueryContext sortQueries);
46+
IQueryable<TResource> Sort(IQueryable<TResource> entities, IReadOnlyCollection<SortQueryContext> sortQueryContexts);
4747
/// <summary>
4848
/// Paginate the provided queryable
4949
/// </summary>

src/JsonApiDotNetCore/Extensions/QueryableExtensions.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,6 @@ public static IOrderedQueryable<TSource> Sort<TSource>(this IQueryable<TSource>
7777

7878
public static IOrderedQueryable<TSource> Sort<TSource>(this IOrderedQueryable<TSource> source, SortQueryContext sortQuery)
7979
{
80-
8180
return sortQuery.Direction == SortDirection.Descending
8281
? source.ThenByDescending(sortQuery.GetPropertyPath())
8382
: source.ThenBy(sortQuery.GetPropertyPath());

src/JsonApiDotNetCore/QueryParameterServices/SortService.cs

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,13 +29,21 @@ public SortService(IResourceDefinitionProvider resourceDefinitionProvider,
2929
/// <inheritdoc/>
3030
public List<SortQueryContext> Get()
3131
{
32-
if (!_queries.Any())
32+
if (_queries.Any())
3333
{
34-
var requestResourceDefinition = _resourceDefinitionProvider.Get(_requestResource.ResourceType);
35-
if (requestResourceDefinition != null)
36-
return requestResourceDefinition.DefaultSort()?.Select(d => BuildQueryContext(new SortQuery(d.Attribute.PublicAttributeName, d.SortDirection))).ToList();
34+
return _queries.ToList();
3735
}
38-
return _queries.ToList();
36+
37+
var requestResourceDefinition = _resourceDefinitionProvider.Get(_requestResource.ResourceType);
38+
var defaultSort = requestResourceDefinition?.DefaultSort();
39+
if (defaultSort != null)
40+
{
41+
return defaultSort
42+
.Select(d => BuildQueryContext(new SortQuery(d.Attribute.PublicAttributeName, d.SortDirection)))
43+
.ToList();
44+
}
45+
46+
return new List<SortQueryContext>();
3947
}
4048

4149
/// <inheritdoc/>

src/JsonApiDotNetCore/Services/DefaultResourceService.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,7 @@ protected virtual async Task<IEnumerable<TResource>> ApplyPageQueryAsync(IQuerya
290290
protected virtual IQueryable<TResource> ApplySort(IQueryable<TResource> entities)
291291
{
292292
var queries = _sortService.Get();
293-
if (queries != null && queries.Any())
294-
foreach (var query in queries)
295-
entities = _repository.Sort(entities, query);
296-
293+
entities = _repository.Sort(entities, queries);
297294
return entities;
298295
}
299296

test/JsonApiDotNetCoreExampleTests/Acceptance/SerializationTests.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@ public async Task When_getting_person_it_must_match_JSON_text()
2626
FirstName = "John",
2727
LastName = "Doe",
2828
Age = 57,
29-
Gender = Gender.Male
29+
Gender = Gender.Male,
30+
Category = "Family"
3031
};
3132

3233
_dbContext.People.RemoveRange(_dbContext.People);
@@ -65,7 +66,8 @@ public async Task When_getting_person_it_must_match_JSON_text()
6566
""initials"": ""J"",
6667
""lastName"": ""Doe"",
6768
""the-Age"": 57,
68-
""gender"": ""Male""
69+
""gender"": ""Male"",
70+
""category"": ""Family""
6971
},
7072
""relationships"": {
7173
""todoItems"": {

test/JsonApiDotNetCoreExampleTests/Acceptance/Spec/AttributeSortTests.cs

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
1+
using System;
12
using JsonApiDotNetCoreExample;
23
using System.Net;
34
using System.Net.Http;
45
using System.Threading.Tasks;
6+
using JsonApiDotNetCore.Models;
57
using JsonApiDotNetCore.Models.JsonApiDocuments;
8+
using JsonApiDotNetCoreExample.Models;
69
using Newtonsoft.Json;
710
using Xunit;
811

@@ -40,5 +43,66 @@ public async Task Cannot_Sort_If_Explicitly_Forbidden()
4043
Assert.Equal("Sorting on attribute 'achievedDate' is not allowed.", errorDocument.Errors[0].Detail);
4144
Assert.Equal("sort", errorDocument.Errors[0].Source.Parameter);
4245
}
46+
47+
[Fact]
48+
public async Task Can_Sort_On_Multiple_Attributes()
49+
{
50+
// Arrange
51+
var category = Guid.NewGuid().ToString();
52+
53+
var persons = new[]
54+
{
55+
new Person
56+
{
57+
Category = category,
58+
FirstName = "Alice",
59+
LastName = "Smith",
60+
Age = 23
61+
},
62+
new Person
63+
{
64+
Category = category,
65+
FirstName = "John",
66+
LastName = "Doe",
67+
Age = 49
68+
},
69+
new Person
70+
{
71+
Category = category,
72+
FirstName = "John",
73+
LastName = "Doe",
74+
Age = 31
75+
},
76+
new Person
77+
{
78+
Category = category,
79+
FirstName = "Jane",
80+
LastName = "Doe",
81+
Age = 19
82+
}
83+
};
84+
85+
_fixture.Context.People.AddRange(persons);
86+
_fixture.Context.SaveChanges();
87+
88+
var httpMethod = new HttpMethod("GET");
89+
var route = "/api/v1/people?filter[category]=" + category + "&sort=lastName,-firstName,the-Age";
90+
var request = new HttpRequestMessage(httpMethod, route);
91+
92+
// Act
93+
var response = await _fixture.Client.SendAsync(request);
94+
95+
// Assert
96+
var body = await response.Content.ReadAsStringAsync();
97+
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
98+
99+
var document = JsonConvert.DeserializeObject<Document>(body);
100+
Assert.Equal(4, document.ManyData.Count);
101+
102+
Assert.Equal(document.ManyData[0].Id, persons[2].StringId);
103+
Assert.Equal(document.ManyData[1].Id, persons[1].StringId);
104+
Assert.Equal(document.ManyData[2].Id, persons[3].StringId);
105+
Assert.Equal(document.ManyData[3].Id, persons[0].StringId);
106+
}
43107
}
44108
}

0 commit comments

Comments
 (0)