Skip to content

Commit

Permalink
Merge pull request #42 from ZEXSM/feature/sorting-multiple-fields
Browse files Browse the repository at this point in the history
feature sorting by several fields with indication of direction
  • Loading branch information
ZEXSM authored Jan 13, 2021
2 parents 944e5d2 + 0342f0f commit 7cdcd89
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 3 deletions.
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The library primarily targets OData Version 4.01 and provides linq syntax for cr
* [`toupper`](#toupper)
* [`tolower`](#tolower)
* [`concat`](#concat)
* sorting by several fields with indication of direction

## Installation
To install `OData.QueryBuilder` from `Visual Studio`, find `OData.QueryBuilder` in the `NuGet` package manager user interface or enter the following command in the package manager console:
Expand Down Expand Up @@ -182,6 +183,15 @@ var constValue = 3;
.OrderBy(s => new { s.IdType, s.Sum })
```
> $orderby=IdType,Sum asc
```csharp
.OrderBy((entity, sort) => sort
.Ascending(entity.BeginDate)
.Descending(entity.EndDate)
.Ascending(entity.IdRule)
.Ascending(entity.Sum)
.Descending(entity.ODataKind.OpenDate))
```
> $orderby=BeginDate asc,EndDate desc,IdRule asc,Sum asc,ODataKind/OpenDate desc
#### <a name="orderbydesc"/> orderby desc
```csharp
.OrderByDescending(s => s.IdType)
Expand Down
9 changes: 9 additions & 0 deletions src/OData.QueryBuilder/Conventions/Functions/ISortFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace OData.QueryBuilder.Conventions.Functions
{
public interface ISortFunction
{
ISortFunction Ascending<T>(T column);

ISortFunction Descending<T>(T column);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ public interface IODataOptionList<TEntity> : IODataQuery

IODataOptionList<TEntity> OrderBy(Expression<Func<TEntity, object>> entityOrderBy);

IODataOptionList<TEntity> OrderBy(Expression<Func<TEntity, ISortFunction, object>> entityOrderBy);

IODataOptionList<TEntity> OrderByDescending(Expression<Func<TEntity, object>> entityOrderByDescending);

IODataOptionList<TEntity> Top(int number);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OData.QueryBuilder.Resources;
using OData.QueryBuilder.Conventions.Functions;
using OData.QueryBuilder.Resources;
using System;
using System.Linq.Expressions;

Expand All @@ -16,6 +17,8 @@ public interface IODataOptionNested<TEntity>

IODataOptionNested<TEntity> OrderBy(Expression<Func<TEntity, object>> entityNestedOrderBy);

IODataOptionNested<TEntity> OrderBy(Expression<Func<TEntity, ISortFunction, object>> entityOrderBy);

IODataOptionNested<TEntity> OrderByDescending(Expression<Func<TEntity, object>> entityNestedOrderByDescending);

IODataOptionNested<TEntity> Top(int number);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using OData.QueryBuilder.Conventions.Constants;
using OData.QueryBuilder.Conventions.Functions;
using OData.QueryBuilder.Expressions.Visitors;
using OData.QueryBuilder.Options;
using OData.QueryBuilder.Resources;
Expand Down Expand Up @@ -53,6 +54,15 @@ public IODataOptionNested<TEntity> OrderBy(Expression<Func<TEntity, object>> ent
return this;
}

public IODataOptionNested<TEntity> OrderBy(Expression<Func<TEntity, ISortFunction, object>> entityOrderBy)
{
var query = new ODataOptionOrderByExpressionVisitor().ToQuery(entityOrderBy.Body);

_stringBuilder.Append($"{ODataOptionNames.OrderBy}{QuerySeparators.EqualSign}{query}{QuerySeparators.Nested}");

return this;
}

public IODataOptionNested<TEntity> OrderByDescending(Expression<Func<TEntity, object>> entityNestedOrderByDescending)
{
var query = new ODataOptionOrderByExpressionVisitor().ToQuery(entityNestedOrderByDescending.Body);
Expand Down
9 changes: 9 additions & 0 deletions src/OData.QueryBuilder/Conventions/Options/ODataOptionList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,15 @@ public IODataOptionList<TEntity> OrderBy(Expression<Func<TEntity, object>> entit
return this;
}

public IODataOptionList<TEntity> OrderBy(Expression<Func<TEntity, ISortFunction, object>> entityOrderBy)
{
var query = new ODataOptionOrderByExpressionVisitor().ToQuery(entityOrderBy.Body);

_stringBuilder.Append($"{ODataOptionNames.OrderBy}{QuerySeparators.EqualSign}{query}{QuerySeparators.Main}");

return this;
}

public IODataOptionList<TEntity> OrderByDescending(Expression<Func<TEntity, object>> entityOrderByDescending)
{
var query = new ODataOptionOrderByExpressionVisitor().ToQuery(entityOrderByDescending.Body);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
namespace OData.QueryBuilder.Expressions.Visitors
using OData.QueryBuilder.Conventions.Constants;
using OData.QueryBuilder.Conventions.Functions;
using System;
using System.Linq.Expressions;

namespace OData.QueryBuilder.Expressions.Visitors
{
internal class ODataOptionOrderByExpressionVisitor : ODataOptionExpressionVisitor
{
public ODataOptionOrderByExpressionVisitor()
: base()
{
}

protected override string VisitMethodCallExpression(MethodCallExpression methodCallExpression)
{
switch (methodCallExpression.Method.Name)
{
case nameof(ISortFunction.Ascending):
var ascending0 = VisitExpression(methodCallExpression.Arguments[0]);

var ascendingQuery = VisitExpression(methodCallExpression.Object as MethodCallExpression);
var ascendingQueryComma = ascendingQuery == default ? string.Empty : ",";

return $"{ascendingQuery}{ascendingQueryComma}{ascending0} {QuerySorts.Asc}";
case nameof(ISortFunction.Descending):
var descending0 = VisitExpression(methodCallExpression.Arguments[0]);

var descendingQuery = VisitExpression(methodCallExpression.Object as MethodCallExpression);
var descendingQueryComma = descendingQuery == default ? string.Empty : ",";

return $"{descendingQuery}{descendingQueryComma}{descending0} {QuerySorts.Desc}";
default:
throw new NotSupportedException($"Method {methodCallExpression.Method.Name} not supported");
}
}
}
}
51 changes: 50 additions & 1 deletion test/OData.QueryBuilder.Test/ODataQueryOptionListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,26 @@ public void ODataQueryBuilderList_ExpandNested_Top_Success()
uri.OriginalString.Should().Be("http://mock/odata/ODataType?$expand=ODataKindNew($select=IdKind;$top=1;$orderby=EndDate asc)");
}

[Fact(DisplayName = "Expand orderBy multiple sort => Success")]
public void ODataQueryBuilderList_Expand_OrderBy_Multiple_Sort_Success()
{
var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Expand(f =>
{
f.For<ODataKindEntity>(s => s.ODataKindNew)
.Select(s => s.IdKind)
.OrderBy((entity, sort) => sort
.Ascending(entity.OpenDate)
.Descending(entity.ODataCode.Code)
.Ascending(entity.IdKind));
})
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$expand=ODataKindNew($select=IdKind;$orderby=OpenDate asc,ODataCode/Code desc,IdKind asc)");
}

[Fact(DisplayName = "Expand nested orderby desc => Success")]
public void ODataQueryBuilderList_ExpandNested_OrderByDescending_Success()
{
Expand Down Expand Up @@ -147,6 +167,36 @@ public void ODataQueryBuilderList_OrderBy_Simple_Success()
uri.OriginalString.Should().Be("http://mock/odata/ODataType?$orderby=IdType asc");
}

[Fact(DisplayName = "Filter orderBy multiple sort => Success")]
public void ODataQueryBuilderList_Filter_OrderBy_Multiple_Sort_Success()
{
var uri = _odataQueryBuilderDefault
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.OrderBy((entity, sort) => sort
.Ascending(entity.BeginDate)
.Descending(entity.EndDate)
.Ascending(entity.IdRule)
.Ascending(entity.Sum)
.Descending(entity.ODataKind.OpenDate))
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$orderby=BeginDate asc,EndDate desc,IdRule asc,Sum asc,ODataKind/OpenDate desc");
}

[Fact(DisplayName = "Filter orderBy multiple sort => NotSupportedException")]
public void ODataQueryBuilderList_Filter_OrderBy_Multiple_Sort_NotSupportedException()
{
var uri = _odataQueryBuilderDefault
.Invoking(i => i
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.OrderBy((entity, sort) => sort.Equals(1))
.ToUri()
)
.Should().Throw<NotSupportedException>().WithMessage($"Method {nameof(string.Equals)} not supported");
}

[Fact(DisplayName = "OrderByDescending simple => Success")]
public void ODataQueryBuilderList_OrderByDescending_Simple_Success()
{
Expand Down Expand Up @@ -184,7 +234,6 @@ public void ODataQueryBuilderList_Skip_Top_Simple_Success()
uri.OriginalString.Should().Be("http://mock/odata/ODataType?$skip=1&$top=1");
}


[Fact(DisplayName = "Filter call ToString => Success")]
public void ODataQueryBuilderList_Filter_Call_ToString_Success()
{
Expand Down

0 comments on commit 7cdcd89

Please sign in to comment.