There is a way to prepare dynamic queries and conditions, and also to use functions to build parts of them. The syntax is also readable, which would do for the "simple" part of the question. It's possible through combining Linq expressions. There are several articles on how this can be done, but I think I came up with a new approach. At least I didn't find it on web.
To proceed you need a library of 3 simple functions. They use System.Linq.Expressions.ExpressionVisitor
to dynamically modify expressions. The key feature is unifying parameters inside the expression, so that 2 parameters with the same name were made identical (UnifyParametersByName
). The remaining part is replacing a named parameter with given expression (ReplacePar
) and a helper method (NewExpr
). The library is available with MIT license on github: LinqExprHelper, but you may quickly write something on your own.
First you define some methods, that may later be used in creating dynamic queries.
public class Store
{
...
public static Expression<Func<Store, bool>>
SafeSearchName(string sWhat)
{
return LinqExprHelper.NewExpr(
(Store s) => s.Name != null && s.Name.ToLower().Contains(sWhat)
);
}
public static Expression<Func<Store, bool>>
SafeSearchDesc(string sWhat)
{
return LinqExprHelper.NewExpr(
(Store s) => s.Description != null && s.Description.ToLower().Contains(sWhat)
);
}
}
Then you query in this way:
// Define a master condition, using named parameters.
var masterExpr = LinqExprHelper.NewExpr(
(Store s, bool bSearchName, bool bSearchDesc)
=> (bSearchName && bSearchDesc));
// Replace stub parameters with some real conditions.
var combExpr = masterExpr
.ReplacePar("bSearchName", Store.SafeSearchName("b").Body)
.ReplacePar("bSearchDesc", Store.SafeSearchDesc("p").Body);
// Sometimes you may skip a condition using this syntax:
//.ReplacePar("bSearchDesc", Expression.Constant(true));
// It's interesting to see how the final expression looks like.
Console.WriteLine("expr: " + combExpr);
// Execute the query using combined expression.
db.Stores
.Where((Expression<Func<Store, bool>>)combExpr)
.ToList().ForEach(i => { Console.WriteLine(i.Name + ", " + i.Description); });
I didn't use this in production yet, but some simple tests are passed. I don't see any limits in combining queries this way. If we need more parameters we can append additional level of combining. The advantage of this method is that you can use inline lambda expressions, which are nice to read, together with dynamic expression creation and composition, which is very capable.
Is it "simple" after all? If you consider method syntax of Linq as simple, then this is nearly that simple. It doesn't allow you to create custom Linq functions, but gives you comparable capabilities.