Skip to content

Commit

Permalink
support cast function (#98)
Browse files Browse the repository at this point in the history
* support cast function
  • Loading branch information
ZEXSM authored May 7, 2022
1 parent 7258abc commit 0168279
Show file tree
Hide file tree
Showing 13 changed files with 166 additions and 51 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ Library for creating complex OData queries (OData version 4.01) based on data mo
* [`indexof`](#indexof)
* [`startswith`](#startswith)
* [`length`](#length)
* type
* [`cast`](#cast)
* sorting by several fields with indication of direction

## Installation
Expand Down Expand Up @@ -329,6 +331,15 @@ var constValue = 3;
```
> $filter=length(ODataKind/ODataCode/Code) gt 0

## Usage type functions

#### <a name="cast"/> cast

```csharp
.Filter((s, f) => f.Contains(f.Cast(s.ODataKindNew.ODataCode.Code, "Edm.String"), "55"))
```
> $filter=contains(cast(ODataKindNew/ODataCode/Code,Edm.String),'55')

## Usage other functions

#### ConvertEnumToString
Expand Down
13 changes: 0 additions & 13 deletions src/OData.QueryBuilder/Conventions/Functions/IConvertFunction.cs

This file was deleted.

36 changes: 36 additions & 0 deletions src/OData.QueryBuilder/Conventions/Functions/ICustomFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Collections.Generic;

namespace OData.QueryBuilder.Conventions.Functions
{
/// <summary>
/// Custom functions
/// </summary>
public interface ICustomFunction
{
/// <summary>
/// Convert enum to string
/// </summary>
T ConvertEnumToString<T>(T value) where T : Enum;

/// <summary>
/// Convert datetime to string
/// </summary>
DateTime ConvertDateTimeToString(DateTime value, string format);

/// <summary>
/// Convert datetimeoffset to string
/// </summary>
DateTimeOffset ConvertDateTimeOffsetToString(DateTimeOffset value, string format);

/// <summary>
/// Replace characters
/// </summary>
string ReplaceCharacters(string value, IDictionary<string, string> keyValuePairs);

/// <summary>
/// Replace characters
/// </summary>
IEnumerable<string> ReplaceCharacters(IEnumerable<string> values, IDictionary<string, string> keyValuePairs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace OData.QueryBuilder.Conventions.Functions
{
public interface IODataDateFunction
/// <summary>
/// https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_DateandTimeFunctions
/// </summary>
public interface IODataDateTimeFunction
{
/// <summary>
/// http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_date
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
namespace OData.QueryBuilder.Conventions.Functions
{
public interface IODataFunction : IODataStringAndCollectionFunction, IODataDateFunction, IConvertFunction, IReplaceFunction
/// <summary>
/// OData functions
/// </summary>
public interface IODataFunction : IODataStringAndCollectionFunction, IODataDateTimeFunction, ICustomFunction, ITypeFunction
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public interface IODataStringAndCollectionFunction

/// <summary>
/// http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_startswith
/// </summary>
bool StartsWith(string columnName, string value);

/// <summary>
Expand Down
11 changes: 0 additions & 11 deletions src/OData.QueryBuilder/Conventions/Functions/IReplaceFunction.cs

This file was deleted.

9 changes: 9 additions & 0 deletions src/OData.QueryBuilder/Conventions/Functions/ISortFunction.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
namespace OData.QueryBuilder.Conventions.Functions
{
/// <summary>
/// Sort functions
/// </summary>
public interface ISortFunction
{
/// <summary>
/// Sort ascending
/// </summary>
ISortFunction Ascending<T>(T column);

/// <summary>
/// Sort descending
/// </summary>
ISortFunction Descending<T>(T column);
}
}
16 changes: 16 additions & 0 deletions src/OData.QueryBuilder/Conventions/Functions/ITypeFunction.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
namespace OData.QueryBuilder.Conventions.Functions
{
/// <summary>
/// https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_TypeFunctions
/// </summary>
public interface ITypeFunction
{
/// <summary>
/// https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part2-url-conventions.html#sec_cast
/// </summary>
/// <param name="columnName">Column name</param>
/// <param name="type">https://docs.microsoft.com/en-us/dotnet/framework/data/adonet/entity-data-model-primitive-data-types#primitive-data-types-supported-in-the-entity-data-model</param>
/// <returns></returns>
string Cast(string columnName, string type);
}
}
2 changes: 1 addition & 1 deletion src/OData.QueryBuilder/Expressions/ValueExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected virtual object GetValueOfMethodCallExpression(MethodCallExpression met
{
switch (methodCallExpression.Method.Name)
{
case nameof(IReplaceFunction.ReplaceCharacters):
case nameof(ICustomFunction.ReplaceCharacters):
var @symbol0 = GetValue(methodCallExpression.Arguments[0]) as ICollection<string>;
var @symbol1 = GetValue(methodCallExpression.Arguments[1]) as IDictionary<string, string>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -178,17 +178,17 @@ protected override string VisitMethodCallExpression(MethodCallExpression methodC
var length0 = VisitExpression(methodCallExpression.Arguments[0]);

return $"{nameof(IODataStringAndCollectionFunction.Length).ToLowerInvariant()}({length0})";
case nameof(IConvertFunction.ConvertEnumToString):
case nameof(ICustomFunction.ConvertEnumToString):
return $"'{_valueExpression.GetValue(methodCallExpression.Arguments[0])}'";
case nameof(IConvertFunction.ConvertDateTimeToString):
case nameof(ICustomFunction.ConvertDateTimeToString):
var dateTime = (DateTime)_valueExpression.GetValue(methodCallExpression.Arguments[0]);

return dateTime.ToString((string)_valueExpression.GetValue(methodCallExpression.Arguments[1]));
case nameof(IConvertFunction.ConvertDateTimeOffsetToString):
case nameof(ICustomFunction.ConvertDateTimeOffsetToString):
var dateTimeOffset = (DateTimeOffset)_valueExpression.GetValue(methodCallExpression.Arguments[0]);

return dateTimeOffset.ToString((string)_valueExpression.GetValue(methodCallExpression.Arguments[1]));
case nameof(IReplaceFunction.ReplaceCharacters):
case nameof(ICustomFunction.ReplaceCharacters):
var @symbol0 = _valueExpression.GetValue(methodCallExpression.Arguments[0]).ToQuery();
var @symbol1 = _valueExpression.GetValue(methodCallExpression.Arguments[1]);

Expand All @@ -198,6 +198,21 @@ protected override string VisitMethodCallExpression(MethodCallExpression methodC
}

return @symbol0.ReplaceWithStringBuilder(@symbol1 as IDictionary<string, string>);
case nameof(ITypeFunction.Cast):
var cast0 = VisitExpression(methodCallExpression.Arguments[0]);
var cast1 = _valueExpression.GetValue(methodCallExpression.Arguments[1]) as string;

if (string.IsNullOrEmpty(cast1))
{
if (!_odataQueryBuilderOptions.SuppressExceptionOfNullOrEmptyFunctionArgs)
{
throw new ArgumentException("Type is empty or null");
}

return default;
}

return $"{nameof(ITypeFunction.Cast).ToLowerInvariant()}({cast0},{cast1})";
case nameof(string.ToString):
return _valueExpression.GetValue(methodCallExpression.Object).ToString().ToQuery();
default:
Expand Down
41 changes: 21 additions & 20 deletions src/OData.QueryBuilder/OData.QueryBuilder.csproj
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>OData.QueryBuilder</PackageId>
<PackageVersion>$(PackageVersion)</PackageVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/ZEXSM/OData.QueryBuilder</PackageProjectUrl>
<RepositoryUrl>https://github.com/ZEXSM/OData.QueryBuilder</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>odata;querybuilder;dotnet;charp</PackageTags>
<Authors>ZEXSM</Authors>
<Description>The library primarily targets OData Version 4.01 and provides linq syntax for creating OData queries based on a data model.</Description>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<PropertyGroup>
<IsPackable>true</IsPackable>
<IncludeSymbols>true</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
<TargetFramework>netstandard2.0</TargetFramework>
<PackageId>OData.QueryBuilder</PackageId>
<PackageVersion>$(PackageVersion)</PackageVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
<PackageProjectUrl>https://github.com/ZEXSM/OData.QueryBuilder</PackageProjectUrl>
<RepositoryUrl>https://github.com/ZEXSM/OData.QueryBuilder</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<PackageTags>odata;querybuilder;dotnet;charp</PackageTags>
<Authors>ZEXSM</Authors>
<Description>The library primarily targets OData Version 4.01 and provides linq syntax for creating OData queries based on a data model.</Description>
<LangVersion>8.0</LangVersion>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<Target Name="RestoreTools" BeforeTargets="Build">
<Exec Command="dotnet tool restore" />
</Target>
<Target Name="RestoreTools" BeforeTargets="Build">
<Exec Command="dotnet tool restore" />
</Target>

</Project>
44 changes: 44 additions & 0 deletions test/OData.QueryBuilder.Test/ODataQueryCollectionTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1415,5 +1415,49 @@ public void ODataQueryBuilder_Function_Length_Success()

uri.Should().Be("ODataType?$filter=length(TypeCode) gt 0");
}

[Fact(DisplayName = "Function Cast => Success")]
public void ODataQueryBuilder_Function_Cast_Success()
{
var uri = new ODataQueryBuilder<ODataInfoContainer>()
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f) => f.Contains(f.Cast(s.ODataKindNew.ODataCode.Code, "Edm.String"), "55"))
.ToUri();

uri.Should().Be("ODataType?$filter=contains(cast(ODataKindNew/ODataCode/Code,Edm.String),'55')");
}

[Theory(DisplayName = "Function Cast => Exception")]
[InlineData(null)]
[InlineData("")]
public void ODataQueryBuilder_Function_Cast_Exception(string value)
{
_odataQueryBuilderDefault
.Invoking((r) => r
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f) => f.Contains(f.Cast(s.ODataKindNew.ODataCode.Code, value), "55"))
.ToUri())
.Should().Throw<ArgumentException>().WithMessage("Type is empty or null");
}

[Theory(DisplayName = "Function Cast => Skip Exception")]
[InlineData(null)]
[InlineData("")]
public void ODataQueryBuilder_Function_Cast_Skip_Exception(string value)
{
var odataQueryBuilderOptions = new ODataQueryBuilderOptions { SuppressExceptionOfNullOrEmptyFunctionArgs = true };
var odataQueryBuilder = new ODataQueryBuilder<ODataInfoContainer>(
_commonFixture.BaseUri, odataQueryBuilderOptions);

var uri = odataQueryBuilder
.For<ODataTypeEntity>(s => s.ODataType)
.ByList()
.Filter((s, f) => f.Contains(f.Cast(s.ODataKindNew.ODataCode.Code, value), "55"))
.ToUri();

uri.Should().Be("http://mock/odata/ODataType?$filter=contains(,'55')");
}
}
}

0 comments on commit 0168279

Please sign in to comment.