I am in need of some assistance, I have a method which is repeated 6 times in my class, and the only thing that changes in each of the methods is the LINQ property (in the example "first name", but I also have one for last name, firm name, user id, status). I would like some help refactoring this so that I can use just one method and make the property to be dynamic or passed in.
private static IQueryable<MyModel> FilterFirstName(IQueryable<MyModel> query, string searchText, string searchFilter)
{
switch (searchFilter.ToLower())
{
case "contains":
query = query.Where(x => x.FirstName.ToLower().Contains(searchText.ToLower()));
break;
case "does not contain":
query = query.Where(x => !x.FirstName.ToLower().Contains(searchText.ToLower()));
break;
case "starts with":
query = query.Where(x => x.FirstName.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase));
break;
case "ends with":
query = query.Where(x => x.FirstName.EndsWith(searchText, StringComparison.InvariantCultureIgnoreCase));
break;
case "equals":
query = query.Where(x => x.FirstName.Equals(searchText, StringComparison.InvariantCultureIgnoreCase));
break;
}
return query;
}
What you can do is use a Compose
method that can compose one expression with another:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
Which uses the following method to replace all instances of one expression with another:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Now you can write:
private static IQueryable<MyModel> FilterFirstName(
IQueryable<MyModel> query,
Expression<Func<MyModel, string>> selector,
string searchText,
string searchFilter)
{
switch (searchFilter.ToLower())
{
case "contains":
query = query.Where(selector.Compose(
text => text.ToLower().Contains(searchText.ToLower())));
break;
case "does not contain":
query = query.Where(selector.Compose(
text => !text.ToLower().Contains(searchText.ToLower())));
break;
case "starts with":
query = query.Where(selector.Compose(
text => text.StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase)));
break;
case "ends with":
query = query.Where(selector.Compose(
text => text.EndsWith(searchText, StringComparison.InvariantCultureIgnoreCase)));
break;
case "equals":
query = query.Where(selector.Compose(
text => text.Equals(searchText, StringComparison.InvariantCultureIgnoreCase)));
break;
}
return query;
}
On a side note, you should really use an enum
to represent the different types of filters for searchFilter
, rather than a string. It will make it much easier on the caller, as they won't need to type out an exact string without having any good way of knowing what the exact options are, or if the provided option is valid.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With