Tag Archives: expression

[Entity Framework] Utiliser Include avec des expressions lambda

Je travaille en ce moment sur un projet qui utilise Entity Framework 4. Bien que le lazy loading soit activé, j’utilise généralement la méthode ObjectQuery.Include pour charger les entités associées en une seule fois, de façon à éviter des appels supplémentaires à la base de données lors de l’accès à ces entités :

var query =
    from ord in db.Orders.Include("OrderDetails")
    where ord.Date >= DateTime.Today
    select ord;

Ou encore, pour inclure aussi le produit :

var query =
    from ord in db.Orders.Include("OrderDetails.Product")
    where ord.Date >= DateTime.Today
    select ord;

Il y a quelque chose qui m’ennuie avec cette méthode Include : le fait de devoir spécifier le chemin de la propriété sous forme de chaine de caractères. En effet cette approche présente deux inconvénients majeurs :

  • Elle comporte un risque d’erreur important : on a vite fait de faire une faute de frappe dans le chemin de la propriété, et puisque c’est une chaine de caractères, le compilateur ne remarque rien. On a donc une erreur à l’exécution alors que ça aurait pu être vérifié dès la compilation.
  • On ne profite plus de l’assistance de l’IDE : pas d’intellisense ni de refactoring. Si on renomme une propriété du modèle, le refactoring automatique ne prend pas en compte le contenu des chaines de caractères. Il faut donc aller modifier manuellement les appels à Include qui font référence à cette propriété, avec le risque non négligeable d’en oublier au passage…

Il serait donc plus pratique d’utiliser une expression lambda pour spécifier le chemin de la propriété à inclure. Le principe est connu, et fréquemment utilisé pour éviter de passer une chaine quand on veut spécifier le nom d’une propriété.

Le cas “de base”, dans lequel on ne charge qu’une propriété directement liée à la source, est assez simple à gérer, et on trouve des implémentations un peu partout sur le net. Il suffit d’utiliser une méthode qui extrait le nom de la propriété à partir de l’expression :

    public static class ObjectQueryExtensions
    {
        public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, Expression<Func<T, object>> selector)
        {
            string propertyName = GetPropertyName(selector);
            return query.Include(propertyName);
        }

        private static string GetPropertyName<T>(Expression<Func<T, object>> expression)
        {
            MemberExpression memberExpr = expression.Body as MemberExpression;
            if (memberExpr == null)
                throw new ArgumentException("Expression body must be a member expression");
            return memberExpr.Member.Name;
        }
    }

En utilisant cette méthode d’extension, on peut réécrire le code du premier exemple de la façon suivante :

var query =
    from ord in db.Orders.Include(o => o.OrderDetails)
    where ord.Date >= DateTime.Today
    select ord;

Ce code fonctionne, mais seulement pour les cas simples… dans le deuxième exemple, on veut aussi inclure la propriété OrderDetail.Product, et le code ci-dessus ne permet pas de gérer ce cas. En effet, l’expression qu’il faudrait écrire pour inclure la propriété Product serait du type o.OrderDetails.Select(od => od.Product), or la méthode GetPropertyName ne sait gérer que les propriétés, pas les appels de méthode…

Pour obtenir le chemin complet de la propriété à inclure, il faut parcourir tout l’arbre d’expression pour en extraire les propriétés. Bien que cela puisse paraitre assez complexe, il existe une classe qui peut nous y aider : ExpressionVisitor. Cette classe, introduite en .NET 4.0, implémente le design pattern Visiteur pour parcourir tous les noeuds de l’arbre. L’implémentation de base ne fait rien de particulier, elle se contente de visiter chaque noeud. Tout ce que nous avons à faire, c’est en hériter pour spécialiser certaines méthodes de façon à extraire les propriétés utilisées dans l’expression. On va donc redéfinir les méthodes suivantes :

  • VisitMember : c’est la méthode appelée pour visiter l’accès à une propriété ou à un champ
  • VisitMethodCall : la méthode appelée pour visiter les appels de méthode. Bien que ce cas ne nous intéresse pas directement a priori, on doit modifier son comportement dans le cas des opérateurs Linq : l’implémentation par défaut visite les paramètres dans l’ordre normal, mais pour les méthodes d’extension comme Select ou SelectMany, on doit visiter le premier paramètre (le paramètre this) en dernier, de façon à conserver l’ordre voulu pour le chemin de la propriété

Voici donc la nouvelle implémentation de la méthode d’extension Include :

    public static class ObjectQueryExtensions
    {
        public static ObjectQuery<T> Include<T>(this ObjectQuery<T> query, Expression<Func<T, object>> selector)
        {
            string path = new PropertyPathVisitor().GetPropertyPath(expression);
            return query.Include(path);
        }

        class PropertyPathVisitor : ExpressionVisitor
        {
            private Stack<string> _stack;

            public string GetPropertyPath(Expression expression)
            {
                _stack = new Stack<string>();
                Visit(expression);
                return _stack
                    .Aggregate(
                        new StringBuilder(),
                        (sb, name) =>
                            (sb.Length > 0 ? sb.Append(".") : sb).Append(name))
                    .ToString();
            }

            protected override Expression VisitMember(MemberExpression expression)
            {
                if (_stack != null)
                    _stack.Push(expression.Member.Name);
                return base.VisitMember(expression);
            }

            protected override Expression VisitMethodCall(MethodCallExpression expression)
            {
                if (IsLinqOperator(expression.Method))
                {
                    for (int i = 1; i < expression.Arguments.Count; i++)
                    {
                        Visit(expression.Arguments[i]);
                    }
                    Visit(expression.Arguments[0]);
                    return expression;
                }
                return base.VisitMethodCall(expression);
            }

            private static bool IsLinqOperator(MethodInfo method)
            {
                if (method.DeclaringType != typeof(Queryable) && method.DeclaringType != typeof(Enumerable))
                    return false;
                return Attribute.GetCustomAttribute(method, typeof(ExtensionAttribute)) != null;
            }
        }
    }

J’ai déjà parlé plus haut de la méthode VisitMethodCall, je ne reviens donc pas dessus. L’implémentation de VisitMember est très simple : on se contente d’ajouter le nom de la propriété sur une pile. Au fait, pourquoi une pile ? Parce que la visite de l’expression ne se déroule pas dans l’ordre auquel on pense intuitivement. Par exemple, dans une expression du type o.OrderDetails.Select(od => od.Product), le premier noeud examiné n’est pas o, mais l’appel à Select, car ce qui précède (o.OrderDetails) est en fait un paramètre de la méthode statique Select… Pour obtenir les propriétés dans l’ordre voulu, on les place donc sur une pile de façon à les relire ensuite dans l’ordre inverse.

La méthode GetPropertyPath est elle aussi assez facile à comprendre : elle initialise la pile, visite l’expression, et reconstitue le chemin à partir de la pile.

On peut donc maintenant réécrire le code du deuxième exemple de la façon suivante :

var query =
    from ord in db.Orders.Include(o => OrderDetails.Select(od => od.Product))
    where ord.Date >= DateTime.Today
    select ord;

Cette méthode fonctionne aussi pour des cas plus complexes. Ajoutons un peu de piment à notre exemple : une ou plusieurs remises peuvent être appliquées à chaque article commandé, et chaque remise est associée à une campagne promotionnelle. Si on veut inclure les remises et les campagnes associées dans les résultats de la requête, on peut écrire quelque chose comme ça :

var query =
    from ord in db.Orders.Include(o => OrderDetails.Select(od => od.Discounts.Select(d => d.Campaign)))
    where ord.Date >= DateTime.Today
    select ord;

Le résultat est le même que si on avait passé à Include le chemin “OrderDetails.Discounts.Campaign”. Comme les Select imbriqués réduisent la lisibilité du code, on peut écrire l’expression un peu différemment :

var query =
    from ord in db.Orders.Include(o => o.OrderDetails
                                        .SelectMany(od => od.Discounts)
                                        .Select(d => d.Campaign))
    where ord.Date >= DateTime.Today
    select ord;

Pour finir, deux remarques sur cette solution :

  • Une méthode d’extension similaire est inclue dans le Entity Framework Feature CTP4 (voir cet article pour plus de détails). Il est donc probable qu’elle finisse par être intégrée dans le Framework (peut-être dans un service pack pour .NET 4.0 ?)
  • Bien que cette solution cible Entity Framework 4.0, il est a priori possible de l’adapter à EF 3.5. La classe ExpressionVisitor n’est pas disponible en 3.5, mais le LINQKit de Joseph Albahari en fournit une implémentation. Je n’ai pas essayé, mais ça devrait fonctionner de la même façon…

Automatiser la vérification des null avec les expressions Linq

Le problème

Je suis sûr qu’il vous est déjà arrivé d’écrire ce genre de code :

X x = GetX();
string name = "Default";
if (xx != null && xx.Foo != null && xx.Foo.Bar != null && xx.Foo.Bar.Baz != null)
{
    name = xx.Foo.Bar.Baz.Name;
}

On veut juste obtenir name = xx.Foo.Bar.Baz.Name, mais on est obligé de tester chaque objet intermédiaire pour vérifier qu’il n’est pas nul, ce qui peut vite s’avérer pénible si la propriété voulue est profondément enfouie dans un graphe d’objets…

Une solution

Linq offre une fonctionnalité qui permet (entre autres) de régler ce problème : les expressions. Il est possible, à partir d’une expression lambda, d’obtenir son arbre syntaxique (ou AST : Abstract Syntax Tree), et de faire toutes sortes de manipulations sur cet arbre. On peut également générer dynamiquement un arbre syntaxique, et le compiler pour obtenir un delegate qu’on pourra ensuite exécuter.

Mais quel rapport avec le problème qui nous intéresse ? Eh bien tout simplement, nous allons pouvoir utiliser les expressions Linq pour analyser l’arbre syntaxique correspondant à l’accès à la propriété xx.Foo.Bar.Baz.Name, et réécrire cet arbre de façon à y ajouter des tests de nullité pour chaque objet intermédiaire.

Nous allons donc créer une méthode d’extension NullSafeEval, qui prendra en premier paramètre une expression lambda définissant comment accéder à la propriété voulue, et en deuxième paramètre la valeur par défaut à renvoyer si un objet nul est rencontré en cours de route.

Cette méthode va transformer l’expression xx.Foo.Bar.Baz.Name en ceci :

    (xx == null)
    ? defaultValue
    : (xx.Foo == null)
      ? defaultValue
      : (xx.Foo.Bar == null)
        ? defaultValue
        : (xx.Foo.Bar.Baz == null)
          ? defaultValue
          : xx.Foo.Bar.Baz.Name;

Voici l’implémentation de la méthode NullSafeEval :

        public static TResult NullSafeEval<TSource, TResult>(this TSource source, Expression<Func<TSource, TResult>> expression, TResult defaultValue)
        {
            var safeExp = Expression.Lambda<Func<TSource, TResult>>(
                NullSafeEvalWrapper(expression.Body, Expression.Constant(defaultValue)),
                expression.Parameters[0]);

            var safeDelegate = safeExp.Compile();
            return safeDelegate(source);
        }

        private static Expression NullSafeEvalWrapper(Expression expr, Expression defaultValue)
        {
            Expression obj;
            Expression safe = expr;

            while (!IsNullSafe(expr, out obj))
            {
                var isNull = Expression.Equal(obj, Expression.Constant(null));

                safe =
                    Expression.Condition
                    (
                        isNull,
                        defaultValue,
                        safe
                    );

                expr = obj;
            }
            return safe;
        }

        private static bool IsNullSafe(Expression expr, out Expression nullableObject)
        {
            nullableObject = null;

            if (expr is MemberExpression || expr is MethodCallExpression)
            {
                Expression obj;
                MemberExpression memberExpr = expr as MemberExpression;
                MethodCallExpression callExpr = expr as MethodCallExpression;

                if (memberExpr != null)
                {
                    // Static fields don't require an instance
                    FieldInfo field = memberExpr.Member as FieldInfo;
                    if (field != null && field.IsStatic)
                        return true;

                    // Static properties don't require an instance
                    PropertyInfo property = memberExpr.Member as PropertyInfo;
                    if (property != null)
                    {
                        MethodInfo getter = property.GetGetMethod();
                        if (getter != null && getter.IsStatic)
                            return true;
                    }
                    obj = memberExpr.Expression;
                }
                else
                {
                    // Static methods don't require an instance
                    if (callExpr.Method.IsStatic)
                        return true;

                    obj = callExpr.Object;
                }

                // Value types can't be null
                if (obj.Type.IsValueType)
                    return true;

                // Instance member access or instance method call is not safe
                nullableObject = obj;
                return false;
            }
            return true;
        }

En résumé, ce code remonte l’arbre de l’expression lambda, en encadrant chaque appel à une propriété ou méthode d’instance par une expression conditionnelle (condition ? valeur si vrai : valeur si faux).

Et voilà comment on utilise cette méthode :

string name = xx.NullSafeEval(x => x.Foo.Bar.Baz.Name, "Default");

C’est tout de même plus clair et plus concis que notre code initial 🙂

Notez que l’implémentation proposée gère non seulement les accès aux propriétés, mais également les appels de méthode, on pourrait donc avoir quelque chose comme ça :

string name = xx.NullSafeEval(x => x.Foo.GetBar(42).Baz.Name, "Default");

Les indexeurs ne sont pas encore gérés, mais pourraient être ajoutés sans grande difficulté ; je vous laisse le soin de le faire si vous en avez l’usage 😉

Limitations

Même si cette solution peut sembler très intéressante au premier abord, lisez la suite avant de vous précipiter pour intégrer ce code dans des programmes réels…

  • Tout d’abord, le code proposé est avant tout un “proof of concept” et n’a pas subi de tests approfondis, sa fiabilité peut donc laisser à désirer.
  • Ensuite, il ne faut pas perdre de vue que la génération dynamique de code à partir d’une expression est très pénalisante pour les performances…

    Une piste possible pour limiter ce problème serait de mettre en cache les delegates obtenus pour chaque expression, de façon à ne pas les regénérer inutilement. Malheureusement, il n’y a pas (à ma connaissance) de moyen simple de comparer deux expressions Linq, ce qui complique sensiblement l’implémentation de ce cache…

  • D’autre part, vous aurez peut-être remarqué que les propriétés et méthodes intermédiaires de l’expression sont évaluées plusieurs fois ; non seulement cela peut avoir un impact non négligeable sur les performances, mais surtout, cela peut causer des effets de bord aux conséquences difficilement prévisibles…

    Une solution possible serait de réécrire l’expression de la façon suivante :

    Foo foo = null;
    Bar bar = null;
    Baz baz = null;
    var name =
        (x == null)
        ? defaultValue
        : ((foo = x.Foo) == null)
          ? defaultValue
          : ((bar = foo.Bar) == null)
            ? defaultValue
            : ((baz = bar.Baz) == null)
              ? defaultValue
              : baz.Name;
    

    Malheureusement, ce n’est pas possible en .NET 3.5 : en effet, cette version ne supporte que des expressions simples, il n’est donc pas possible de déclarer des variables ou de leur affecter une valeur, ni d’écrire plusieurs instructions distinctes. En revanche, en .NET 4.0, le support des expressions Linq a été grandement amélioré, et il est possible de générer ce type de code. J’ai commencé à transformer le code de NullSafeEval pour arriver à ce résultat, mais ça s’avère beaucoup plus complexe que prévu… je publierai la nouvelle méthode si j’arrive à quelque chose de concluant 😉

Au final, je ne recommande donc pas d’utiliser cette technique dans du code réel, en tous cas en l’état actuel. Cela donne cependant un aperçu intéressant des possibilités des expressions Linq. Sachez qu’elle sont également utilisées, entre autres :

  • Pour la génération de requêtes SQL dans des ORM comme Linq to SQL ou Entity Framework
  • Pour construire dynamiquement des prédicats complexes, comme dans la classe PredicateBuilder de Joseph Albahari
  • Pour implementer la “réflexion statique” dont on a beaucoup parlé sur les blogs depuis quelques temps