Frage

I can extract and reuse entire expression like that:

Expression<Func<User, int>> userExpression = x => x.Roles.Count()

but is it possible to extract some how only x.Roles.Count() part and use that in context of Expression<Func<User, T>>

Thing that I am trying to achieve is reuse that part across different selects like:

users.Select(x => new AnotherClass { RoleCount = roleCountPartOfExpression})

and

users.Select(x => new OneMoreAnotherClass 
              { 
                 AnotherProperty = roleCountPartOfExpression
              });

So what roleCountPartOfExpression is supposed to be in this case should be supported in LINQ to Entities (so creating a method where I will pass User where will be return user.Roles.Count() will not work) also I cant create expression for select like Expression<Func<User, AnotherClass>> because in that case I will need to create Expression<Func<User, OneMoreAnotherClass>> and that will break my "reusability" goal.

War es hilfreich?

Lösung

If you compile to a Func<User, int>, you can call it in other areas like so:

Expression<Func<User, int>> userExpression = x => x.Roles.Count();

Func<User,int> userFunc = userExpression.Compile();

users.Select(x => new AnotherClass { RoleCount = userFunc(x) });

Or simply define as a Func to begin with:

Func<User,int> userFunc = x => x.Roles.Count();

Is this using Linq-to-Objects or something else? If you need to keep it as an Expression because the Expression gets converted into something else (like a SQL call), you can use LinqKit's AsExpandable like so:

public static Expression<Func<User,int>> RoleCount()
{
    return u => u.Roles.Count();
}

public static void DoStuff()
{
    var roleCounter = RoleCount();

    var query = users.AsExpandable()
                     .Select(u => roleCounter.Invoke(u));
}

Andere Tipps

We can create a Combine method that is able to take a selector for an object and then another selector that also takes the output of the first selector to produce a final result:

public static Expression<Func<TFirstParam, TResult>>
    Combine<TFirstParam, TIntermediate, TResult>(
    this Expression<Func<TFirstParam, TIntermediate>> first,
    Expression<Func<TFirstParam, TIntermediate, TResult>> second)
{
    var param = Expression.Parameter(typeof(TFirstParam), "param");

    var newFirst = first.Body.Replace(first.Parameters[0], param);
    var newSecond = second.Body.Replace(second.Parameters[0], param)
        .Replace(second.Parameters[1], newFirst);

    return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}

This uses the following helper method to replace all instance of one expression with another:

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

public static Expression Replace(this Expression expression,
    Expression searchEx, Expression replaceEx)
{
    return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}

Now we can write:

Expression<Func<User, int>> userExpression = x => x.Roles.Count()

var query = users.Select(userExpression.Combine((x, count) => 
    new OneMoreAnotherClass { AnotherProperty = count});

You can do this with a closure:

User x = z; // assign local value 
var countX = () => x.Roles.Count();

now this will work:

users.Select(x => new AnotherClass { RoleCount = countX() })
Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top