Help refactoring the LINQ method

I need help, I have a method that repeats 6 times in my class, and the only thing that changes in each of the methods is the LINQ property (in the example β€œname”, but I also have a name for the surname, company name, user ID status). I would like some to help refactor this so that I can use only one method and make the property dynamic or passed.

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; } 
+5
source share
4 answers

What you can do is use the Compose method, which 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); } 

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 the side of the note, you really should use enum to represent different types of filters for searchFilter , not for a string. This will make it much easier for the caller, as they will not need to enter the exact line without any good way to find out what the exact parameters are, or if the provided parameter is valid.

+2
source

This version allows you to pass a property to a filter, for example:

 Filter(models, (MyModel m) => m.FirstName, "Joe", "contains"); Filter(models, (MyModel m) => m.LastName, "Smith", "contains"); private static IQueryable<MyModel> Filter(IQueryable<MyModel> query, Func<MyModel, string> property, string searchText, string searchFilter) { switch (searchFilter.ToLower()) { case "contains": query = query.Where(x => property(x).ToLower().Contains(searchText.ToLower())); break; case "does not contain": query = query.Where(x => !property(x).ToLower().Contains(searchText.ToLower())); break; case "starts with": query = query.Where(x => property(x).StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase)); break; case "ends with": query = query.Where(x => property(x).EndsWith(searchText, StringComparison.InvariantCultureIgnoreCase)); break; case "equals": query = query.Where(x => property(x).Equals(searchText, StringComparison.InvariantCultureIgnoreCase)); break; } return query; } 
-1
source

I would do one or more of the following:

  • Reflection and caching of the property search method (the "x.FirstName" bit can be easily processed using reflection

  • Take the selector function and apply it to get the target

  • Make it an extension method

the code:

 public static class FilterPerson { static IQueryable<Person> FilterPerson( this IQueryable<Person> query, FilterString filter, Func<Person, string> selector, string searchText) { var enumerableQuery = query.AsEnumerable(); switch (filter) { case FilterString.Contains: enumerableQuery = enumerableQuery.Where(x => selector(x).ToLowerInvariant().Contains(searchText.ToLowerInvariant())); break; case FilterString.DoesNotContain: enumerableQuery = enumerableQuery.Where(x => !selector(x).ToLower().Contains(searchText.ToLower())); break; case FilterString.StartsWith: enumerableQuery = enumerableQuery.Where(x => selector(x).StartsWith(searchText, StringComparison.InvariantCultureIgnoreCase)); break; case FilterString.EndsWith: enumerableQuery = enumerableQuery.Where(x => selector(x).EndsWith(searchText, StringComparison.InvariantCultureIgnoreCase)); break; case FilterString.Equals: enumerableQuery = enumerableQuery.Where(x => selector(x).Equals(searchText, StringComparison.InvariantCultureIgnoreCase)); break; } return enumerableQuery.AsQueryable(); } } public class Person { public string FirstName { get; set; } public string LastName { get; set; } public string PhoneNumber { get; set; } } public enum FilterString { StartsWith, Contains, DoesNotContain, EndsWith, Equals } 
-1
source

If it called 6 times in your code, and there are 5 ways in it, I would say that the method itself introduces an unnecessary connection and should be deleted.

-5
source

Source: https://habr.com/ru/post/1213086/


All Articles