In my web page, there are multiple textboxes where users can enter complex queries. A complex query can contain any of the following Parameter
s:
XX*
that matches everything starting with XX*XX
for everything ending in XX*XX*
for everything containing XXXX123-XX129
for everthing matching the inclusive range from XX123 to XX129XX444
for an exact individual valueImplementing that is not my problem; my problem is implementing it for multiple values in a reuseable manner.
The sample below filters Items
on the Item.Value
property.
public static IQueryable<Item> WithMatchingItemValues(this IQueryable<Item> items,
IEnumerable<Parameter> itemValues)
{
var parameters = (itemValues ?? Enumerable.Empty<Parameter>()).ToList();
if (parameters.IsEmpty()) return items;
var (wildCards, exactMatches) = parameters.SplitOnWildCards();
var predicate = PredicateBuilder.New<Item>(); // https://github.com/scottksmith95/LINQKit
wildCards.ForEach(wc =>
{
switch (wc)
{
case WildCardStartsWith startsWith:
predicate = predicate.Or(s => s.Value.ToUpper().StartsWith(startsWith.ToString()));
break;
case WildCardContains contains:
predicate = predicate.Or(s => s.Value.ToUpper().Contains(contains.ToString()));
break;
case WildCardEndsWith endsWith:
predicate = predicate.Or(s => s.Value.ToUpper().EndsWith(endsWith.ToString()));
break;
}
});
if (exactMatches.Any())
predicate = predicate.Or(s => exactMatches.Select(p => p.Value).Contains(s.Value.ToUpper()));
return items.AsExpandableEFCore().Where(predicate);
}
How can I refactor this so I can "pass in" the Item.Value
to the method, so I can also pass in Item.PartNumber
or Item.Foo
without having to duplicate all this code for every property I want to filter? I can't just pass in Item.Value
... that's just a string, and won't work in the lambda statements.
Write your method to take an ExpressionLambda
that represents the field reference:
public static IQueryable<Item> WithMatchingItemValues(this IQueryable<Item> items,
IEnumerable<Parameter> itemValues,
Expression<Func<Item,string>> field)
Then in the code that needs to refer to the field, use LINQKit's Invoke
method:
case WildCardStartsWith startsWith:
predicate = predicate.Or(s => field.Invoke(s).ToUpper().StartsWith(startsWith.ToString()));
Finally, use LINQKit's Expand
method to inline expand the field
references, or use AsExpandable
as you have on the data source:
if (exactMatches.Any())
predicate = predicate.Or(s => exactMatches.Select(p => p.Value).Contains(field.Invoke(s).ToUpper()));
return items.AsExpandableEFCore().Where(predicate);