-
Notifications
You must be signed in to change notification settings - Fork 37
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
Added a new filter expression for multiple search on strings #205
Added a new filter expression for multiple search on strings #205
Conversation
Added tests for the new expression
Thanks! I will review and hopefully give some pointers/ideas tomorrow morning, but it looks quite good at first glance. |
After looking through the code again, I think the changes need to be one level lower for this to work for all types. Already when parsing the query parameters we assume there is one value. This will fail in the multi-filter scenario for everything except strings (because most things cannot contain a comma). Even at that spot (or for clarity, this spot), we should assume there can be more than one filter value. So the values are always a Then in the filter interpreter instead of effectively creating an expression like: collection.Where(item => item == value); we should create one like: collection.Where(item => item == value1 || item == value2 ... || item == valueN); Note that since we don't know the types at compile time, we use reflection to build these expressions. Also note that the I realize some of this is quite complex (especially building the expressions), so please let me know if you get stuck on anything. I'll be happy to help out with specific parts, either by writing code or by helping you through it. In any case thank you for your work on this so far 😄 |
So I did some tinkering around and am now at a point where I don't really know how to go from here. I think my experience with C# starts to 'shine'. I never worked with reflection. Any chance you could give me some hint how to fix this one? I am stuck here ~Cheers |
@PhyberApex sorry for the late reply. The ultimate goal of this piece of code is to generate an The secondary goal then is to create expressions for As you can see, that method is generic, because all the The line you linked essentially does the following:
However since First we get the reflection After doing all that we can call Hopefully (if the above was clear), you can see that the |
Just to make sure here. With
You mean like it was before I started or how it is right now? Because currently I can't build the solution and I can't really figure out what to do to fix that. So I am not sure if you said that what I did so far is in the wrong direction or if I am going the right way. Because the splitting of the actual query parameter has to happen somewhere. ~Cheers |
I realize this line "everything else can stay the same" is super confusing 😄 sorry about that. What I meant was the code as it is in that specific method can stay the same, as you have it. The build fails because it says Down lower at I definitely think you're on the right track and I think it's all very close to working. This part of the code is certainly not the most straightforward. Thanks for doing this work! * (There's also some build errors in the test project because you changed |
Thank you for helping me understand what I am actually doing here and always being so responsive! I will take a look at this soon! ~Cheers |
Also added expression chaining
Sorry for not trying again sooner, but I finally got around to give this another try this evening. With your help I got somewhat further with this. I changed the parameter to object[] and added the expression chaining. It does compile now but I get a runtime error on the reflection part while running the tests that a list of object could not be cast to int/string. My guess would be that as the return value of PS: ~Cheers |
So apparently it was just way to late yesterday for me and I got this working now. There are other errors now in the chaining of the expression. Will take a look at this soonish. ~Cheers |
Saule/Queries/Lambda.cs
Outdated
MemberExpression propertyExpression, | ||
ParameterExpression parameter) | ||
{ | ||
var curriedBody = new FilterLambdaVisitor<TProperty>(propertyExpression, constant).Visit(expression.Body); | ||
// initialize the expression with a always true one to make chaining possible | ||
Expression curriedBody = Expression.Lambda(Expression.Constant(true)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should be false
? (true || a == b || a == c || a == d
is always true)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are right my bad!
Saule/Queries/Lambda.cs
Outdated
@@ -50,15 +51,27 @@ internal static List<object> TryConvert(List<string> values, Type type) | |||
return parsedList; | |||
} | |||
|
|||
internal static object TryConvert(string value, Type type) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to call this for each filter, so instead of
var parsedValues = TryConvert(values, valueType);
(line 17)
var parsedValues = values.Select(v => TryConvert(v, valueType)).ToList();
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I had two TryConvert
methods. One which took an array and one that only took a single one. I have those reduced to one now and added the Select
function at the appropriate places.
… into feature/csvfilter
The github outtage is given me quite a hard time atm. Sorry for comment spamming! I did add the changes like you suggested. Will take a look at the other stuff soon. ~Cheers |
I think this is it. Please let me know if you think there need to be any additional tests or changes. Also if you could let me know how to run that code style check within Visual Studio that would help me out not having to commit to wait for AppVeyor to tell me whats wrong :) ~Cheers |
As I tried to get the nested filter working for the other PR I discovered an error in my CSV regex if the value had whitespace. I added a unit test for this as well. ~Cheers |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks really great! Thanks very much for putting so much time and effort into this.
// Spliting the string into multiple values with csv notation | ||
Values = new List<string>(); | ||
|
||
var match = new Regex("(?:,\"|^\")(\"\"|[\\w\\W]*?)(?=\",|\"$)|(?:,(?!\")|^(?!\"))([^,]*?)(?=$|,)").Matches(values); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you maybe put a comment that explains this regex? Some time in the future we may want to remember how it works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried my best to explain this one. I hope that it's enough.
var people = Get.People(100).ToList().AsQueryable(); | ||
var expected = people.Where(c => c.LastName == "Russel" || c.LastName == "Comma,Test").ToList(); | ||
|
||
var query = GetQuery(new[] { "LastName" }, new[] { "Russel,\"Comma,Test\"" }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we can also see what happens on some edge cases/malformed strings such as "a"b
(no comma), "ab,c
(no end quote), etc.
FWIW i'm not sure what should happen in this case.. I would be fine with a 400 although technically that would be a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now this is failing silently as anything not captured will be ignored. It's certainly possible to add a 400. I am not sure if this would be the best way tho.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would vote that either
- We throw an error for this (400 bad request)
- We identify the filter as "malformed" and interpret it as a single filter value
I don't think we should let parts drop, throw 500, etc.
I think you're right that option 2 is probably better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a few checks and one more unit test for this.
Added explanation to the regex Added checks to ensure well formed csv
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great!! I'm really happy with this result. Thanks again for all the effort. I know this was a complicated one! 💪
Btw. do you want me to help you with the documentation of the PRs I did as well? I discovered that you are using the github pages to host the official website for saule and the source files for that are in the doc folder. So if you are okay with that I'd more than happy to do so. ~Cheers |
@PhyberApex yes, if you have time that would be great. For this PR I don't know if we really need much documentation, since this is basically what the spec describes. |
Added tests for the new expression