ZEXSM / OData.QueryBuilder

OData.QueryBuilder - library for creating complex OData queries (OData version 4.01) based on data models with linq syntax.
MIT License
71 stars 31 forks source link

Dynamics Or Filter support #120

Open VasylZvarydchuk opened 6 months ago

VasylZvarydchuk commented 6 months ago

Hi, my OData service doesn't support In statement and I need to make code like that:

var emails = new[] {"email1@gmail.com", "email2@yahoo.com"}

 var builder = ODataQueryBuilder(baseUrl).For<MyEntity>(nameof(MyEntity)).ByList()

for(email in emails) {
   builder.Filter(t => t.Email== email)
}

but this code will generate Url like tha

baseUrl/MyEntity?$filter=Email eq 'email1@gmail.com' and Email eq 'email2@yahoo.com'

How can I get the filter dynamically with OR statement instead of AND? I would like to have code like that

for(email in emails) {
   builder.OrFilter(t => t.Email== email)
}

that will generate this url

baseUrl/MyEntity?$filter=Email eq 'email1@gmail.com' or Email eq 'email2@yahoo.com'

Is it possible to add such support? Thanks

LinusCenterstrom commented 5 months ago

While not explicitly supported this is still doable by dynamically creating the expression.

Here is an example in the form of a test using the TestClasses in this library

var validValues = new List<string>
{
    "44",
    "45"
};

var entityType = typeof(ODataTypeEntity);
var filterParameter = Expression.Parameter(entityType, "s");

var expr = validValues
    .Select(v => Expression.Equal(
        Expression.Property(filterParameter, nameof(ODataTypeEntity.TypeCode)),
        Expression.Constant(v)))
    .Aggregate(Expression.OrElse);

var filter = Expression.Lambda<Func<ODataTypeEntity, bool>>(expr, filterParameter);

var uri = _odataQueryBuilderDefault
    .For<ODataTypeEntity>(s => s.ODataType)
    .ByList()
    .Filter(filter)
    .ToUri();

uri.Should().Be("http://mock/odata/ODataType?$filter=TypeCode eq '44' or TypeCode eq '45'");

There are also other ways to construct the expression, for example by having a list of expressions and merging them. You might want to take a look at that to see if there is another way you prefer.

VasylZvarydchuk commented 5 months ago

LinusCenterstrom

Thank you very much for the suggestion. That's exactly how did I solve it for now:


    public static Expression<Func<TEntity, bool>> GetOrFilter<TEntity>(string[] items, string parameterName)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(TEntity), "t");
        var filterProperty = typeof(TEntity).GetProperty(parameterName);

        Expression body = Expression.Equal(
            Expression.Property(parameter, filterProperty),
            Expression.Constant(Uri.EscapeDataString(items[0])));

        for (var i = 1; i < items.Length; i++)
        {
            // Create an expression for (t.<parameterName> == item[i])
            var equalsExpression = Expression.Equal(
                Expression.Property(parameter, filterProperty),
                Expression.Constant(Uri.EscapeDataString(items[i])));

            body = Expression.OrElse(body, equalsExpression);
        }

        Expression<Func<TEntity, bool>> filter = Expression.Lambda<Func<TEntity, bool>>(body, parameter);

        return filter;
    }

But I'd like to have such code in Framework itself and also if I want a more complex scenario like that

http://mock/odata/ODataType?$filter=(TypeCode eq '44' and Param2 eq '1') or (TypeCode eq '45' and Param2 eq '2')

In that case such expression will become more complex and easier to use by old school method with string manipulations.

LinusCenterstrom commented 5 months ago

But I'd like to have such code in Framework itself and also if I want a more complex scenario like that

http://mock/odata/ODataType?$filter=(TypeCode eq '44' and Param2 eq '1') or (TypeCode eq '45' and Param2 eq '2')

In that case such expression will become more complex and easier to use by old school method with string manipulations.

Agreed, it can get confusing quickly.