Extrait un morceau de lambda pour l'expression dans select

c# expression expression-trees lambda linq-to-entities

Question

Je peux extraire et réutiliser toute l'expression comme ça:

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

mais est-il possible d'extraire un peu comment seulement x.Roles.Count() partie et l'utiliser dans le contexte de l' Expression<Func<User, T>>

Ce que j'essaie de réaliser est de réutiliser cette partie sur différents éléments sélectionnés, comme:

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

et

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

Ainsi, ce que roleCountPartOfExpression est censé être dans ce cas devrait être pris en charge dans LINQ to Entities (créer une méthode où je passerai à User, où return user.Roles.Count() ne fonctionnera pas), je ne peux pas non plus créer une expression pour select Expression<Func<User, AnotherClass>> car dans ce cas, je Expression<Func<User, AnotherClass>> créer Expression<Func<User, OneMoreAnotherClass>> , ce qui rompra mon objectif de "réutilisabilité".

Réponse acceptée

Si vous compilez vers un Func<User, int> , vous pouvez l'appeler dans d'autres domaines, comme suit:

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

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

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

Ou simplement définir comme un Func pour commencer:

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

Est-ce que cela utilise Linq-to-Objects ou autre chose? Si vous avez besoin de le garder comme une Expression parce que l' Expression se transforme en quelque chose d' autre (comme un appel SQL), vous pouvez utiliser LinqKit de AsExpandable comme ceci:

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

Réponse populaire

Nous pouvons créer une méthode Combine capable de prendre un sélecteur pour un objet, puis un autre sélecteur qui prend également la sortie du premier sélecteur pour produire un résultat final:

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

Ceci utilise la méthode d'assistance suivante pour remplacer toutes les occurrences d'une expression par une autre:

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

Maintenant nous pouvons écrire:

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

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


Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi
Sous licence: CC-BY-SA with attribution
Non affilié à Stack Overflow
Est-ce KB légal? Oui, apprenez pourquoi