I am trying to write a custom Linq extension, which can also be performed within the DB.
Basically, what I want to do is collectionOfStrings.Where(x => !x.IsNullOrWhiteSpace)
Unfortunately it's not supported.
=============== What I've tried so far =============
This part is only interesting to those, who might come up with another idea apart from the one below.
There is a workaround by going this way collection.Where(x => x != null && x.Trim() != string.Empty)
, but since I use it frequently, it's not the best solution.
The prettiest solution would be, to find a way to write a string extension IsNullOrWhiteSpaceDB
, which works or to kind of add the IsNullOrWhiteSpace
method to the database programmatically to ensure support.
That's been my attempt to create a working IsNullOrWhiteSpace
method, but it's not supported too:
public static bool IsNullOrWhiteSpaceDB(this string? str) =>
str == null || str.Trim() == String.Empty;
So I've started writing a predicate, which is working fine:
public IQueryable<string> GetAll() =>
GetAll().Select(x => x.property).Where(StringIsNotNullOrWhiteSpace).Distinct();
private static Expression<Func<string?, bool>> StringIsNotNullOrWhiteSpace =>
x => x != null && x.Trim() != string.Empty;
=============== The current problem =============
Neverthless I'd actually like to be able to run it on another collection than on a collection of strings. So I tried to build a custom linq extension (inspired by this solution (https://stackoverflow.com/a/40924558/9487478)):
public class QueryVisitor : ExpressionVisitor
{
protected override Expression VisitMethodCall(MethodCallExpression node)
{
if (node.Method.IsStatic && node.Method.Name == "IsNullOrWhiteSpace")
{
//!(b.Diameter == null || b.Diameter.Trim() == string.Empty)
var arg = node.Arguments[0];
var argTrim = Expression.Call(arg, typeof(string).GetMethod("Trim", Type.EmptyTypes));
var exp = Expression.MakeBinary(ExpressionType.Or,
Expression.MakeBinary(ExpressionType.Equal, arg, Expression.Constant(null, arg.Type)),
Expression.MakeBinary(ExpressionType.Equal, argTrim, Expression.Constant(string.Empty, arg.Type))
);
return exp;
}
return base.VisitMethodCall(node);
}
}
public static class EfQueryableExtensions
{
public static IQueryable<T> WhereEx<T>(this IQueryable<T> queryable, Expression<Func<T, bool>> where)
{
var visitor = new QueryVisitor();
return queryable.Where(visitor.VisitAndConvert(where, "WhereEx"));
}
}
And that's what my custom extension actually looks like:
public static class QueryHelper
{
public static IQueryable<T> WhereIsNotNullOrWhiteSpace<T>(this IQueryable<T> query, Expression<Func<T, string?>> expression)
{
var arg = expression.Body;
var argTrim = Expression.Call(arg, typeof(string).GetMethod("Trim", Type.EmptyTypes));
var exp = Expression.MakeBinary(ExpressionType.And,
Expression.MakeBinary(ExpressionType.NotEqual, arg, Expression.Constant(null, arg.Type)),
Expression.MakeBinary(ExpressionType.NotEqual, argTrim, Expression.Constant(string.Empty, arg.Type))
);
var lambda = Expression.Lambda<Func<T, bool>>(exp, expression.Parameters);
var result = query.Where(lambda);
return result;
}
}
After the query.Where(lambda)
is executed there is an inner exception within the result:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException: A recognition error occurred.
The "original version" throws the same error too, so I thought it might be the created expression ((x == null) Or (x.Trim() == ""))
(copied from the debugger). For me it looks actually quite good and I don't understand the cause of the error.
Any ideas? I would be pleased!