Combine Two Linq Lambda Expressions

Combine two Linq lambda expressions

So logically what we want to be able to do is create a new lambda in which it has a parameter of the input to the first function, and a body that calls the first function with that parameter and then passes the result as the parameter to the second function, and then returns that.

We can replicate that easily enough using Expression objects:

public static Expression<Func<T1, T3>> Combine<T1, T2, T3>(
Expression<Func<T1, T2>> first,
Expression<Func<T2, T3>> second)
{
var param = Expression.Parameter(typeof(T1), "param");
var body = Expression.Invoke(second, Expression.Invoke(first, param));
return Expression.Lambda<Func<T1, T3>>(body, param);
}

Sadly, EF and most other query providers won't really know what to do with that and won't function properly. Whenever they hit an Invoke expression they generally just throw an exception of some sort. Some can handle it though. In theory all the information they need is there, if they're written with the robustness to get at it.

What we can do however is, from a conceptual standpoint, replace every instance of the first lambda's parameter in that lambda's body with the parameter of a new lambda we're creating, and then replace all instances of the second lambda's parameter in the second lambda with the new body of the first lambda. Technically, if these expressions have side effects, and these parameters are used more than once, they wouldn't be the same, but as these are going to be parsed by an EF query provider they really shouldn't ever have side effects.

Thanks to David B for providing a link to this related question which provides a ReplaceVisitor implementation. We can use that ReplaceVisitor to go through the entire tree of an expression and replace one expression with another. The implementation of that type is:

class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}

And now we can write our proper Combine method:

public static Expression<Func<T1, T3>> Combine<T1, T2, T3>(
this Expression<Func<T1, T2>> first,
Expression<Func<T2, T3>> second)
{
var param = Expression.Parameter(typeof(T1), "param");

var newFirst = new ReplaceVisitor(first.Parameters.First(), param)
.Visit(first.Body);
var newSecond = new ReplaceVisitor(second.Parameters.First(), newFirst)
.Visit(second.Body);

return Expression.Lambda<Func<T1, T3>>(newSecond, param);
}

and a simple test case, to just demonstrate what's going on:

Expression<Func<MyObject, string>> fn1 = x => x.PossibleSubPath.MyStringProperty;
Expression<Func<string, bool>> fn2 = x => x.Contains("some literal");

var composite = fn1.Combine(fn2);

Console.WriteLine(composite);

Which will print out:

param => param.PossibleSubPath.MyStringProperty.Contains("some literal")

Which is exactly what we want; a query provider will know how to parse something like that.

How to Combine two lambdas

To complete Eric's answer, using the new ExpressionVisitor introduced in .NET 4 rather than a custom rewriter:

internal class ParameterReplacer : ExpressionVisitor {
private readonly ParameterExpression _parameter;

protected override Expression VisitParameter(ParameterExpression node) {
return base.VisitParameter(_parameter);
}

internal ParameterReplacer(ParameterExpression parameter) {
_parameter = parameter;
}
}

class Program {

static void Main(string[] args) {
Expression<Func<string, bool>> expr1 = s => s.Length == 5;
Expression<Func<string, bool>> expr2 = s => s == "someString";
var paramExpr = Expression.Parameter(typeof(string));
var exprBody = Expression.Or(expr1.Body, expr2.Body);
exprBody = (BinaryExpression) new ParameterReplacer(paramExpr).Visit(exprBody);
var finalExpr = Expression.Lambda<Func<string, bool>>(exprBody, paramExpr);
}

}

Combining two expressions (ExpressionFuncT, bool)

Well, you can use Expression.AndAlso / OrElse etc to combine logical expressions, but the problem is the parameters; are you working with the same ParameterExpression in expr1 and expr2? If so, it is easier:

var body = Expression.AndAlso(expr1.Body, expr2.Body);
var lambda = Expression.Lambda<Func<T,bool>>(body, expr1.Parameters[0]);

This also works well to negate a single operation:

static Expression<Func<T, bool>> Not<T>(
this Expression<Func<T, bool>> expr)
{
return Expression.Lambda<Func<T, bool>>(
Expression.Not(expr.Body), expr.Parameters[0]);
}

Otherwise, depending on the LINQ provider, you might be able to combine them with Invoke:

// OrElse is very similar...
static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> left,
Expression<Func<T, bool>> right)
{
var param = Expression.Parameter(typeof(T), "x");
var body = Expression.AndAlso(
Expression.Invoke(left, param),
Expression.Invoke(right, param)
);
var lambda = Expression.Lambda<Func<T, bool>>(body, param);
return lambda;
}

Somewhere, I have got some code that re-writes an expression-tree replacing nodes to remove the need for Invoke, but it is quite lengthy (and I can't remember where I left it...)


Generalized version that picks the simplest route:

static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
// need to detect whether they use the same
// parameter instance; if not, they need fixing
ParameterExpression param = expr1.Parameters[0];
if (ReferenceEquals(param, expr2.Parameters[0]))
{
// simple version
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(expr1.Body, expr2.Body), param);
}
// otherwise, keep expr1 "as is" and invoke expr2
return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(
expr1.Body,
Expression.Invoke(expr2, param)), param);
}

Starting from .NET 4.0, there is the ExpressionVisitor class which allows you to build expressions that are EF safe.

    public static Expression<Func<T, bool>> AndAlso<T>(
this Expression<Func<T, bool>> expr1,
Expression<Func<T, bool>> expr2)
{
var parameter = Expression.Parameter(typeof (T));

var leftVisitor = new ReplaceExpressionVisitor(expr1.Parameters[0], parameter);
var left = leftVisitor.Visit(expr1.Body);

var rightVisitor = new ReplaceExpressionVisitor(expr2.Parameters[0], parameter);
var right = rightVisitor.Visit(expr2.Body);

return Expression.Lambda<Func<T, bool>>(
Expression.AndAlso(left, right), parameter);
}

private class ReplaceExpressionVisitor
: ExpressionVisitor
{
private readonly Expression _oldValue;
private readonly Expression _newValue;

public ReplaceExpressionVisitor(Expression oldValue, Expression newValue)
{
_oldValue = oldValue;
_newValue = newValue;
}

public override Expression Visit(Expression node)
{
if (node == _oldValue)
return _newValue;
return base.Visit(node);
}
}

How can I combine two lambda expressions without using Invoke method?

The problem is that you can't just "and"/"or" them, because you need to re-write the internals to change the parameters; if you use the .Body from e1, but the parameter from e2, it won't work - because the .Body of e1 references a completely unrelated parameter instance that isn't defined. This is more obvious if you use:

Expression<Func<MyEntity, bool>> e1 = i => i.FName.Contains("john");
Expression<Func<MyEntity, bool>> e2 = j => j.LName.Contains("smith");

(note the difference between e1 using i and e2 using j)

If we combine them without rewriting the parameter, we would get the nonsensical:

Expression<Func<MyEntity, bool>> combined =
i => i.FName.Contains("john") && j.LName.Contains("smith");

(woah.... where did j come from?)

HOWEVER; the problem is identical regardless of the name of the parameter: it is still a different parameter.

And since the expression is immutable you can't just swap it "in place".

The trick is to use a "visitor" to rewrite the nodes, like so:

using System;
using System.Linq.Expressions;

class SwapVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public SwapVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}

static class Program
{
static void Main()
{
Expression<Func<MyEntity, bool>> e1 = i => i.FName.Contains("john");
Expression<Func<MyEntity, bool>> e2 = i => i.LName.Contains("smith");

// rewrite e1, using the parameter from e2; "&&"
var lambda1 = Expression.Lambda<Func<MyEntity, bool>>(Expression.AndAlso(
new SwapVisitor(e1.Parameters[0], e2.Parameters[0]).Visit(e1.Body),
e2.Body), e2.Parameters);

// rewrite e1, using the parameter from e2; "||"
var lambda2 = Expression.Lambda<Func<MyEntity, bool>>(Expression.OrElse(
new SwapVisitor(e1.Parameters[0], e2.Parameters[0]).Visit(e1.Body),
e2.Body), e2.Parameters);
}
}

How to combine multiple c# Lambda Expressions (ExpressionFuncT, T)

You cannot use AndAlso, since that's meant for the BinaryExpression - Expression<Func<T,bool>>, In this case you need Expression Visitor, as defined here by Marc Gravell (so he deserves all the credit)

I am using the same to provide a solution in your case, with an assumption of Problem class schema, pasting the Linqpad code:

void Main()
{
var final = UpdateBulk((Problem p) => new Problem{CatId = 1,SubCatId = 2, ListId=3});

// final is of type Expression<Func<T,T>>, which can be used for further processing

final.Dump();
}

public static Expression<Func<T, T>> UpdateBulk<T>(Expression<Func<T, T>> updateFactory) where T : IBaseEntity, new()
{
Expression<Func<T, T>> modifiedExpression = x => new T() { ModifiedBy = "Test", ModifiedDate = DateTime.Now };

var result = Combine(updateFactory, modifiedExpression);

return result;
}

static Expression<Func<TSource, TDestination>> Combine<TSource, TDestination>(
params Expression<Func<TSource, TDestination>>[] selectors)
{
var param = Expression.Parameter(typeof(TSource), "x");
return Expression.Lambda<Func<TSource, TDestination>>(
Expression.MemberInit(
Expression.New(typeof(TDestination).GetConstructor(Type.EmptyTypes)),
from selector in selectors
let replace = new ParameterReplaceVisitor(
selector.Parameters[0], param)
from binding in ((MemberInitExpression)selector.Body).Bindings
.OfType<MemberAssignment>()
select Expression.Bind(binding.Member,
replace.VisitAndConvert(binding.Expression, "Combine")))
, param);
}

class ParameterReplaceVisitor : ExpressionVisitor
{
private readonly ParameterExpression from, to;
public ParameterReplaceVisitor(ParameterExpression from, ParameterExpression to)
{
this.from = from;
this.to = to;
}
protected override Expression VisitParameter(ParameterExpression node)
{
return node == from ? to : base.VisitParameter(node);
}
}

public abstract class IBaseEntity
{
public System.DateTime CreatedDate { get; set; }
public string CreatedBy { get; set; }

public System.DateTime ModifiedDate { get; set; }
public string ModifiedBy { get; set; }

public string DeletedBy { get; set; }
}

public class Problem : IBaseEntity
{
public int CatId { get; set; }

public int SubCatId { get; set; }

public int ListId { get; set; }
}

LINQ lambda: how to combine two tables with no links

This query should create needed result:

var Foobar = Foo.SelectMany(f => Bar, (f, b) => new { Foo = f, Bar = b});

Need to combine two lambda expressions in one to execute two separate conditions of one parameter

I did this

            if (tempChoiceConfigItems.Any(cg => cg.IsIncluded == true))
{
tempChoiceConfigItems
.Select(stc =>
{
stc.IsSelected = stc.IsIncluded;
return stc;
})
.ToList();
}

Combine two lambda expressions with inner expression

The ParameterReplacer you have used is too simplified and blindly replaces every parameter.

Use this instead:

public static class ExpressionUtils
{
public static Expression ReplaceParameter(this Expression expression, ParameterExpression source, Expression target)
{
return new ParameterReplacer { Source = source, Target = target }.Visit(expression);
}

class ParameterReplacer : ExpressionVisitor
{
public ParameterExpression Source;
public Expression Target;
protected override Expression VisitParameter(ParameterExpression node)
{
return node == Source ? Target : base.VisitParameter(node);
}
}
}

Or use this or this predicate builder helpers.



Related Topics



Leave a reply



Submit