Arbres d'expression: nombre filtré sur la propriété de navigation

asp.net-mvc c# expression-trees linq

Question

Je crée un générateur de rapport dynamique qui permet à l'utilisateur de sélectionner des champs dans des classes prédéfinies (qui mappent vers des tables de base de données via Entity Framework) comme filtres pour leurs données. Pour construire ma requête LINQ to Entities, j'utilise des arbres d'expression en raison de la nature dynamique des requêtes. Je le fais fonctionner pour à peu près tous les scénarios non personnalisés, mais j'ai beaucoup de mal à le faire fonctionner pour quelques scénarios personnalisés.

Une version abrégée de mes modèles pour l'une de mes requêtes personnalisées se présente comme suit:

public class Attendee {
    public int ID { get; set; }
    public DateTime? CancelledOn { get; set; }

    [ForeignKey("Event")]
    public int Event_ID { get; set; }
    public virtual Event Event { get; set; }        
}

public class Event {
    public int ID { get; set; }
    public virtual ICollection<Attendee> Attendees { get; set; }        
}

Un exemple de requête qu'un utilisateur souhaite exécuter consiste à filtrer pour afficher uniquement les événements comportant plus de 10 participants non annulés. Si j’écrivais ceci dans une requête IQueryable normale, j’écrirais ceci comme suit:

db.Event.Where(s => s.Attendees.Count(a => a.CancelledOn == null) > 10);

Avec le framework Expression Tree que j'ai configuré, je peux déjà gérer la partie "> 10", mais je ne vois pas comment générer dynamiquement la partie "s.Attendees.Count (a => a.CancelledOn == null)". J'ai lu des articles SO sur la manière de compter ou de totaliser une propriété de niveau supérieur, mais je n'ai pas été en mesure de modifier l'une de ces solutions pour utiliser une propriété de navigation filtrée. Exemple: Dynamic LINQ, fonction Select, fonctionne sur Enumerable, mais pas Queryable

La capture d'écran ci-dessous est un exemple de filtre différent construit avec Expression Trees afin que vous puissiez voir un exemple de ce que j'ai travaillé. "pe" est l'expression ParameterExpression du type transmis, "Event". "expression" est ce que j'essaie de créer et d'évaluer. http://grab.by/RoYm

La requête LINQ en cours d’exécution est la suivante:

db.Event.Where(s=> s.StartDate >= '1/1/2016 12:00 am')

Toute aide ou orientation à ce sujet serait grandement appréciée. Veuillez me faire savoir si je dois inclure d’autres extraits de code.

Réponse acceptée

Vous ne savez pas quels sont les paramètres d'entrée pour la méthode que vous recherchez, mais ce qui suit devrait vous donner un point de départ. La partie essentielle consiste à créer un appel de méthode à la méthode Enumerable.Count(predicate) .

static Expression<Func<TSource, bool>> MakeCountPredicate<TSource>(string collectionName, string itemName, ExpressionType itemComparison, string itemValue, ExpressionType countComparison, int countValue)
{
    var source = Expression.Parameter(typeof(TSource), "s");
    var collection = Expression.Property(source, collectionName);
    var itemType = collection.Type.GetInterfaces()
        .Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .GetGenericArguments()[0];
    var item = Expression.Parameter(itemType, "e");
    var itemProperty = Expression.Property(item, itemName);
    var itemPredicate = Expression.Lambda(
        Expression.MakeBinary(itemComparison, itemProperty, Expression.Constant(
            string.IsNullOrEmpty(itemValue) || itemValue.Equals("null", StringComparison.OrdinalIgnoreCase) ? null :
            Convert.ChangeType(itemValue, itemProperty.Type))),
        item);
    var itemCount = Expression.Call(
        typeof(Enumerable), "Count", new[] { itemType },
        collection, itemPredicate);
    var predicate = Expression.Lambda<Func<TSource, bool>>(
        Expression.MakeBinary(countComparison, itemCount, Expression.Constant(countValue)),
        source);
    return predicate;
}

donc l'expression de prédicat d'échantillon

Expression<Func<Event, bool>> predicate =
    s => s.Attendees.Count(a => a.CancelledOn == null) > 10

peut être construit dynamiquement comme ça

var predicate = MakeCountPredicate<Event>("Attendees", 
    "CancelledOn", ExpressionType.Equal, "null", ExpressionType.GreaterThan, 10);

Réponse populaire

Afin de générer l’arbre d’ Expression pour Count , vous devez générer un Call à la Method correspondante.

Voici un premier brouillon du code ...

Expression callExpr = Expression.Call(
    Expression.Constant(s.Attendees), 
    typeof(ICollection<Attendee>).GetMethod("get_Count")); // + 2 Arguments

Bien sûr, vous devez élaborer davantage et le fusionner dans votre programme principal.

Un exemple d'utilisation de base serait

// Print out the expression.
Debug.WriteLine(callExpr.ToString());

// The following statement first creates an expression tree,
// then compiles it, and then executes it.  
Debug.WriteLine(Expression.Lambda<Func<int>>(callExpr).Compile()());

Enfin, le comte Expression (NodeType Call ) devra contenir 2 arguments ( en tant que troisième paramètre, non illustré ci - dessus):

  • la collection
  • Lambda pour a => a.CancelledOn == null


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