Skip to content

Add IN filter operation for array searching #288

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Jun 6, 2018

Conversation

milosloub
Copy link
Contributor

@milosloub milosloub commented May 29, 2018

Add new IN filtering options:
a) "?filter[attribute]=in:value1,value2,value3"
b) "?include=relation&filter[relation.attribute]=in:value1,value2,value3"

Edited files:

  1. FilterOperations. Only added @in value to enum.
  2. IQueryableExtensions. IN operation can´t be handled in standard Expression builder as other properties, so I created new ArrayContainsPredicate() handler, to build predicate like this
    Items.Where(i => array.Contains(i.attribute)) which is translated to SQL as WHERE IN clause.
  3. TypeHelper. There is new ConvertListType method, which is only helper method for ArrayContainsPredicate()
  4. QueryParser. There is new method GetFilterOperation(). It is shared for standard and IN filter operations. In case of IN, value can't be comma separated, so whole IN value is send as one FilterQuery and then converted to array in Filter() method of IQueryableExtensions class.
  5. There are two acceptance tests for attribute test case and for included relationship test case.

I'll be glad for your feedback

@wayne-o
Copy link
Contributor

wayne-o commented May 29, 2018 via email

@jaredcnance jaredcnance mentioned this pull request Jun 5, 2018
Copy link
Contributor

@jaredcnance jaredcnance left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is fantastic! Great work. Just a few comments. Also, opened #289 for documentation.

foreach (var val in values)
// InArray case
var op = GetFilterOperation(value);
if (op == [email protected]())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should ignore case

op.Equals(FilterOperations.@in.ToString(), StringComparer.OrdinalIgnoreCase)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, good point

@@ -131,5 +133,74 @@ public AttributeFilterTests(TestFixture<TestStartup> fixture)
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
Assert.False(deserializedTodoItems.Any(i => i.Ordinal == todoItem.Ordinal));
}

[Fact]
public async Task Can_Filter_On_In_Array_Values()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need a negative test, i.e. instances with property values that are not included in the array get excluded from the results

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I'll add this test case.

if (Enum.TryParse(operation[0], out FilterOperations op) == false)
return string.Empty;

return operation[0];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's assign operation[0] to a local var so we don't have to perform the index lookup twice.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, no problem

MethodInfo method = null;
foreach (var m in containsMethods)
{
if (m.GetParameters().Count() == 2)
Copy link
Contributor

@jaredcnance jaredcnance Jun 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I recommend consolidating this into:

var method = typeof(Enumerable)
  .GetMethods(BindingFlags.Static | BindingFlags.Public)
  .Where(m => 
    m.Name == nameof(Enumerable.Contains) // not sure if this actually works, but would be nice
    && m.GetParameters().Count() == 2)
  .First(m => m);

Also, the result of this query never changes. It would probably be best to run it in a static constructor and store the MethodInfo in a private field so we don't do this on every invocation at runtime. I suspect there are several places in these extensions that could benefit from such a refactor

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uf, this is much better solution. And method will be singleton, good point.

foreach (var item in convertedArray)
{
list.Add(item);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 things:

  1. the signature return should be IList instead of object
  2. any reason for multiple enumeration? it seems like you would enumerate values once and perform ConvertType at the same time you add it to list. L68-L72 don't seem necessary to me.

Copy link
Contributor Author

@milosloub milosloub Jun 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This statement was hell of pain:
Expression.Call(method, new Expression[] { Expression.Constant(obj), member });

Expression.Call does some unboxing during runtime and if there are uncompatible object types, it throws exception of Type mismatch. So idea of this is to convert boxed types in object to compatible IList of target property.

I'll make some investigation later to simplify enumerations.

}

[Fact]
public async Task Can_Filter_On_Related_In_Array_Values()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 👏 👏

@milosloub
Copy link
Contributor Author

milosloub commented Jun 6, 2018

I made requested changes. Please have a look.

Copy link
Contributor

@jaredcnance jaredcnance left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks great! Thanks a lot! Will be publishing a release shortly.

.First();
}
return _containsMethod;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉 awesome!

@jaredcnance jaredcnance merged commit a1832f7 into json-api-dotnet:master Jun 6, 2018
jaredcnance added a commit that referenced this pull request Aug 12, 2018
Add IN filter operation for array searching
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

3 participants