EDIT: I apologize from my earlier sample that didn't quite compile. I've fixed it and added a more complete example.
You could associate each condition with a strategy for changing the query. Each strategy (called SearchFieldMutator
in this example) will hold two things:
- A way to decide if to apply the strategy.
- The strategy itself.
The first part is a delegate (of type Predicate<TSearch>
) that returns true
or false
based on the data in the UserSearchViewModel
(or any other type, since it only defines a generic type TSearch
). If it return true
, the strategy is applied. If it returns false
, it's not applied. This is the type of the delegate:
Predicate<TSearch>
(it could also have been written as Func<TSearch, bool>
)
The second part is the strategy itself. It is supposed to 'mutate' the query by applying a LINQ operator to it, but really just returns a new query with the added operator, and the caller of it should discard the old query and keep the new one. So it's not really mutation, but it has the same effect. I've created a new delegate type for it, so its usage is clear:
public delegate IQueryable<TItem> QueryMutator<TItem, TSearch>(IQueryable<TItem> items, TSearch search);
NOTE: I've defined both the item type and the search data as generic types (as TItem
and TSearch
, respectively) so this code is usable in multiple locations in your code. But if this is confusing, you can remove the generics completely, and replace any TItem
with UserListItem
and any TSearch
with UserSearchViewModel
.
Now that we have defined the two types of the strategy, we can create a class that holds them both, and also does the (simulated) mutation:
public class SearchFieldMutator<TItem, TSearch> { public Predicate<TSearch> Condition { get; set; } public QueryMutator<TItem, TSearch> Mutator { get; set; } public SearchFieldMutator(Predicate<TSearch> condition, QueryMutator<TItem, TSearch> mutator) { Condition = condition; Mutator = mutator; } public IQueryable<TItem> Apply(TSearch search, IQueryable<TItem> query) { return Condition(search) ? Mutator(query, search) : query; } }
This class holds both the condition and the strategy itself, and by using the Apply()
method, we can easily apply it to a query if the condition is met.
We can now go create out list of strategies. We'll define some place to hold them (on one of your classes), since they only have to be created once (they are stateless after all):
List<SearchFieldMutator<UserListItem, UserSearchViewModel>> SearchFieldMutators { get; set; }
We'll then populate the list:
SearchFieldMutators = new List<SearchFieldMutator<UserListItem, UserSearchViewModel>> { new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => !string.IsNullOrWhiteSpace(search.Name), (users, search) => users.Where(u => u.FirstName.Contains(search.Name) || u.LastName.Contains(search.Name))), new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => !string.IsNullOrWhiteSpace(search.Email), (users, search) => users.Where(u => u.Email.Contains(search.Email))), new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => search.UsertypeId.HasValue, (users, search) => users.Where(u => u.UsertypeId == search.UsertypeId.Value)), new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => search.CurrentSort.ToLower() == "namedesc", (users, search) => users.OrderByDescending(u => u.FirstName).ThenByDescending(u => u.LastName)), new SearchFieldMutator<UserListItem, UserSearchViewModel>(search => search.CurrentSort.ToLower() == "emailasc", (users, search) => users.OrderBy(u => u.Email)), // etc... };
We can then try to run it on a query. Instead of an actual Entity Framework query, I'm going to use a simply array of UserListItem
s and add a .ToQueryable()
on to it. It will work the same if you replace it with an actual database query. I'm also going to create a simple search, for the sake of the example:
// This is a mock EF query. var usersQuery = new[] { new UserListItem { FirstName = "Allon", LastName = "Guralnek", Email = null, UsertypeId = 7 }, new UserListItem { FirstName = "Kristof", LastName = "Claes", Email = "[email protected]", UsertypeId = null }, new UserListItem { FirstName = "Tugboat", LastName = "Captain", Email = "[email protected]", UsertypeId = 12 }, new UserListItem { FirstName = "kiev", LastName = null, Email = null, UsertypeId = 7 }, }.AsQueryable(); var searchModel = new UserSearchViewModel { UsertypeId = 7, CurrentSort = "NameDESC" };
The following actually does all the work, it changes query inside the usersQuery
variable to the one specified by all the search strategies:
foreach (var searchFieldMutator in SearchFieldMutators) usersQuery = searchFieldMutator.Apply(searchModel, usersQuery);
That's it! This is the result of the query:

You can try running it yourself. Here's a LINQPad query for you to play around with:
http://share.linqpad.net/7bud7o.linq