首页 > 解决方案 > C# 使用动态 Linq 表达式使用 Startswith 过滤 int 类型的列并获取 Nullable`1 进行转换

问题描述

作为使用动态 linq 进行过滤的基础,我使用了这个答案,它适用于 String 类型的列,但是我还需要对整数类型的列执行startsWith/Contains - 在这种情况下,我使用了这个答案- 它与错误如下所示。

这是创建 IQueryable 结果的代码。

    public static IQueryable<T> Has<T>(this IQueryable<T> source, ExpressionFilter filter) {
    if (source == null || filter.PropertyName.IsNull() || filter.Value == null) {
        return source;
    }
    // Find the type incase we get an int column.
    Type propertyType = source.ElementType.GetProperty(filter.PropertyName).PropertyType;

    // For our Call.
    MethodCallExpression methodCallExpression = null;

    // If its a string we need to change it to lower case.
    MethodCallExpression toLowerExpression = null;

    // If its any one of the binary expressions.
    Expression binaryExpression = null;

    // ..and our predicate is initiated here as well.
    Expression<Func<T, bool>> predicate = null;

    // We need the parameter eg x => ..
    ParameterExpression parameter = Expression.Parameter(source.ElementType, "x");

    // Finally here is our Property expression eg x => LastName Last name being the property name
    Expression propertyExp = Expression.Property(parameter, filter.PropertyName);

    // our METHODINFO's
    var CONTAINS_METHOD = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    var STARTS_WITH_METHOD = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
    var ENDS_WITH_METHOD = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
    var BOOL_EQUAL_METHOD = typeof(bool).GetMethod("Equals", new Type[] { typeof(bool) });
    var TO_LOWER_METHOD = typeof(string).GetMethod("ToLower", new Type[] { });
    var TRIM_START = typeof(string).GetMethod("Trim", new Type[] { typeof(int) });
    var STRING_CONVERT = typeof(SqlFunctions).GetMethod("StringConvert", new[] { typeof(double?) }); ////get the SqlFunctions.StringConvert method for nullable double

    // We supply a type of object for our term to search on.. it needs to be a string.
    ConstantExpression termConstant = Expression.Constant(filter.Value.ToString(), typeof(string));

    // In case we get a propertyType of (int) we cant just perform a lower case on it.
    if (propertyType == typeof(string)) {
        toLowerExpression = Expression.Call(propertyExp, TO_LOWER_METHOD);
    }

    switch (filter.Comparison) {
        case Comparison.Contains:
            methodCallExpression = Expression.Call(toLowerExpression, CONTAINS_METHOD, termConstant);
            break;
        case Comparison.StartsWith:
            if (propertyType == typeof(int)) { // We'll do a startsWith on an int column

                //convert Expression to a nullable double (or nullable decimal),
                //so that you can use SqlFunctions.StringConvert
                propertyExp = Expression.Convert(propertyExp, typeof(double?));

                //call StringConvert on your converted expression
                propertyExp = Expression.Call(null, STRING_CONVERT, propertyExp);

                methodCallExpression = Expression.Call(propertyExp, STARTS_WITH_METHOD, termConstant);
            }
            else
                methodCallExpression = Expression.Call(toLowerExpression, STARTS_WITH_METHOD, termConstant); //WORKS HERE..

            break;
        case Comparison.EndsWith:
            methodCallExpression = Expression.Call(toLowerExpression, ENDS_WITH_METHOD, termConstant);
            break;
        case Comparison.BoolTest:
            bool parsedBoolValue;

            if (bool.TryParse(filter.Value.ToString().ToLower(), out parsedBoolValue)) { // Its a bool column.
                termConstant = Expression.Constant(parsedBoolValue, typeof(bool));
                methodCallExpression = Expression.Call(propertyExp, BOOL_EQUAL_METHOD, termConstant);
            }
            break;
        case Comparison.Equal:
            binaryExpression = Expression.Equal(propertyExp, termConstant);
            break;
        case Comparison.GreaterThan:
            binaryExpression = Expression.GreaterThan(propertyExp, termConstant);
            break;
        case Comparison.GreaterThanOrEqual:
            binaryExpression = Expression.GreaterThanOrEqual(propertyExp, termConstant);
            break;
        case Comparison.LessThan:
            binaryExpression = Expression.LessThan(propertyExp, termConstant);
            break;
        case Comparison.LessThanOrEqual:
            binaryExpression = Expression.LessThanOrEqual(propertyExp, termConstant);
            break;
        case Comparison.NotEqual:
            binaryExpression = Expression.NotEqual(propertyExp, termConstant);
            break;
        case Comparison.IndexOf:
            binaryExpression = Expression.NotEqual(
                Expression.Call(
                    propertyExp,
                    "IndexOf",
                    null,
                    Expression.Constant(filter.Value, typeof(string)),
                    Expression.Constant(StringComparison.InvariantCultureIgnoreCase, typeof(StringComparison))
                ),
                Expression.Constant(-1, typeof(int))
            );
            break;
        default:
            return null;
    }

    if (binaryExpression == null) {
        predicate = Expression.Lambda<Func<T, bool>>(methodCallExpression, parameter);
    }
    else {
        predicate = Expression.Lambda<Func<T, bool>>(binaryExpression, parameter);
    }

    methodCallExpression = Expression.Call(typeof(Queryable), "Where",
                                        new Type[] { source.ElementType },
                                        source.Expression, Expression.Quote(predicate));

    return source.Provider.CreateQuery<T>(methodCallExpression);
}

因此,它适用于字符串类型的列,但是当您尝试对 int 列进行过滤时,它会失败。

“由于对象的当前状态,操作无效。”

对于 int 列,它使用以下方式构建表达式:

            if (propertyType == typeof(int)) { // We'll do a startsWith on an int column

                //convert Expression to a nullable double (or nullable decimal),
                //so that you can use SqlFunctions.StringConvert
                propertyExp = Expression.Convert(propertyExp, typeof(double?));

                //call StringConvert on your converted expression
                propertyExp = Expression.Call(null, STRING_CONVERT, propertyExp);

                methodCallExpression = Expression.Call(propertyExp, STARTS_WITH_METHOD, termConstant);
            }

作为一个表达式,这会产生

{x => StringConvert(Convert(x.ClientNo, Nullable`1)).StartsWith("101")}

在此处输入图像描述

我在这里可能是错的,但为什么它会在可为空后加上一个反勾和一个“1”?这就是它倒下的原因吗?

为了完整性,它完成了“Has”方法(添加到表达式树),这是结果(失败): 在此处输入图像描述

有没有更好的方法来构造这个表达式以便它工作?

标签: c#linqdynamic-linq

解决方案


推荐阅读