web-dev-qa-db-fra.com

Pourquoi voudrais-je utiliser un ExpressionVisitor?

Je sais de l'article de MSDN sur Comment: modifier les arbres d'expression ce qu'un ExpressionVisitor est censé faire. Il devrait modifier les expressions.

Leur exemple est cependant assez irréaliste, donc je me demandais pourquoi en aurais-je besoin? Pourriez-vous nommer des cas concrets où il serait logique de modifier un arbre d'expression? Ou pourquoi doit-il être modifié du tout? De quoi à quoi?

Il a également de nombreuses surcharges pour visiter toutes sortes d'expressions. Comment puis-je savoir quand je dois en utiliser un et que dois-je retourner? J'ai vu des gens utiliser VisitParameter et renvoyer base.VisitParameter(node) d'autre part retournaient Expression.Parameter(..).

24
t3chb0t

Pourriez-vous nommer des cas concrets où il serait logique de modifier un arbre d'expression?

À strictement parler, nous ne modifions jamais un arbre d'expression, car ils sont immuables (vu de l'extérieur, au moins, il n'y a aucune promesse qu'il ne mémorise pas de valeurs en interne ou autrement n'a pas d'état privé mutable). C'est précisément parce qu'ils sont immuables et donc nous ne pouvons pas simplement changer un nœud que le modèle de visiteur a beaucoup de sens si nous voulons créer un nouvel arbre d'expression basé sur celui que nous avons mais différent d'une manière particulière (le chose la plus proche que nous devons modifier un objet immuable).

Nous pouvons en trouver quelques-uns dans Linq lui-même.

À bien des égards, le fournisseur Linq le plus simple est le fournisseur linq-to-objects qui fonctionne sur les objets énumérables en mémoire.

Lorsqu'il reçoit des énumérables directement sous forme d'objets IEnumerable<T>, C'est assez simple dans la mesure où la plupart des programmeurs peuvent écrire une version non optimisée de la plupart des méthodes assez rapidement. Par exemple. Where est juste:

foreach (T item in source)
  if (pred(item))
    yield return item;

Etc. Mais qu'en est-il de EnumerableQueryable implémentant les versions IQueryable<T>? Puisque le EnumerableQueryable encapsule un IEnumerable<T> Nous pourrions faire l'opération souhaitée sur le ou les objets énumérables impliqués, mais nous avons une expression décrivant cette opération en termes de IQueryable<T> Et autres des expressions pour les sélecteurs, les prédicats, etc., où nous avons besoin d'une description de cette opération en termes de IEnumerable<T> et des délégués pour les sélecteurs, les prédicats, etc.

System.Linq.EnumerableRewriter est une implémentation de ExpressionVisitor fait exactement une telle réécriture, et le résultat peut alors simplement être compilé et exécuté.

Dans System.Linq.Expressions Lui-même, il existe quelques implémentations de ExpressionVisitor à des fins différentes. Un exemple est que la forme interprète de compilation ne peut pas gérer directement les variables hissées dans les expressions citées, elle utilise donc un visiteur pour la réécrire en travaillant sur des index dans un dictionnaire.

En plus de produire une autre expression, un ExpressionVisitor peut produire un autre résultat. Encore une fois, System.Linq.Expressions A lui-même des exemples internes, avec des chaînes de débogage et ToString() pour de nombreux types d'expression fonctionnant en visitant l'expression en question.

Cela peut (bien que cela ne soit pas obligatoire) être l'approche utilisée par un fournisseur linq interrogeant la base de données pour transformer une expression en requête SQL.

Comment puis-je savoir quand je dois utiliser l'un d'eux et que dois-je retourner?

L'implémentation par défaut de ces méthodes:

  1. Si l'expression ne peut avoir aucune expression enfant (par exemple, le résultat de Expression.Constant()), elle renverra à nouveau le nœud.
  2. Sinon, visitez toutes les expressions enfants, puis appelez Update sur l'expression en question, en renvoyant les résultats. Update à son tour renverra soit un nouveau nœud du même type avec les nouveaux enfants, soit renverra le même nœud si les enfants n'ont pas été modifiés.

En tant que tel, si vous ne savez pas que vous devez explicitement opérer sur un nœud pour quelque raison que ce soit, alors vous n'avez probablement pas besoin de le changer. Cela signifie également que Update est un moyen pratique d'obtenir une nouvelle version d'un nœud pour une modification partielle. Mais ce que signifie "quels que soient vos objectifs" dépend bien sûr du cas d'utilisation. Les cas les plus courants vont probablement à un extrême ou à l'autre, avec seulement un ou deux types d'expression nécessitant un remplacement, ou tous ou presque tous en ayant besoin.

(Une mise en garde est si vous examinez les enfants de ces nœuds qui ont des enfants dans un ReadOnlyCollection tel que BlockExpression pour ses étapes et ses variables ou TryExpression pour ses blocs catch , et vous ne changerez que parfois ces enfants, alors si vous n'avez pas changé, vous êtes mieux de vérifier cela vous-même comme un défaut [récemment corrigé, mais pas encore dans une version publiée] signifie que si vous passez les mêmes enfants à Update dans une collection différente de l'original ReadOnlyCollection puis une nouvelle expression est créée inutilement qui a des effets plus haut dans l'arborescence. Ceci est normalement inoffensif, mais il gaspille du temps et de la mémoire).

8
Jon Hanna

Il y avait un problème où, dans la base de données, nous avions des champs contenant 0 ou 1 (numérique), et nous voulions utiliser des bools sur l'application.

La solution était de créer un objet "Flag", qui contenait le 0 ou 1 et avait une conversion en booléen. Nous l'avons utilisé comme un booléen dans toute l'application, mais lorsque nous l'avons utilisé dans une clause .Where (), EntityFramework s'est plaint de ne pas pouvoir appeler la méthode de conversion.

Nous avons donc utilisé une expression visiteur pour modifier tous les accès aux propriétés comme .Where (x => x.Property) en .Where (x => x.Property.Value == 1) juste avant d'envoyer l'arborescence à EF.

10
Lucas Corsaletti

ExpressionVisitor active le modèle visiteur pour Expression.

Conceptuellement, le problème est que lorsque vous naviguez dans une arborescence Expression, tout ce que vous savez, c'est qu'un nœud donné est un Expression, mais vous ne savez pas précisément quel type de Expression. Ce modèle vous permet de savoir avec quel type de Expression vous travaillez et de spécifier la gestion spécifique au type pour différents types.

Lorsque vous avez un Expression, vous pouvez simplement appeler .Modify. Le Expression connaît son propre type, donc il rappellera le override approprié.

En regardant exemple MSDN que vous avez lié :

public class AndAlsoModifier : ExpressionVisitor  
{  
    public Expression Modify(Expression expression)  
    {  
        return Visit(expression);  
    }  

    protected override Expression VisitBinary(BinaryExpression b)  
    {  
        if (b.NodeType == ExpressionType.AndAlso)  
        {  
            Expression left = this.Visit(b.Left);  
            Expression right = this.Visit(b.Right);  

            // Make this binary expression an OrElse operation instead of an AndAlso operation.  
            return Expression.MakeBinary(ExpressionType.OrElse, left, right, b.IsLiftedToNull, b.Method);  
        }  

        return base.VisitBinary(b);  
    }  
}

Dans cet exemple, si le Expression se trouve être un BinaryExpression, il rappellera VisitBinary(BinaryExpression b) donné dans l'exemple. Maintenant, vous pouvez gérer ce BinaryExpression en sachant qu'il s'agit d'un BinaryExpression. Vous pouvez également spécifier d'autres méthodes override qui gèrent d'autres types de Expression.

Il convient de noter que, comme il s'agit d'une astuce de résolution surchargée, les Expression visités rappelleront la méthode la mieux adaptée. Donc, s'il existe différents types de BinaryExpression, vous pouvez écrire un override pour un sous-type spécifique; si un autre sous-type rappelle, il utilisera simplement la gestion par défaut BinaryExpression.

En bref, ce modèle vous permet de naviguer dans un arbre Expression en sachant avec quel type de Expression vous travaillez.

5
Nat

Un exemple spécifique du monde réel que je viens de rencontrer s'est produit lors du passage à EF Core et de la migration de Sql Server (MS Specific) vers SqlLite (indépendant de la plate-forme).

La logique métier existante tournait autour d'une interface de couche intermédiaire/couche de service qui supposait que la recherche en texte intégral (FTS) se produisait automatiquement comme en arrière-plan, ce qu'elle fait avec SQL Server. Les requêtes liées à la recherche ont été transmises à ce niveau via Expressions et FTS sur un magasin Sql Server ne nécessitant pas d'entités spécifiques FTS supplémentaires.

Je ne voulais rien changer à cela, mais avec SqlLite, vous devez cibler une table virtuelle spécifique pour une recherche en texte intégral, ce qui aurait à son tour signifié changer tous les appels de niveau intermédiaire pour cibler à nouveau les tables/entités FTS, puis rejoindre les aux tables d'entité commerciale pour obtenir un jeu de résultats similaire.

Mais en sous-classant ExpressionVisitor, j'ai pu intercepter les appels dans la couche DAL et simplement réécrire l'expression entrante (ou plus précisément certaines des expressions binaires dans l'expression de recherche globale) pour gérer spécifiquement les exigences SqlLites FTS.

Cela signifie que la spécialisation de la couche de données vers le magasin de données s'est produite au sein d'une seule classe qui a été appelée à partir d'un seul endroit au sein d'une classe de base du référentiel. Aucun autre aspect de l'application n'a dû être modifié pour prendre en charge FTS via EFCore et aucune entité liée à SqlLite FTS ne pouvait être contenue dans un seul assemblage enfichable.

Ainsi, ExpressionVisitor est vraiment très utile, surtout lorsqu'il est combiné avec toute la notion de pouvoir contourner les arbres d'expression sous forme de données via diverses formes d'IPC.

2
rism