Using info from various SO posts and notably this blog (corrected to use AndAlso
rather than And
) I've managed to combine similarly typed linq expressions into a single predicate. But now I want to combine two Expressions where one is an input to the other. Here's the fully expanded original Expression
;
private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames)
{
// works
Expression<Func<T, bool>> Texpr = x => x.Security.Readers.Any(n => AccessorNames.ToStringArray().Contains(n.Text));
return Texpr;
}
Note that crucially, I need to manage these as Expressions because my DB driver needs to walk the tree and convert into a native call so using Compile() to combine is not an option.
So below is the function I want to combine with the Any()
call above. The final output Expression needs to be of type Expression<Func<T, bool>>
and I need to pass x.Security.Readers
into this one.
public static Expression<Func<IEnumerable<EntityName>,bool>> AccessCheckExpression(IEnumerable<EntityName> AccessorNames)
{
return accessList => accessList.Any(n => AccessorNames.ToStringArray().Contains(n.Text));
}
I've got as far as this, but I'm struggling to work out how to swap out accessList =>
from accessCheck
and have it use accessList
in a single Expression. So far, I have this;
private Expression<Func<T, bool>> ExpressionIsNamed(IEnumerable<EntityName> AccessorNames)
{
Expression<Func<T, IEnumerable<EntityName>>> accessList = (T x) => x.Security.Readers;
Expression<Func<IEnumerable<EntityName>, bool>> accessCheck = SecurityDescriptor.AccessCheckExpression(AccessorNames);
// Combine?
Expression<Func<T, bool>> Texpr = ??? accessCheck + accessList ???
return Texpr;
}
[Update]
So I've got a little further;
class ParameterUpdateVisitor : System.Linq.Expressions.ExpressionVisitor
{
private ParameterExpression _oldParameter;
private ParameterExpression _newParameter;
public ParameterUpdateVisitor(ParameterExpression oldParameter, ParameterExpression newParameter)
{
_oldParameter = oldParameter;
_newParameter = newParameter;
}
protected override Expression VisitParameter(ParameterExpression node)
{
if (object.ReferenceEquals(node, _oldParameter))
return _newParameter;
return base.VisitParameter(node);
}
}
static Expression<Func<T, bool>> UpdateParameter<T>(
Expression<Func<T, IEnumerable<EntityName>>> expr,
ParameterExpression newParameter)
{
var visitor = new ParameterUpdateVisitor(expr.Parameters[0], newParameter);
var body = visitor.Visit(expr.Body);
return Expression.Lambda<Func<T, bool>>(body, newParameter);
}
I can then compile with;
UpdateParameter(accessList, accessCheck.Parameters[0]);
Thanks to all for those Invoke()
suggestions, but my guess is that by the time they get to the MongoDB driver it won't like InvocationExpression
. However, both Invoke
and my code above now fail in exactly the same way. Namely;
System.ArgumentException: Expression of type
'System.Func`2[MyLib.Project,System.Collections.Generic.IEnumerable`1[MyLib.EntityName]]'
cannot be used for parameter of type
'System.Collections.Generic.IEnumerable`1[MyLib.EntityName]'
So it would appear that the implicit parameter x.Security.Readers
is a different type to a plain old IEnumerable<EntityName>
to combine two expressions or more, put every expression in brackets, and use: *?
An expression in C# is a combination of operands (variables, literals, method calls) and operators that can be evaluated to a single value. To be precise, an expression must have at least one operand but may not have any operator.
VisitorExpression
is your friend here. Here's a simplified but complete example of merging something similar:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
class Source {
public List<Value> Values {get;set;}
}
class Value {
public int FinalValue {get;set;}
}
static class Program {
static void Main() {
Expression<Func<Source, IEnumerable<Value>>> f1 =
source => source.Values;
Expression<Func<IEnumerable<Value>, bool>> f2 =
vals => vals.Any(v => v.FinalValue == 3);
// change the p0 from f2 => f1
var body = SwapVisitor.Swap(f2.Body, f2.Parameters[0], f1.Body);
var lambda = Expression.Lambda<Func<Source, bool>>(body,f1.Parameters);
// which is:
// source => source.Values.Any(v => (v.FinalValue == 3))
}
}
class SwapVisitor : ExpressionVisitor {
private readonly Expression from, to;
private SwapVisitor(Expression from, Expression to) {
this.from = from;
this.to = to;
}
public static Expression Swap(Expression body,
Expression from, Expression to)
{
return new SwapVisitor(from, to).Visit(body);
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}
Edit: in your case, this would be:
private Expression<Func<T, bool>> ExpressionIsNamed(
IEnumerable<EntityName> AccessorNames)
{
Expression<Func<T, IEnumerable<EntityName>>> accessList =
(T x) => x.Security.Readers;
Expression<Func<IEnumerable<EntityName>, bool>> accessCheck =
SecurityDescriptor.AccessCheckExpression(AccessorNames);
var body = SwapVisitor.Swap(accessCheck.Body,
accessCheck.Parameters[0], accessList.Body);
return Expression.Lambda<Func<T, bool>>(body, accessList.Parameters);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With