Skip to content

Commit

Permalink
Merge pull request #19 from alexandresanlim/master
Browse files Browse the repository at this point in the history
Add ToDictionary() and other things
  • Loading branch information
ZEXSM authored Sep 23, 2019
2 parents f5738f3 + cdca514 commit d7c7b70
Show file tree
Hide file tree
Showing 9 changed files with 191 additions and 11 deletions.
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ Library provides linq syntax and allows you to build OData queries based on the

## Benefits
* Expression is not used to `Compile()`, which generates `MSIL` code, which leads to memory leaks
* Support for nested `OData` extenders with a choice of filtering
* Support `OData` functions `Date`, `Any`, `All`
* Support `OData` operator `IN`
* Support:
* nested `OData` extenders with a choice of filtering
* `all`, `any`
* date functions `date`
* string functions `substringof`, `toupper`
* operator `in`

## 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 @@ -57,9 +60,10 @@ dotnet add -v 1.0.0 OData.QueryBuilder
* [Top](#Top)
* [Skip](#Skip)
* [Count](#Count)
4. Get a Uri request from the builder
4. Get Uri request or collection of operators from the builder
```csharp
odataQueryBuilder.ToUri()
odataQueryBuilder.ToDictionary()
```
## <a name="ByKey"/> ByKey
```csharp
Expand Down Expand Up @@ -212,6 +216,21 @@ var constValue = 3;
&& constStrIds.Contains(s.ODataKind.ODataCode.Code))
```
> $filter=IdRule eq 3 and IsActive and date(EndDate) eq null or EndDate gt 2019-08-18T00:00:00.0000000+03:00 and date(BeginDate) ne null or date(BeginDate) le 2019-08-18 and ODataKind/ODataCode/Code in ('123','512')
```csharp
.Filter(s => s.ODataKind.Color.ToString() == ColorEnum.Blue.ToString()
&& s.ODataKind.Color == ColorEnum.Blue)
```
> $filter=ODataKind/Color eq 'Blue' and ODataKind/Color eq 2
```csharp
var constValue = "p".ToUpper();
var newObject = new ODataTypeEntity { TypeCode = "TypeCodeValue".ToUpper() };

.Filter(s => s.ODataKind.ODataCode.Code.ToUpper().Contains("W")
|| s.ODataKind.ODataCode.Code.Contains(constValue)
|| s.ODataKindNew.ODataCode.Code.Contains(newObject.TypeCode)
|| s.ODataKindNew.ODataCode.Code.Contains("55"))
```
> $filter=substringof('W',toupper(ODataKind/ODataCode/Code)) or substringof('P',ODataKind/ODataCode/Code) or substringof('TYPECODEVALUE',ODataKindNew/ODataCode/Code) or substringof('55',ODataKindNew/ODataCode/Code)
## <a name="OrderBy"/> OrderBy
```csharp
.OrderBy(s => s.IdType)
Expand Down
44 changes: 41 additions & 3 deletions src/OData.QueryBuilder/Extensions/ExpressionExtension.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,21 @@ public static object GetMemberExpressionValue(this MemberExpression memberExpres

public static string GetInSequence(this object arrayObj)
{
if (arrayObj == default(object))
{
return default(string);
}
if (arrayObj is IEnumerable<int>)
{
var inSequenceInt = string.Join(",", arrayObj as IEnumerable<int>);
if (!string.IsNullOrEmpty(inSequenceInt))
{
return $"in ({inSequenceInt})";
}
else
{
return default(string);
}
}

if (arrayObj is IEnumerable<string>)
Expand All @@ -42,6 +50,10 @@ public static string GetInSequence(this object arrayObj)
{
return $"in ('{inSequenceInt}')";
}
else
{
return default(string);
}
}

return string.Empty;
Expand Down Expand Up @@ -86,6 +98,8 @@ public static string ToODataQuery(this ConstantExpression constantExpression)
return $"'{stringVal}'";
case int intVal:
return intVal.ToString();
case object objectVal:
return $"'{objectVal.ToString()}'";
default:
return "null";
}
Expand Down Expand Up @@ -258,18 +272,42 @@ public static string ToODataQuery(this Expression expression, string queryString
}
else if (methodCallExpression.Object is MemberExpression)
{
resource = (methodCallExpression.Object as MemberExpression).GetMemberExpressionValue();
resource = (methodCallExpression.Object as MemberExpression).GetMemberExpressionValue() ??
methodCallExpression.Object.ToODataQuery(string.Empty);

filter = methodCallExpression.Arguments[0].ToODataQuery(queryString);
}
else if (methodCallExpression.Object is MethodCallExpression)
{
resource = methodCallExpression.Object.ToODataQuery(string.Empty);
filter = methodCallExpression.Arguments[0].ToODataQuery(string.Empty);
}

var inSequence = resource.GetInSequence();

if (!string.IsNullOrEmpty(inSequence))
if (inSequence != default(string))
{
return $"{filter} {inSequence}";
if (!string.IsNullOrEmpty(inSequence))
{
return $"{filter} {inSequence}";
}
else
{
return $"substringof({filter},{resource})";
}
}
}

if (methodName == nameof(string.ToUpper))
{
return $"toupper({methodCallExpression.Object.ToODataQuery(string.Empty)})";
}

if (methodName == nameof(ToString))
{
return methodCallExpression.Object.ToODataQuery(string.Empty);
}

return string.Empty;

case NewExpression newExpression:
Expand Down
3 changes: 3 additions & 0 deletions src/OData.QueryBuilder/Parameters/IODataQueryParameter.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;

namespace OData.QueryBuilder.Parameters
{
public interface IODataQueryParameter
{
Uri ToUri();

Dictionary<string, string> ToDictionary();
}
}
18 changes: 18 additions & 0 deletions src/OData.QueryBuilder/Parameters/ODataQueryParameterKey.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using OData.QueryBuilder.Builders.Nested;
using OData.QueryBuilder.Extensions;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

Expand Down Expand Up @@ -43,5 +44,22 @@ public IODataQueryParameterKey<TEntity> Select(Expression<Func<TEntity, object>>
}

public Uri ToUri() => new Uri(_queryBuilder.ToString().TrimEnd('&'));

public Dictionary<string, string> ToDictionary()
{
var odataOperators = _queryBuilder.ToString()
.Split(new char[2] { '?', '&' }, StringSplitOptions.RemoveEmptyEntries);

var dictionary = new Dictionary<string, string>(odataOperators.Length - 1);

for (var step = 1; step < odataOperators.Length; step++)
{
var odataOperator = odataOperators[step].Split('=');

dictionary.Add(odataOperator[0], odataOperator[1]);
}

return dictionary;
}
}
}
18 changes: 18 additions & 0 deletions src/OData.QueryBuilder/Parameters/ODataQueryParameterList.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using OData.QueryBuilder.Builders.Nested;
using OData.QueryBuilder.Extensions;
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Text;

Expand Down Expand Up @@ -91,5 +92,22 @@ public IODataQueryParameterList<TEntity> Count(bool value = true)
}

public Uri ToUri() => new Uri(_queryBuilder.ToString().TrimEnd('&'));

public Dictionary<string, string> ToDictionary()
{
var odataOperators = _queryBuilder.ToString()
.Split(new char[2] { '?', '&' }, StringSplitOptions.RemoveEmptyEntries);

var dictionary = new Dictionary<string, string>(odataOperators.Length - 1);

for (var step = 1; step < odataOperators.Length; step++)
{
var odataOperator = odataOperators[step].Split('=');

dictionary.Add(odataOperator[0], odataOperator[1]);
}

return dictionary;
}
}
}
11 changes: 11 additions & 0 deletions test/OData.QueryBuilder.Test/Fakes/ColorEnum.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
namespace OData.QueryBuilder.Test.Fakes
{
public enum ColorEnum
{
Green,
Red,
Blue,
Yellow,
Black
}
}
2 changes: 2 additions & 0 deletions test/OData.QueryBuilder.Test/Fakes/ODataKindEntity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,7 @@ public class ODataKindEntity
public IEnumerable<int> Sequence { get; set; }

public int[] SequenceArray { get; set; }

public ColorEnum Color { get; set; }
}
}
10 changes: 10 additions & 0 deletions test/OData.QueryBuilder.Test/ODataQueryBuilderByKeyTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,15 @@ public void ODataQueryBuilderKey_Expand_Nested_Filter_Success()

uri.OriginalString.Should().Be("http://mock/odata/ODataType(223123123)?$expand=ODataKind($filter=IdKind eq 1;$select=IdKind)&$select=IdType,Sum");
}

[Fact(DisplayName = "(ODataQueryBuilderKey) ToDicionary => Success")]
public void ToDicionaryTest()
{
var uri = _odataQueryBuilder
.For<ODataTypeEntity>(s => s.ODataType)
.ByKey("223123123")
.Expand(s => s.ODataKind)
.ToDictionary();
}
}
}
69 changes: 65 additions & 4 deletions test/OData.QueryBuilder.Test/ODataQueryBuilderByListTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,12 @@ public void ODataQueryBuilderList_Expand_Filter_Select_OrderBy_OrderByDescending
)
.Select(s => new { s.ODataKind, s.Sum })
.OrderBy(s => new { s.IdType })
.OrderByDescending(s => s.IdType)
.Skip(1)
.Top(1)
.Count()
.ToUri();

uri.OriginalString.Should().Be($"http://mock/odata/ODataType?$expand=ODataKind&$filter=IdType lt 2 and ODataKind/ODataCode/IdCode ge 3 or IdType eq 5 and IdRule ne null and IdRule eq null&$select=ODataKind,Sum&$orderby=IdType asc&$orderby=IdType desc&$skip=1&$top=1&$count=true");
uri.OriginalString.Should().Be($"http://mock/odata/ODataType?$expand=ODataKind&$filter=IdType lt 2 and ODataKind/ODataCode/IdCode ge 3 or IdType eq 5 and IdRule ne null and IdRule eq null&$select=ODataKind,Sum&$orderby=IdType asc&$skip=1&$top=1&$count=true");
}

[Fact(DisplayName = "(ODataQueryBuilderList) Function Date => Success")]
Expand Down Expand Up @@ -229,12 +228,31 @@ public void ODataQueryBuilderList_Operator_In_Success()
uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=ODataKind/ODataCode/Code in ('123','512') and ODataKind/ODataCode/Code in ('123','512') and IdType in (123,512) and IdType in (123,512) and IdRule in (123,512) and IdRule in (123,512) and ODataKind/IdKind in (123,512) and ODataKind/ODataCode/IdCode in (123,512)");
}

[Fact(DisplayName = "(ODataQueryBuilderList) Contains with ToUpper Simple Test => Success")]
public void ODataQueryBuilderList_Test_ContainsSimple()
{
var constValue = "p".ToUpper();
var newObject = new ODataTypeEntity { TypeCode = "TypeCodeValue".ToUpper() };
var uri = _odataQueryBuilder
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter(s =>
s.ODataKind.ODataCode.Code.ToUpper().Contains("W")
|| s.ODataKind.ODataCode.Code.Contains(constValue)
|| s.ODataKindNew.ODataCode.Code.Contains(newObject.TypeCode)
|| s.ODataKindNew.ODataCode.Code.Contains("55"))
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=substringof('W',toupper(ODataKind/ODataCode/Code)) or substringof('P',ODataKind/ODataCode/Code) or substringof('TYPECODEVALUE',ODataKindNew/ODataCode/Code) or substringof('55',ODataKindNew/ODataCode/Code)");
}

[Fact(DisplayName = "(ODataQueryBuilderList) Operator IN empty => Success")]
public void ODataQueryBuilderList_Operator_In_Empty_Success()
{
var constStrIds = default(IEnumerable<string>);
var constStrListIds = new string[] { }.ToList();
var constEmprtyStrListIds = new string[] { }.ToList();
var constIntIds = default(int[]);
var constEmptyIntIds = new int[0];
var constIntListIds = new[] { 123, 512 }.ToList();
var newObject = new ODataTypeEntity { ODataKind = new ODataKindEntity { Sequence = constIntListIds } };
var newObjectSequenceArray = new ODataTypeEntity { ODataKind = new ODataKindEntity { SequenceArray = constIntIds } };
Expand All @@ -243,8 +261,9 @@ public void ODataQueryBuilderList_Operator_In_Empty_Success()
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter(s => constStrIds.Contains(s.ODataKind.ODataCode.Code)
&& constStrListIds.Contains(s.ODataKind.ODataCode.Code)
&& constEmprtyStrListIds.Contains(s.ODataKind.ODataCode.Code)
&& constIntIds.Contains(s.IdType)
&& constEmptyIntIds.Contains(s.IdType)
&& constIntListIds.Contains(s.IdType)
&& constIntIds.Contains((int)s.IdRule)
&& constIntListIds.Contains((int)s.IdRule)
Expand Down Expand Up @@ -317,5 +336,47 @@ public void ODataQueryBuilderList_Filter_Not__Bool_Success()

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=IsActive and not IsOpen");
}

[Fact(DisplayName = "(ODataQueryBuilderList) ToDictionary")]
public void ToDicionaryTest()
{
var constValue = false;
var newObject = new ODataTypeEntity { IsOpen = false };

var dictionary = _odataQueryBuilder
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter(s => s.IsActive
&& s.IsOpen == constValue
&& s.IsOpen == true
&& s.ODataKind.ODataCode.IdActive == newObject.IsOpen)
.Skip(1)
.Top(10)
.ToDictionary();

var resultEquivalent = new Dictionary<string, string>
{
{ "$filter", "IsActive and IsOpen eq false and IsOpen eq true and ODataKind/ODataCode/IdActive eq false" },
{ "$skip", "1" },
{ "$top", "10" }
};

dictionary.Should().BeEquivalentTo(resultEquivalent);
}

[Fact(DisplayName = "(ODataQueryBuilderList) Filter Enum")]
public void FilterEnumTest()
{
var uri = _odataQueryBuilder
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter(s => s.ODataKind.Color.ToString() == ColorEnum.Blue.ToString()
&& s.ODataKind.Color == ColorEnum.Blue)
.Skip(1)
.Top(10)
.ToUri();

uri.OriginalString.Should().Be("http://mock/odata/ODataType?$filter=ODataKind/Color eq 'Blue' and ODataKind/Color eq 2&$skip=1&$top=10");
}
}
}

0 comments on commit d7c7b70

Please sign in to comment.