我正在尝试编写一个自定义的 Linq 扩展,它也可以在数据库中执行。
基本上,我想做的是
collectionOfStrings.Where(x => !x.IsNullOrWhiteSpace)
不幸的是不支持。
================ 到目前为止我已经尝试过的==============
这部分只对那些除了下面的想法之外可能想出另一种想法的人感兴趣。
通过这种方式有一个解决方法
collection.Where(x => x != null && x.Trim() != string.Empty)
,但由于我经常使用它,所以这不是最好的解决方案。
最漂亮的解决方案是找到一种方法来编写字符串扩展
IsNullOrWhiteSpaceDB
,该方法可以工作,或者以编程方式将 IsNullOrWhiteSpace
方法添加到数据库中以确保支持。
这是我尝试创建一个有效的
IsNullOrWhiteSpace
方法,但它也不支持:
public static bool IsNullOrWhiteSpaceDB(this string? str) =>
str == null || str.Trim() == String.Empty;
所以我开始编写一个谓词,它工作得很好:
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;
================ 目前的问题============
尽管如此,我实际上希望能够在另一个集合上运行它而不是在字符串集合上。所以我尝试构建一个自定义的 linq 扩展(受到此解决方案的启发(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"));
}
}
这就是我的自定义扩展实际上的样子:
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;
}
}
执行
query.Where(lambda)
后,结果中存在内部异常:
NHibernate.Hql.Ast.ANTLR.QuerySyntaxException: A recognition error occurred.
“原始版本”也会引发相同的错误,所以我认为这可能是创建的表达式
((x == null) Or (x.Trim() == ""))
(从调试器复制)。对我来说,它看起来实际上相当不错,但我不明白错误的原因。
有什么想法吗?我会很高兴!
您不需要为此构建表达式。 您只需要一个接受并返回
IQueryable<string>
的扩展方法。
public static class Extensions
{
public static IQueryable<string> IsNullOrWhiteSpaceDB(this IQueryable<string> input)
{
return input.Where(x => x != null && x.Trim() != string.Empty);
}
}
我为 LINQKit 创建了 PR,它应该可以简化您的生活 https://github.com/scottksmith95/LINQKit/pull/127 想法是向此类方法添加
ExpandableAttribute
,该方法指向具有替换表达式的静态函数。
public static class Extensions
{
[Expandable(nameof(IsNotNullOrWhiteSpaceDBImpl))]
public static bool IsNotNullOrWhiteSpaceDB(string str)
=> throw new NotImplementedException();
public static Expression<Func<string, bool>> IsNotNullOrWhiteSpaceDBImpl()
=> x => x != null && x.Trim() != string.Empty;
}
所以查询应该至少使用一次
AsExpandable()
。将此调用放在存储库中的某个位置。
db.Users.AsExpandable()
.Where(u => u.FirstName.IsNotNullOrWhiteSpaceDB() || u.MiddleName.IsNotNullOrWhiteSpaceDB())