如何向表达式中添加内容

62

根据我昨天在StackOverflow上的问题

如果我需要追加到现有的'where'表达式中,该如何追加?

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    clientWhere = c => c.ClientFName == searchForClientFName;
}

 if (filterByClientLName)
    {
        clientWhere = c => c.ClientLName == searchForClientLName;
    }
用户可以输入名字、姓氏或者两者都输入。如果他们都输入了,我想要将它们添加到表达式中。我想知道是否有一个相当于追加的方法,可以实现这个功能。

用户可以输入名字、姓氏或者两者都输入。如果他们都输入了,我想要将它们添加到表达式中。我想知道是否有一个相当于追加的方法,可以实现这个功能。

clientWhere.Append or clientWhere += add new expression

或类似的东西


我一直在尝试寻找类似的解决方案,因为我们使用由我们团队开发的旧ORML工具,该工具支持通过“AND”或“OR”进行附加操作,并且我们的代码严重依赖于这样的where扩展。到目前为止,我们无法切换到linq,但基本上linq创建IExpression<T>,如果您找到绕过自己创建IExpression<T>树的方法,那会很有帮助。 - Akash Kava
8个回答

75

我认为你只需要按照以下步骤进行:

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    var prefix = clientWhere.Compile();
    clientWhere = c => prefix(c) && c.ClientFName == searchForClientFName;
}
if (filterByClientLName)
{
    var prefix = clientWhere.Compile();
    clientWhere = c => prefix(c) && c.ClientLName == searchForClientLName;
}

如果你需要在IQueryable中使用,同时保持所有内容都在Expression领域内,你可以采取以下方法:

Expression<Func<Client, bool>> clientWhere = c => true;

if (filterByClientFName)
{
    Expression<Func<Client, bool>> newPred = 
        c => c.ClientFName == searchForClientFName;
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
        Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters);
}
if (filterByClientLName)
{
    Expression<Func<Client, bool>> newPred = 
        c => c.ClientLName == searchForClientLName;
    clientWhere = Expression.Lambda<Func<Freight, bool>>(
        Expression.AndAlso(clientWhere, newPred), clientWhere.Parameters);
}

可以通过定义这个扩展方法来使其更简洁:

public static Expression<TDelegate> AndAlso<TDelegate>(this Expression<TDelegate> left, Expression<TDelegate> right)
{
    return Expression.Lambda<TDelegate>(Expression.AndAlso(left, right), left.Parameters);
}

你可以使用如下语法:

Expression<Func<Client, bool>> clientWhere = c => true;
if (filterByClientFName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientFName == searchForClientFName);
}
if (filterByClientLName)
{
    clientWhere = clientWhere.AndAlso(c => c.ClientLName == searchForClientLName);
}

7
Jason: 在大家提供的所有信息的基础上学习。我尝试了你的方法,通过在静态类中定义扩展方法,但是出现了错误: 二进制运算符AndAlso对类型'System.Func2[Models.Client,System.Boolean]'和'System.Func2[Models.Client,System.Boolean]'未定义。 - ltech
5
即使你修复了扩展方法中的错误,这仍然是一种非常脆弱的方式进行操作。有关详细信息,请参见我的答案:https://dev59.com/mXE95IYBdhLWcg3wn_Rr - Eric Lippert
3
我认为有太多用户使用“Jason”作为他们的显示名称。 - jason
Where does AndAlso come from? - jjxtra

14

这是一个复杂的场景。你几乎要在LINQ之上构建自己的查询引擎。如果你想在所有条件之间实现逻辑AND,JaredPar的解决方案(它去哪了?)非常好,但这并不总是情况。

最近我在其中一个项目中处理这个问题时,创建了两个列表:

List<Predicate<T>> andCriteria;
List<Predicate<T>> orCriteria;

在这种情况下,T指的是客户端(也就是你)。
我会使用我想要成立的谓词来填充列表。例如,
decimal salRequirement = 50000.00;
andCriteria.Add(c => c.Salary > salRequirement);
orCriteria.Add(c => c.IsMarried);

接下来,我会检查我的Where子句中所有列表中的标准。例如:

Expression<Func<Client, bool>> clientWhere =
    c => andCriteria.All(pred => pred(c) ) && orCriteria.Any(pred => pred(c) );

出于可读性的考虑,这也可以使用for循环来完成。请记得在应用OR和AND子句时使用正确的运算顺序。


请注意,这也处理了你想要做更多事情而不仅仅使用“==”的情况,因为谓词可以是任何布尔函数。 - JoshJordan
1
Josh:如果我按照你的风格构建它,顺便说一下,你的风格很棒,然后调用查询: var query = from C in db.clients.Where(clientWhere) join O in db.orders.Where(orderWhere) on c.clientid equals O.clientid join P in db.products.Where(productWhere) on O.productid equals P.productid select new {C,O};我会得到这个错误: 本地序列不能在LINQ to SQL实现的查询运算符中使用,除了Contains()运算符。 - ltech
不确定这个问题,可能与您正在使用的特定标准有关。您尝试过使用空标准列表吗? - JoshJordan
Josh:我的查询返回IQueryable<Client>,然后我将其发送到分页例程: public PaginatedList(IQueryable<T> source, int pageIndex, int pageSize) { PageIndex = pageIndex; PageSize = pageSize; TotalCount = source.Count(); //这里是失败的地方 TotalPages = (int)Math.Ceiling(TotalCount / (double)PageSize);this.AddRange(source.Skip(PageIndex * PageSize).Take(PageSize));} - ltech

11
如果你遇到类似的问题,可以在这篇很棒的主题中找到所有可能的解决方案。单击此处。 或者只需使用PredicateBuilder,它是这个目的的一个非常好的助手。
var predicate = PredicateBuilder.True<Client>();

if (filterByClientFName)
{
    predicate = predicate.And(c => c.ClientFName == searchForClientFName);
}

if (filterByClientLName)
{
        predicate = predicate.And(c => c.ClientLName == searchForClientLName);
}

var result = context.Clients.Where(predicate).ToArray();

这是一个构建器的实现。

public static class PredicateBuilder
    {
        // Creates a predicate that evaluates to true.        
        public static Expression<Func<T, bool>> True<T>() { return param => true; }

        // Creates a predicate that evaluates to false.        
        public static Expression<Func<T, bool>> False<T>() { return param => false; }

        // Creates a predicate expression from the specified lambda expression.        
        public static Expression<Func<T, bool>> Create<T>(Expression<Func<T, bool>> predicate) { return predicate; }

        // Combines the first predicate with the second using the logical "and".        
        public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.AndAlso);
        }

        // Combines the first predicate with the second using the logical "or".        
        public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
        {
            return first.Compose(second, Expression.OrElse);
        }

        // Negates the predicate.        
        public static Expression<Func<T, bool>> Not<T>(this Expression<Func<T, bool>> expression)
        {
            var negated = Expression.Not(expression.Body);
            return Expression.Lambda<Func<T, bool>>(negated, expression.Parameters);
        }

        // Combines the first expression with the second using the specified merge function.        
        static Expression<T> Compose<T>(this Expression<T> first, Expression<T> second, Func<Expression, Expression, Expression> merge)
        {
            // zip parameters (map from parameters of second to parameters of first)
            var map = first.Parameters
                .Select((f, i) => new { f, s = second.Parameters[i] })
                .ToDictionary(p => p.s, p => p.f);

            // replace parameters in the second lambda expression with the parameters in the first
            var secondBody = ParameterRebinder.ReplaceParameters(map, second.Body);

            // create a merged lambda expression with parameters from the first expression
            return Expression.Lambda<T>(merge(first.Body, secondBody), first.Parameters);
        }

        class ParameterRebinder : ExpressionVisitor
        {
            readonly Dictionary<ParameterExpression, ParameterExpression> map;

            ParameterRebinder(Dictionary<ParameterExpression, ParameterExpression> map)
            {
                this.map = map ?? new Dictionary<ParameterExpression, ParameterExpression>();
            }

            public static Expression ReplaceParameters(Dictionary<ParameterExpression, ParameterExpression> map, Expression exp)
            {
                return new ParameterRebinder(map).Visit(exp);
            }

            protected override Expression VisitParameter(ParameterExpression p)
            {
                ParameterExpression replacement;
                if (map.TryGetValue(p, out replacement))
                {
                    p = replacement;
                }
                return base.VisitParameter(p);
            }
        }
    }

1
在PredicateBuilder中插入ParameterRebinder是无意中的错误吗?它们是两个不同的类,采用基本相同的方法来实现相同的目标。PredicateBuilder本身不适用于EF,它需要该扩展库中的其他类。另一个似乎对EF友好。感谢您提供源链接,这是一篇很棒的文章! - Heriberto Lugo

6

1
Predicate Builder是个好东西。它与LinqToSQL完美配合,而且最重要的是,易于使用,功能强大。 - Piotr Zierhoffer

2

从2020年开始有一种更加简单和优雅的解决方案 :)

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> first, Expression<Func<T, bool>> second)
    {
        return first.Compose(second, Expression.And);
    }

适用于IQueryable。


你能提供Compos方法的文档吗? - Kasun Koswattha
可以在此处找到:(https://learn.microsoft.com/en-us/archive/blogs/meek/linq-to-entities-combining-predicates)。我刚意识到对于这么简单的任务来说似乎过于复杂,并且重复了trueboroda提供的解决方案。 - Petr Savchenko
这是不完整的答案。Compose并不存在,因为它是在自定义类中创建的方法。And<T>也不存在。这个解决方案是误导性的,没有提供任何有用的东西。你只拿出了一个自定义类中的方法,并没有包含它所依赖的其他代码。 - Heriberto Lugo
嗨@HeribertoLugo,我认为我在评论中承认了重复,并参考了trueboroda的解决方案,其中包含了丰富的细节。 - Petr Savchenko

2

虽然这不完全是你问题的答案,但我也曾经寻找与你类似的解决方案,最后找到了更好的答案。

你可以通过获取IQueryable并进行过滤来实现想要的效果,而不是构建一个动态表达式,具体操作如下:

var customers = CustomerRepository.AllEntities();

if (!forename.IsNullOrEmpty())
    customers = customers.Where(p => p.Forename == forename);
if (!familyname.IsNullOrEmpty())
    customers = customers.Where(p => p.FamilyNames.Any(n => n.Name==familyname));
if (dob.HasValue)
    customers = customers.Where(p => p.DOB == dob);
注意:我担心执行多个“.Where”语句会在数据库中生成多个查询,或者因为我必须检索所有记录然后过滤它们而产生问题,但事实并非如此。Linq动态仅在调用.ToList()方法时生成一个查询。

这里是我从中取得例子的原始问题。


1

我有一些东西要给Josh(放在我的技能包里):

public static IQueryable<TSource> ObjectFilter<TSource>(this TSource SearchObject, List<Predicate<TSource>> andCriteria, List<Predicate<TSource>> orCriteria) where TSource : IQueryable<TSource>
        {
            //Yeah :)
            Expression<Func<TSource, bool>> ObjectWhere = O => andCriteria.All(pred => pred(O)) && orCriteria.Any(pred => pred(O));
            return SearchObject.Where<TSource>(ObjectWhere);
        }

-1

我尝试实现这种东西。花了我一天的时间才找到解决方案。 我的解决方案基于循环中的过滤器,基于谓词数组。 需要注意的是,它完全是通用的,并且基于反射,因为关于类和字段的唯一信息是字符串。 为了简单起见,我直接调用模型类,但在项目中,您应该通过调用模型的控制器来进行。

所以我们开始吧: T 是类中的通用类型的模型部分

    public class DALXmlRepository<T> where T : class
    {
    public T GetItem(Array predicate)
    {
        IQueryable<T> QueryList = null;

        QueryList = ObjectList.AsQueryable<T>().Where((Expression<Func<T, bool>>)predicate.GetValue(0));
        for (int i = 1; i < predicate.GetLength(0); i++)
        {
            QueryList = QueryList.Where((Expression<Func<T, bool>>)predicate.GetValue(i));
        }

        if (QueryList.FirstOrDefault() == null)
            throw new InvalidOperationException(this.GetType().GetGenericArguments().First().Name + " not found.");
        return QueryList.FirstOrDefault();
    }
    }

现在Lambda表达式构建器是一个基础版本(使用字符串类型或其他类型),您可以通过添加更多功能来改进它:

    private static Expression BuildLambdaExpression(Type GenericArgument, string FieldName, string FieldValue)
    {
        LambdaExpression lambda = null;

        Expression Criteria = null;

        Random r = new Random();
        ParameterExpression predParam = Expression.Parameter(GenericArgument, r.Next().ToString());

        if (GenericArgument.GetProperty(FieldName).PropertyType == typeof(string))
        {
            Expression left = Expression.PropertyOrField(predParam, FieldName);
            Expression LefttoUpper = Expression.Call(left, "ToUpper", null, null);
            //Type du champ recherché
            Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
            Expression right = Expression.Constant(FieldValue, propType);
            Expression RighttoUpper = Expression.Call(right, "ToUpper", null, null);
            Criteria = Expression.Equal(LefttoUpper, RighttoUpper);
        }
        else
        {
            Expression left = Expression.PropertyOrField(predParam, FieldName);
            Type propType = GenericArgument.GetProperty(FieldName).PropertyType;
            Expression right = Expression.Constant(Convert.ChangeType(FieldValue, propType), propType);

            Criteria = Expression.Equal(left, right);
        }

        lambda = Expression.Lambda(Criteria, predParam);
        return lambda;
    }

现在是调用函数:

    public static Hashtable GetItemWithFilter(string Entity, XMLContext contextXML, Hashtable FieldsNameToGet, Hashtable FieldFilter)
    {
        //Get the type
        Type type = Type.GetType("JP.Model.BO." + Entity + ", JPModel");
        Type CtrlCommonType = typeof(CtrlCommon<>).MakeGenericType( type );
        //Making an instance DALXmlRepository<xxx> XMLInstance = new DALXmlRepository<xxx>(contextXML);
        ConstructorInfo ci = CtrlCommonType.GetConstructor(new Type[] { typeof(XMLContext), typeof(String) });
        IControleur DalInstance = (IControleur)ci.Invoke(new object[] { contextXML, null });

        //Building the string type Expression<func<T,bool>> to init the array
        Type FuncType = typeof(Func<,>).MakeGenericType( type ,typeof(bool));
        Type ExpressType = typeof(Expression<>).MakeGenericType(FuncType);
        Array lambda = Array.CreateInstance(ExpressType,FieldFilter.Count);

        MethodInfo method = DalInstance.GetType().GetMethod("GetItem", new Type[] { lambda.GetType() });

        if (method == null)
            throw new InvalidOperationException("GetItem(Array) doesn't exist for " + DalInstance.GetType().GetGenericArguments().First().Name);

        int j = 0;
        IDictionaryEnumerator criterias = FieldFilter.GetEnumerator();
        criterias.Reset();
        while (criterias.MoveNext())
        {
            if (!String.IsNullOrEmpty(criterias.Key.ToString()))
            {
                lambda.SetValue(BuildLambdaExpression(type, criterias.Key.ToString(), criterias.Value.ToString()),j);
            }
            else
            {
                throw new JPException(JPException.MessageKey.CONTROLER_PARAMFIELD_EMPTY, "GetItemWithFilter", criterias.Key.ToString());
            }
            j++;
        }

        Object item = method.Invoke(DalInstance, new object[] { lambda });
        }

参数如下: String Entity:实体类名称。 XMLContext:存储库的工作单元,我用它来初始化模型类的参数。 Hashtable FieldsNameToGet:要获取的字段列表的索引/值 Hashtable FieldFilter:用于创建Lambda表达式的键/值对,其中包含FieldName/Content。
祝你好运。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接