6

我正在使用表达式树构建通用 linq 查询。在子集合上创建表达式时我被卡住了。由于类型不兼容,方法调用崩溃了。通常我知道该放什么,但是 Any() 方法调用让我感到困惑。我已经尝试了我能想到的所有类型,但没有运气。任何帮助,将不胜感激。

这是我的实体类:

public class Story : Entity
{
    public string Author { get; set; }

    public IList<string> Contributors { get; set; }
}

我要为其生成表达式树的查询:

var stories = new List<Story>();
stories.Where(p => p.Author.Contains("Test") || p.Contributors.Any(c => c.Contains("Test")));

到目前为止我得到了什么

public interface IFilterCriteria
{
    string PropertyToCompare { get; set; }
    object ValueToCompare { get; set; }
    FilterOperator FilterOperator { get; set; }
    bool IsList { get; set; }
    Expression Expression { get; set; }
}

public static IQueryable<T> Filter<T>(this IQueryable<T> query, IList<IFilterCriteria> filterCriterias, LogicalOperator logicalOperator = LogicalOperator.And)
{
    if (filterCriterias != null && filterCriterias.Any())
    {
        var resultCondition = filterCriterias.ToExpression(query, logicalOperator);

        var parameter = Expression.Parameter(query.ElementType, "p");

        if (resultCondition != null)
        {
            var lambda = Expression.Lambda(resultCondition, parameter);

            var mce = Expression.Call(
                typeof(Queryable), "Where",
                new[] { query.ElementType },
                query.Expression,
                lambda);

            return query.Provider.CreateQuery<T>(mce);
        }
    }
    return query;
}

public static Expression ToExpression<T>(this IList<IFilterCriteria> filterCriterias, IQueryable<T> query, LogicalOperator logicalOperator = LogicalOperator.And)
{
    Expression resultCondition = null;
    if (filterCriterias.Any())
    {
        var parameter = Expression.Parameter(query.ElementType, "p");

        foreach (var filterCriteria in filterCriterias)
        {
            var propertyExpression = filterCriteria.PropertyToCompare.Split('.').Aggregate<string, MemberExpression>(null, (current, property) => Expression.Property(current ?? (parameter as Expression), property));

            Expression valueExpression;
            var constantExpression = Expression.Constant(filterCriteria.ValueToCompare);

            if (!filterCriteria.IsList)
            {
                valueExpression = Expression.Convert(constantExpression, propertyExpression.Type);
            }
            else
            {
                valueExpression = Expression.Call(typeof (Enumerable), "Any", new[] {typeof (string)},
                                                  propertyExpression, filterCriteria.Expression,
                                                  Expression.Constant(filterCriteria.ValueToCompare,
                                                                      typeof (string)));
            }

            Expression condition;
            switch (filterCriteria.FilterOperator)
            {
                case FilterOperator.IsEqualTo:
                    condition = Expression.Equal(propertyExpression, valueExpression);
                    break;
                case FilterOperator.IsNotEqualTo:
                    condition = Expression.NotEqual(propertyExpression, valueExpression);
                    break;
                case FilterOperator.IsGreaterThan:
                    condition = Expression.GreaterThan(propertyExpression, valueExpression);
                    break;
                case FilterOperator.IsGreaterThanOrEqualTo:
                    condition = Expression.GreaterThanOrEqual(propertyExpression, valueExpression);
                    break;
                case FilterOperator.IsLessThan:
                    condition = Expression.LessThan(propertyExpression, valueExpression);
                    break;
                case FilterOperator.IsLessThanOrEqualTo:
                    condition = Expression.LessThanOrEqual(propertyExpression, valueExpression);
                    break;
                case FilterOperator.Contains:
                    condition = Expression.Call(propertyExpression, typeof(string).GetMethod("Contains", new[] { typeof(string) }), valueExpression);
                    break;
                case FilterOperator.StartsWith:
                    condition = Expression.Call(propertyExpression, typeof(string).GetMethod("StartsWith", new[] { typeof(string) }), valueExpression);
                    break;
                case FilterOperator.EndsWith:
                    condition = Expression.Call(propertyExpression, typeof(string).GetMethod("EndsWith", new[] { typeof(string) }), valueExpression);
                    break;
                default:
                    condition = valueExpression;
                    break;
            }

            if (resultCondition != null)
            {
                switch (logicalOperator)
                {
                    case LogicalOperator.And:
                        resultCondition = Expression.AndAlso(resultCondition, condition);
                        break;
                    case LogicalOperator.Or:
                        resultCondition = Expression.OrElse(resultCondition, condition);
                        break;
                }
            }
            else
            {
                resultCondition = condition;
            }
        }
    }
    return resultCondition;
}

这就是我使用表达式的方式:

var stories = new List<Story>();
var filters = new List<FilterCriteria>();
filter.Add(new FilterCriteria { ValueToCompare = "Test", PropertyToCompare = "Author", FilterOperator = FilterOperator.Contains });

Expression<Func<string, bool>> func  = t => t.Contains("Test");

filter.Add(new FilterCriteria { ValueToCompare = "Test", PropertyToCompare = "Contributors", FilterOperator = FilterOperator.Contains, Expression = func });

stories.Filter(filters, LogicalOperator.Or).ToList();

但是在运行这段代码之后,我得到了这个我无法解决的错误

类型 'System.Linq.Queryable' 上没有通用方法 'Any' 与提供的类型参数和参数兼容。如果方法是非泛型的,则不应提供类型参数。说明:执行当前 Web 请求期间发生未处理的异常。请查看堆栈跟踪以获取有关错误及其源自代码的位置的更多信息。

异常详细信息:System.InvalidOperationException:类型“System.Linq.Queryable”上没有通用方法“Any”与提供的类型参数和参数兼容。如果方法是非泛型的,则不应提供类型参数。

源错误:

第 184 行:{ 第 185 行:
var 重载 = typeof(Queryable).GetMethods().Single(mi => mi.Name == "Any" && mi.GetParameters().Count() == 2); 第 186 行:
Expression.Call(typeof(Queryable), "Any", new[] { typeof(string) }, propertyExpression, or); 第 187 行:
valueExpression = Expression.Call(typeof(Enumerable), "Any", new[] { typeof(string)}, propertyExpression, or, Expression.Constant("Test",

4

1 回答 1

5

You don't call Any method anywhere in your code.

You should extend Contains, example:

case FilterOperator.Contains:
    // if collection
    if (propertyExpression.Type.IsGenericType &&
        typeof(IEnumerable<>)
            .MakeGenericType(propertyExpression.Type.GetGenericArguments())
            .IsAssignableFrom(propertyExpression.Type))
    {
        // find AsQueryable method
        var toQueryable = typeof(Queryable).GetMethods()
            .Where(m => m.Name == "AsQueryable")
            .Single(m => m.IsGenericMethod)
            .MakeGenericMethod(typeof(string));

        // find Any method
        var method = typeof(Queryable).GetMethods()
            .Where(m => m.Name == "Any")
            .Single(m => m.GetParameters().Length == 2)
            .MakeGenericMethod(typeof(string));

        // make expression
        condition = Expression.Call(
            null, 
            method,
            Expression.Call(null, toQueryable, propertyExpression), 
            filterCriteria.Expression
        );
    }
    else
    {
        condition = Expression.Call(propertyExpression, typeof(string).GetMethod("Contains", new[] { typeof(string) }), valueExpression);
    }
    break;

Also you should have created one p parameter (Expression.Parameter(query.ElementType, "p")) otherwise you'll get variable 'p' of type 'WpfApplication2.Story' referenced from scope '', but it is not defined error.

You may pass parameter from Filter method to ToExpression method.

于 2013-11-03T11:48:28.657 回答