与NHibernate一起使用的自定义查询扩展

问题描述

我正在尝试编写自定义的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)),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,string?>> expression)
    {
        var arg = expression.Body;
        var argTrim = Expression.Call(arg,Type.EmptyTypes));

        var exp = Expression.MakeBinary(ExpressionType.And,Expression.MakeBinary(ExpressionType.NotEqual,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())