C# 如何附加到表达式

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1266742/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-06 14:26:39  来源:igfitidea点击:

How to Append to an expression

c#.netlinq

提问by

Based on my question from yesterday:

根据我昨天的问题

if I had to append to my existing 'where' expression, how would i append?

如果我必须附加到我现有的“where”表达式,我将如何附加?

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

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

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

The user can input either first name or last name or both. If they enter both i want to append to the expression. Trying to see if there is an equivalent of an append where i could do

用户可以输入名字或姓氏或两者。如果他们同时输入,我想附加到表达式中。试图看看是否有一个相当于我可以做的附加

clientWhere.Append or clientWhere += add new expression

or something similar

或类似的东西

回答by JoshJordan

This is a complex scenario. You are almost building your own query engine on top of LINQ. JaredPar's solution (where did it go?) is great if you want a logical AND between all of your criteria, but that may not always be the case.

这是一个复杂的场景。您几乎是在 LINQ 之上构建自己的查询引擎。JaredPar 的解决方案(它去哪儿了?)如果您想在所有标准之间进行逻辑 AND 是非常棒的,但情况可能并非总是如此。

When I was wrangling with this in one of my project recently, I created two Lists:

最近在我的一个项目中与此争论时,我创建了两个列表:

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

(In this case, T is Client, for you)

(在这种情况下, T 是Client,对你来说)

I would populate the Lists with predicates that I want to be true. For instance,

我会用我想要为真的谓词填充列表。例如,

decimal salRequirement = 50000.00;
andCriteria.Add(c => c.Salary > salRequirement);
orCriteria.Add(c => c.IsMarried);

Then, I would check against all the criteria in the Lists in my Where clause. For instance:

然后,我将检查我的 Where 子句中列表中的所有标准。例如:

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

This could also be done with a for-loop for readability's sake. Remember to use the correct order of operations when applying your OR and AND clauses.

为了可读性,这也可以通过 for 循环来完成。请记住在应用 OR 和 AND 子句时使用正确的操作顺序。

回答by Jason

I believe you can just do the following:

我相信你可以做到以下几点:

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;
}

If you need to keep everything in Expression-land (to use with IQueryable), you could also do the following:

如果您需要将所有内容保留在Expression-land(与 一起使用IQueryable),您还可以执行以下操作:

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);
}

This can be made less verbose by defining this extension method:

通过定义这个扩展方法,这可以变得不那么冗长:

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

You can then use syntax like this:

然后你可以使用这样的语法:

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);
}

回答by Vasu Balakrishnan

Take a look at Predicate Builder, I believe this might work for you.

看看Predicate Builder,我相信这可能对你有用

回答by MarkWalls

Or something to add to Josh (Put it in my bag of tricks):

或添加到乔希的东西(把它放在我的技巧包里):

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);
        }

回答by Daniel

It′s not exactly the answer for your question, but, I was looking for the same thing you are, and then I've found a better answer to my question.

这不完全是您问题的答案,但是,我一直在寻找与您相同的东西,然后我找到了更好的答案。

Instead of building a dynamic Expression, you could retrieve the IQueryable and then filter what you want like this:

您可以检索 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);

Note:I was concerned about executing more then one ".Where" statement because I was afraid this would generate more than one query in the DataBase, or because I would have to retrive all records and then filter them, but this is not true, Linq dynamic generate just one query only when you call .ToList() method.

注意:我担心执行多个“.Where”语句,因为我担心这会在数据库中生成多个查询,或者因为我必须检索所有记录然后过滤它们,但事实并非如此, 仅当您调用 .ToList() 方法时,Linq 动态仅生成一个查询。

Hereyou can see original question that I've took the example from.

在这里你可以看到我从这个例子中得到的原始问题。

回答by Alan

I tried to implement this kind of stuff. Took me a day to find out. My solution is based on filter in a loop based on a Array of predicate. As a note, it s totally Generic and based Reflection because the only information about class and field are String. To make it simple, i call directly the Model class but in a project you should go by a controler who is calling the Model.

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

So here we go : The Model part where T is a Generic in the class

所以我们开始:模型部分,其中 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();
    }
    }

Now the LambdaExpression Builder, it's a base one(with String type or something else) , you can improve it with more functionnality :

现在 LambdaExpression Builder,它是一个基础的(带有 String 类型或其他类型),您可以使用更多功能对其进行改进:

    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;
    }

Now the Calling function :

现在调用函数:

    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 });
        }

The argument are : String Entity : Entity class name. XMLContext : it s the unit of work of the repository, argument i use to initialize the Model class Hashtable FieldsNameToGet : Index/value of the list of the field i want to get back Hashtable FieldFilter : the key/Value with FieldName/Content used to make the Lambda expression

参数是: String Entity :实体类名。XMLContext :它是存储库的工作单元,我用来初始化模型类的参数 Hashtable FieldsNameToGet :我想要取回的字段列表的索引/值 Hashtable FieldFilter :用于 FieldName/Content 的键/值制作 Lambda 表达式

Good Luck.

祝你好运。

回答by trueboroda

If you encounter a similar problem, you can find all possible solutions in this great topic. Or just use PredicateBuilder is awesome helper for this poporse.

如果您遇到类似的问题,您可以在这个伟大的主题中找到所有可能的解决方案。或者只是使用 PredicateBuilder 是这个 poporse 的好帮手。

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();

It is some builder implementation.

这是一些构建器实现。

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);
            }
        }
    }

回答by Petr Savchenko

Much easier and elegant solution from 2020 :)

从 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);
    }

Works for IQueryable.

适用于 IQueryable。