I have a generic method which dynamically creates a query in Entity Framework. I use this as a search function on data table headers.
The function works perfectly if the Entity property type/SQL data type is a string. This is because of the .Contains() extensions.
The problem comes in when the data type is something other than a string. These data types don't have the .Contains() extension.
I would like to be able to use this method across all data types, and have found that I could possibly use SqlFunctions.StringConvert. I also know that it has no option for integer, and will have to convert the integer based properties into double.
I am unsure how to implement SqlFunctions.StringConvert generically, please see my below method (you will see that I have excluded the data types which have no .Contains() extension):
public static IQueryable<T> Filter<T>(this IQueryable<T> query, List<SearchFilterDto> filters)
where T : BaseEntity
{
if (filters != null && filters.Count > 0 && !filters.Any(f => string.IsNullOrEmpty(f.Filter)))
{
Expression filterExpression = null;
ParameterExpression parameter = Expression.Parameter(query.ElementType, "item");
filterExpression = filters.Select(f =>
{
Expression selector = parameter;
Expression pred = Expression.Constant(f.Filter);
foreach (var member in f.Column.Split('.'))
{
PropertyInfo mi = selector.Type.GetProperty(member, BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.Instance);
if (mi != null)
{
selector = Expression.Property(selector, mi);
if (selector.Type == typeof(Guid) ||
selector.Type == typeof(Guid?) ||
selector.Type == typeof(DateTime) ||
selector.Type == typeof(DateTime?) ||
selector.Type == typeof(int) ||
selector.Type == typeof(int?)
)
{
return null;
}
}
else
{
return null;
}
}
Expression containsMethod = Expression.Call(selector, "Contains", null, pred);
return containsMethod;
}).Where(r => r != null).Aggregate(Expression.And);
LambdaExpression where = Expression.Lambda(filterExpression, parameter);
MethodInfo whereCall = (typeof(Queryable).GetMethods().First(mi => mi.Name == "Where" && mi.GetParameters().Length == 2).MakeGenericMethod(query.ElementType));
MethodCallExpression call = Expression.Call(whereCall, new Expression[] { query.Expression, where });
return query.Provider.CreateQuery<T>(call);
}
return query;
}
I would like to mention that the
Contains
in this case is not an extension, but a regularstring.Contains
method.This is not a good idea since the non string values can have different string representations, so it's not quite clear what you'll be searching for.
But let assume you want it anyway.
There are two drawbacks - first,
SqlFunctions
are SqlServer specific (not likeDbFunctions
for instance), and second,StringConvert
works only withdouble
anddecimal
. IMO the better choice would be using theobject.ToString
method which is supported in EF (at least in the lastest EF6).I'm going to provide you a solution based on
object.ToString()
. But before doing that, let me give you some hints when working with expressions. Anytime you want to build an expression usingSystem.Linq.Expressions
and don't know how, you can build a similar sample typed expression and examine it inside the debugger Locals/Watch window. For instance:You can put a break point and start expanding
e
members, then their members etc. and you'll see how the expression has been built by the compiler, then all you need is to find the respectiveExpression
methods.Finally, here is the solution itself. I've also included some little tricks that allow avoiding working directly with reflection and string method names where possible: