C# 中的柯里化表达式

发布于 2024-10-31 20:45:24 字数 3546 浏览 4 评论 0 原文

我正在尝试构建一个可以输入 Linq2SQL 的表达式树,以便它将生成一个漂亮的干净查询。我的目的是构建一个过滤器,将任意单词集与 AND 和 NOT(或 OR 和 NOT)结合在一起。因为我想改变我搜索的字段,所以我最好想将 Expression> 组合在一起(其中 T 是我正在操作的实体) on)通过调用各种辅助函数。然后我会收到一个单词数组并循环它们并构建一个 Expression> 向上(必要时否定某些表达式),我最终可以将其提供给 .Where 语句。

我一直在使用 LINQKit PredicateBuilder 但此代码处理单个参数表达式。不过,它为我自己的尝试提供了一些基础。我的目标是做这样的事情:

var e = (Expression<Func<Entity, string, bool>>)((p, w) => p.SomeField.ToLower().Contains(w));

var words = new []{"amanda", "bob"};

var expr = (Expression<Func<Entity, bool>>)(p => false);
// building up an OR query
foreach(var w in words) {
    var w1 = w;
>>>>expr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(expr.Body, (Expression<Func<Entity, bool>>)(p => e(p, w))));
}

var filteredEntities = table.Where(expr);

但是由于我使用的是表达式,所以用 >>>> 标记的行是显然是非法的(不能像对函数那样执行e(p, w))。所以我的问题是如何将单个变量(单词)部分应用到包含具有多个参数的函数的表达式?


好的,在 LINQPad 中摆弄并找到了一个适合我的解决方案。 这个问题让我到达了那里。我对构建表达式树还很陌生,所以我很感激(并赞成)任何带有改进或批评的评论/答案。

// Some set of expressions to test against
var expressions = new List<Expression<Func<Entity, string, bool>>>();
expressions.Add((p, w) => p.FirstName.ToLower().Contains(w));
expressions.Add((p, w) => p.LastName.ToLower().Contains(w));
expressions.Add((p, w) => p.Department != null && p.Department.Name.ToLower().Contains(w));

var words = new []{"amanda", "bob"};
var negs = new []{"smith"}; // exclude any entries including these words

var isAndQuery = true; // negate for an OR query
Expression<Func<Entity, bool>> posExpr = p => isAndQuery;

var entityParameter = Expression.Parameter(typeof(Entity), null);

// Build up the NOTs
var negExpr = (Expression<Func<Entity, bool>>)(p => true);
foreach(var w in negs) {
    var w1 = w;
    foreach(var e in expressions) {
        var andNot = Expression.Invoke(e, entityParameter, Expression.Constant(w1));
        negExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(negExpr.Body, Expression.Not(andNot)), entityParameter);
    }
}

// Build up the ANDs or ORs
foreach(var w in words) {
    var w1 = w;
    var orExpr = (Expression<Func<Entity, bool>>)(p => false);
    foreach(var e in expressions) {
        var orElse = Expression.Invoke(e, entityParameter, Expression.Constant(w1));
        orExpr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(orExpr.Body, orElse), entityParameter);
    }
    var orInvoked = Expression.Invoke(orExpr, posExpr.Parameters.Cast<Expression>());
    if(isAndQuery)
        posExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(posExpr.Body, orInvoked), entityParameter);
    else
        posExpr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(posExpr.Body, orInvoked), entityParameter);
}
var posInvoked = Expression.Invoke(posExpr, posExpr.Parameters.Cast<Expression>());
var finalExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(negExpr.Body, posInvoked), entityParameter);

var filteredEntities = entities.Where(finalExpr);

I am trying to build up an expression tree that I can feed into Linq2SQL so that it will generate a nice clean query. My purpose is to build a filter that takes an arbitrary set of words to AND and NOT (or OR and NOT) together. Because I want to vary the fields that I search on I preferably want to compose a list of Expresssion<Func<T, string, bool>>'s together (where T is the entity I am operating on) by calling a variety of helper functions. Then I would receive an array of words and loop though them and build an Expresssion<Func<T, bool>> up (negating certain expressions where necessary) that I can eventually feed to a .Where statement.

I have been using LINQKit PredicateBuilder but this code deals with single parameter expressions. However, it has provided me with some groundwork for my own attempts. I am aiming to do something like this:

var e = (Expression<Func<Entity, string, bool>>)((p, w) => p.SomeField.ToLower().Contains(w));

var words = new []{"amanda", "bob"};

var expr = (Expression<Func<Entity, bool>>)(p => false);
// building up an OR query
foreach(var w in words) {
    var w1 = w;
>>>>expr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(expr.Body, (Expression<Func<Entity, bool>>)(p => e(p, w))));
}

var filteredEntities = table.Where(expr);

But since I am using Expressions the line marked by >>>> is obviously illegal (cannot do e(p, w) like I could for a function). So my question is how do I do the partial application of a single variable (the word) to expressions containing functions with multiple parameters?


Okay, fiddled around in LINQPad and figured out a solution that works for me. This question got me there. I am pretty new to building up expression trees so I would appreciate (and upvote) any comments/answers with improvements or criticism.

// Some set of expressions to test against
var expressions = new List<Expression<Func<Entity, string, bool>>>();
expressions.Add((p, w) => p.FirstName.ToLower().Contains(w));
expressions.Add((p, w) => p.LastName.ToLower().Contains(w));
expressions.Add((p, w) => p.Department != null && p.Department.Name.ToLower().Contains(w));

var words = new []{"amanda", "bob"};
var negs = new []{"smith"}; // exclude any entries including these words

var isAndQuery = true; // negate for an OR query
Expression<Func<Entity, bool>> posExpr = p => isAndQuery;

var entityParameter = Expression.Parameter(typeof(Entity), null);

// Build up the NOTs
var negExpr = (Expression<Func<Entity, bool>>)(p => true);
foreach(var w in negs) {
    var w1 = w;
    foreach(var e in expressions) {
        var andNot = Expression.Invoke(e, entityParameter, Expression.Constant(w1));
        negExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(negExpr.Body, Expression.Not(andNot)), entityParameter);
    }
}

// Build up the ANDs or ORs
foreach(var w in words) {
    var w1 = w;
    var orExpr = (Expression<Func<Entity, bool>>)(p => false);
    foreach(var e in expressions) {
        var orElse = Expression.Invoke(e, entityParameter, Expression.Constant(w1));
        orExpr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(orExpr.Body, orElse), entityParameter);
    }
    var orInvoked = Expression.Invoke(orExpr, posExpr.Parameters.Cast<Expression>());
    if(isAndQuery)
        posExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(posExpr.Body, orInvoked), entityParameter);
    else
        posExpr = Expression.Lambda<Func<Entity, bool>>(Expression.OrElse(posExpr.Body, orInvoked), entityParameter);
}
var posInvoked = Expression.Invoke(posExpr, posExpr.Parameters.Cast<Expression>());
var finalExpr = Expression.Lambda<Func<Entity, bool>>(Expression.AndAlso(negExpr.Body, posInvoked), entityParameter);

var filteredEntities = entities.Where(finalExpr);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

不必在意 2024-11-07 20:45:24

我喜欢使用 linq 来构建表达式树,它让我感觉超级强大,所以我添加了这个,不是作为对你问题的完整答案,而是一种构建表达式树的优雅方法......

var query = ...;
var search = "asdfasdf";
var fields = new Expression<Func<MyEntity,string>>[]{ 
    x => x.Prop1, 
    x => x.Prop2, 
    x => x.Prop3 
};
var notFields = new Expression<Func<MyEntity,string>>[]{ 
    x => x.Prop4, 
    x => x.Prop5 };

//----- 
var paramx = Expression.Parameter(query.ElementType);

//get fields to search for true
var whereColumnEqualsx = fields
    .Select(x => Expression.Invoke(x,paramx))
    .Select(x => Expression.Equal(x,Expression.Constant(search)))
    //you could change the above to use .Contains(...) || .StartsWith(...) etc.
    //you could also make it not case sensitive by 
    //wraping 'x' with a .ToLower() expression call, 
    //and setting the search constant to 'search.ToLower()'
    .Aggregate((x,y) => Expression.And(x,y));

//get fields to search for false
var whereColumnNotEqualsx = notFields
    .Select(x => Expression.Invoke(x,paramx))
    .Select(x => Expression.NotEqual(x, Expression.Constant(search)))
    //see above for the different ways to build your 'not' expression,
    //however if you use a .Contains() you need to wrap it in an Expression.Negate(...)
    .Aggregate((x,y) => Expression.Or(x,y));
    //you can change Aggregate to use Expression.And(...) 
    //if you want the query to exclude results only if the 
    //search string is in ALL of the negated fields.

var lambdax = Expression.Lambda(
    Expression.And(whereColumnEqualsx, whereColumnNotEqualsx), paramx);

var wherex = Expression.Call(typeof(Queryable)
    .GetMethods()
    .Where(x => x.Name == "Where")
    .First()
    .MakeGenericMethod(query.ElementType),
    query.Expression,lambdax);

//create query
var query2 = query.Provider.CreateQuery(wherex).OfType<MyEntity>();

I like using linq to build epression trees, it makes me feel uber-powerfull, so I've added this, not as a complete answer to your question, but more a an elegant way to build up expression trees...

var query = ...;
var search = "asdfasdf";
var fields = new Expression<Func<MyEntity,string>>[]{ 
    x => x.Prop1, 
    x => x.Prop2, 
    x => x.Prop3 
};
var notFields = new Expression<Func<MyEntity,string>>[]{ 
    x => x.Prop4, 
    x => x.Prop5 };

//----- 
var paramx = Expression.Parameter(query.ElementType);

//get fields to search for true
var whereColumnEqualsx = fields
    .Select(x => Expression.Invoke(x,paramx))
    .Select(x => Expression.Equal(x,Expression.Constant(search)))
    //you could change the above to use .Contains(...) || .StartsWith(...) etc.
    //you could also make it not case sensitive by 
    //wraping 'x' with a .ToLower() expression call, 
    //and setting the search constant to 'search.ToLower()'
    .Aggregate((x,y) => Expression.And(x,y));

//get fields to search for false
var whereColumnNotEqualsx = notFields
    .Select(x => Expression.Invoke(x,paramx))
    .Select(x => Expression.NotEqual(x, Expression.Constant(search)))
    //see above for the different ways to build your 'not' expression,
    //however if you use a .Contains() you need to wrap it in an Expression.Negate(...)
    .Aggregate((x,y) => Expression.Or(x,y));
    //you can change Aggregate to use Expression.And(...) 
    //if you want the query to exclude results only if the 
    //search string is in ALL of the negated fields.

var lambdax = Expression.Lambda(
    Expression.And(whereColumnEqualsx, whereColumnNotEqualsx), paramx);

var wherex = Expression.Call(typeof(Queryable)
    .GetMethods()
    .Where(x => x.Name == "Where")
    .First()
    .MakeGenericMethod(query.ElementType),
    query.Expression,lambdax);

//create query
var query2 = query.Provider.CreateQuery(wherex).OfType<MyEntity>();
断桥再见 2024-11-07 20:45:24

这个例子可能对你有帮助。我想最好的方法是构建不带 lambda 的表达式:

public class Entity
{
    public Entity(string someField)
    {
        SomeField = someField;
    }

    public string SomeField { get; set;  }
}

class Program
{
    static void Main(string[] args)
    {
        var entities = new[] {new Entity("fooBar"), new Entity("barBaz"), new Entity("baz"), new Entity("foo")};
        entities.Where(BuildExpression("ar","az").Compile())
                .ToList()
                .ForEach(e => Console.WriteLine(e.SomeField));
        Console.ReadLine();
    }

    public static Expression<Func<Entity, bool>> BuildExpression(params string[] words)
    {
        var parameter = Expression.Parameter(typeof (Entity));

        var matchs = words.Select(word =>
                                        {
                                            var property = Expression.Property(parameter, "SomeField");
                                            var toLower = Expression.Call(property, "ToLower", new Type[] {});
                                            var contains = Expression.Call(toLower, "Contains",
                                                                            new Type[]{},
                                                                            Expression.Constant(word));
                                            return contains;
                                        }).OfType<Expression>();

        var body = matchs.Aggregate(Expression.Or);

        return Expression.Lambda<Func<Entity, bool>>(body, new[] {parameter});
    } 
}

请告诉我是否应该为此答案添加更多信息。

this example might help you. I guess the best is to build the expression without lambdas:

public class Entity
{
    public Entity(string someField)
    {
        SomeField = someField;
    }

    public string SomeField { get; set;  }
}

class Program
{
    static void Main(string[] args)
    {
        var entities = new[] {new Entity("fooBar"), new Entity("barBaz"), new Entity("baz"), new Entity("foo")};
        entities.Where(BuildExpression("ar","az").Compile())
                .ToList()
                .ForEach(e => Console.WriteLine(e.SomeField));
        Console.ReadLine();
    }

    public static Expression<Func<Entity, bool>> BuildExpression(params string[] words)
    {
        var parameter = Expression.Parameter(typeof (Entity));

        var matchs = words.Select(word =>
                                        {
                                            var property = Expression.Property(parameter, "SomeField");
                                            var toLower = Expression.Call(property, "ToLower", new Type[] {});
                                            var contains = Expression.Call(toLower, "Contains",
                                                                            new Type[]{},
                                                                            Expression.Constant(word));
                                            return contains;
                                        }).OfType<Expression>();

        var body = matchs.Aggregate(Expression.Or);

        return Expression.Lambda<Func<Entity, bool>>(body, new[] {parameter});
    } 
}

Please let me know if I should add more information to this answer.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文