我正在尝试从用户提供的一组收集条件中,在Entity Framework中构建动态LINQ查询。最终,这将包括更复杂的行为,但是目前,我仅具有字段名和值的列表,并且我想返回其中字段名具有这些值的所有记录。

我的基本结构是这样的:

public IEnumerable<ThingViewModel> getMythings(SelectionCriteria selectionCriteria)
{
    var predicate = constructPredicate<Thing>(selectionCriteria);
    var things = this.dbContext.Things.Where(predicate).ToList();
    return Mapper.Map<List<Thing>, List<ThingViewModel>>(things);
}

所有有趣的工作都在constructPredicate()中:
private static Expression<Func<T, bool>> constructPredicate<T>(SelectionCriteria selectionCriteria)
{
    // using Pete Montgomery's PredicateBuilder:
    // http://petemontgomery.wordpress.com/2011/02/10/a-universal-predicatebuilder/

    var predicate = PredicateBuilder.True<T>();

    foreach (var item in selectionCriteria.andList)
    {
        // Accessing foreach values in closures can result in unexpected results.
        // http://stackoverflow.com/questions/14907987/access-to-foreach-variable-in-closure
        var fieldName = item.fieldName;
        var fieldValue = item.fieldValue;

        var parameter = Expression.Parameter(typeof (T), "t");
        var property = Expression.Property(parameter, fieldName);
        var value = Expression.Constant(fieldValue);

        var lambda = buildCompareLambda<T>(property, value, parameter);

        predicate = predicate.And(lambda);
    }

    return predicate;
}

所有这些似乎都是一个非常合理的结构,但是在buildCompareLambda()中却遇到了困难。我没有找到一种通用的方法,我必须为不同的类型创建不同的方法。我从处理字符串开始,这很简单。接下来,我尝试处理整数,但事实证明,数据库中的整数字段是可为空的,这引入了全新的复杂性类。

到目前为止,我的buildCompareLambda():
private static Expression<Func<T, bool>> buildCompareLambda<T>(
    MemberExpression property,
    ConstantExpression value,
    ParameterExpression parameter)
{
    Expression<Func<T, bool>> lambda = null;
    if (property.Type == typeof (string))
        lambda = buildStringCompareLambda<T>(property, value, parameter);
    else if (property.Type.IsGenericType && Nullable.GetUnderlyingType(property.Type) != null)
        lambda = buildNullableCompareLambda<T>(property, value, parameter);

    if (lambda == null)
        throw new Exception(String.Format("SelectrionCriteria cannot handle property type '{0}'", property.Type.Name));

    return lambda;
}

如我所说,buildStringCompareLambda很简单:
private static Expression<Func<T, bool>> buildStringCompareLambda<T>(
    MemberExpression property,
    ConstantExpression value,
    ParameterExpression parameter)
{
    var equalsMethod = typeof (string).GetMethod("Equals",
            new[] {typeof (string), typeof (string)});
    var comparison = Expression.Call(equalsMethod, property, value);
    return Expression.Lambda<Func<T, bool>>(comparison, parameter);
}

但是buildNullableCompareLambda()越来越难看:
private static Expression<Func<T, bool>> buildNullableCompareLambda<T>(MemberExpression property,
    ConstantExpression value,
    ParameterExpression parameter)
{
    var underlyingType = Nullable.GetUnderlyingType(property.Type);

    if (underlyingType == typeof (int) || underlyingType == typeof (Int16) || underlyingType == typeof (Int32) ||
        underlyingType == typeof (Int64) || underlyingType == typeof (UInt16) || underlyingType == typeof (UInt32) ||
        underlyingType == typeof (UInt64))
    {
        var equalsMethod = underlyingType.GetMethod("Equals", new[] {underlyingType});

        var left = Expression.Convert(property, underlyingType);
        var right = Expression.Convert(value, underlyingType);

        var comparison = Expression.Call(left, equalsMethod, new Expression[] {right});

        return Expression.Lambda<Func<T, bool>>(comparison, parameter);
    }

    return null;
}

我的目的是在buildNullableCompareLambda()中添加对更多类型的支持,并将每种类型的处理移入一个函数,以便可以从buildCompareLambda()和buildNullableCompareLambda()调用相同的代码。但这是为了将来-目前,我只能比较int。目前,我正在将属性和值都转换为基础类型,因为我不想为每个整数类型都使用单独的函数,并且我不想用户不必担心EF是否为字段建模转换为Int16或Int32。对于非空字段,这是有效的。

我一直在浏览SO,并找到了一些答案,这就是我所获得的一切,但是我在处理可空类型上看到的所有答案都对我没有用,因为它们实际上并没有处理空值。

在我的情况下,如果用户向我传递了一个选择条件,并且该选择条件的项目应该等于null,那么我想返回该字段为null的记录,而关于将双方都转换为基本类型的这一点都不会似乎有效。我收到“对象引用未设置为对象实例”的异常。

在SQL中,我想要的是“WHERE字段为NULL”(如果值是null),或者“WHERE字段='value'”(如果不是)。而且我没有看到如何在表达式树中构建这种替代方案。

有任何想法吗?

补充:建议我使用Expression.Equal()。

这样,我的循环变为:
private static Expression<Func<T, bool>> constructPredicate<T>(SelectionCriteria selectionCriteria)
{
    var predicate = PredicateBuilderEx.True<T>();
    var foo = PredicateBuilder.True<T>();

    foreach (var item in selectionCriteria.andList)
    {
        var fieldName = item.fieldName;
        var fieldValue = item.fieldValue;

        var parameter = Expression.Parameter(typeof (T), "t");
        var property = Expression.Property(parameter, fieldName);
        var value = Expression.Constant(fieldValue);

        var comparison = Expression.Equal(property, value);
        var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);

        predicate = PredicateBuilderEx.And(predicate, lambda);
    }
    return predicate;
}

那是行不通的。我有一个异常(exception):

最佳答案

在很多情况下,这里的人们可能不太想出答案,但他们会采用大多数方法,并且要足够接近,我才能解决其余的问题。

Expression.Equal要求两个参数都属于同一类型。如果一个可为空,则它们两者都必须为可为空。但这并不难处理:

private static Expression<Func<T, bool>> constructPredicate<T>(SelectionCriteria selectionCriteria)
{
    var predicate = PredicateBuilderEx.True<T>();
    var foo = PredicateBuilder.True<T>();

    foreach (var item in selectionCriteria.andList)
    {
        var fieldName = item.fieldName;
        var fieldValue = item.fieldValue;

        var parameter = Expression.Parameter(typeof (T), "t");
        var property = Expression.Property(parameter, fieldName);
        var value = Expression.Constant(fieldValue);
        var converted = Expression.Convert(value, property.Type);

        var comparison = Expression.Equal(property, converted);
        var lambda = Expression.Lambda<Func<T, bool>>(comparison, parameter);

        predicate = PredicateBuilderEx.And(predicate, lambda);
    }

    return predicate;
}

谢谢大家

10-04 18:39