Geek quizz II: comment stopper l’imbrication des expressions Linq to Sql ?


Soit la méthode de ma couche business suivante:


IQueryable<Customer> GetCustomers()
{
return
from c
in dc.Customers
select c;
}

J’aimerai fournir à ma couche présentation l’accès à cette méthode afin d’offrir la possibilité de lister le clients venant de la base.
Juste là, un besoin tout à fait légitime :p.


le problème est que ma couche de présentation va pouvoir écrire ceci:


var q =
from c
in GetCustomers()
where (c.City == London)
select c;

Et je ne veux pas de cette possibilité car cette écriture va générer une nouvelle requête sql et je ne veux pas que ma couche présentation en soit capable. Je veux bien de la notion de filtre (where) mais en mémoire uniquement sans que cela modifie la requête sql que seule la couche business doit être capable de définir.


Comment faire ?


Mitsu


[Update] : Délégation d’implémentation d’interface: réponse au Quizz
[Update] Quizz suivant: Geek Quizz III: subtilités dans les conversions

Comments (15)

  1. Florent says:

    J’ai proposé:

    Créér une liste de Customers intermédiaire pour faire une copie en mémoire de la requête issue de l’objet métier.

    List<Customer> GetCustomer() { return q.ToList(); }

    Pour pouvoir ensuite la manipuler et la filtrer librement avec un .FindAll et un jolie predicate.

    Mais tu m’as dit que c’était pas la meilleure solution :)

  2. Mitsu says:

    En effet, peupler une collection une fois pour toute en parcourant l’énumération nous fait quitter le monde de Linq to Sql pour se retrouver avec une simple collection mémoire et toute l’artillerie de Linq to Objects. C’est donc UNE solution.

    Cependant, retarder l’exécution de la requête à travers toutes les couches de l’architecture reste très tentant et bien plus efficace au niveau des objects alloués.

    Donc en effet, on peut faire mieux.

  3. private IQueryable<Customer> GetCustomersQueryable()

    {

       return

           from c in dc.Customers

           select c;

    }

    public IEnumerable<Customer> GetCustomers()

    {

       return GetCustomersQueryable().AsEnumerable();

    }

  4. Florent, je repensais à ton histoire de liste.

    Tu auras un autre problème avec ta liste. Si les données de ta BD sont modifiées, ta liste ne sera plus à jour alors qu’avec une Query si.

  5. Simon says:

    Attention, y’a un piège Matthieu ^^

    C’est presque ca.

  6. Mitsu says:

    En effet Matthieu,

    Ton code fonctionne bien car désormais les extensions sur GetCustomers() seront bien en mémoire mais cette couche est mal isolée car rien ne m’empêche de recaster en IQueryable.

    var q = GetCustomer() as IQueryable<Customer>;

    q.Where(…); //sql à nouveau.

    La solution de Florent, isole bien Linq to Sql car List<Customer> n’est plus un IQueryable.

    La solution est à mi-chemin

  7. ok et comme ça ?

    private IQueryable<Customer> GetCustomersQueryable()

    {

      return

          from c in dc.Customers

          select c;

    }

    public IEnumerable<Customer> GetCustomers()

    {

     foreach (Customer c in GetCustomersQueryable())

       yield return c;

    }

  8. Mitsu says:

    Good point !!

    C’est en effet la solution.

    IEnumerable<Customer> GetCustomers()

    {

       var q =

           from c in dc.Customers

           select c;

       return

           from c in q.AsEnumerable()

           select c;

    }

    Le fait de réécrire l’itérateur génère une nouvelle classe qui implémente uniqueme IEnumerable<T> sans être IQueryable<T>. Le code est légèrement plus conséquent puis qu’il y a un itérateur supplémentaire mais nous conservons les avantages de l’exécution tardive de la requête sans créer de collection intermédiaire.

    Qui veut encore des Quizz ? :p

  9. Simon says:

    Ouais, des quizz, je veux des quizz bien tordus ! ^^

  10. Pas mal: j’était partit sur la même première idée que Matthieu. Comme quoi, faut faire attention :)

    Allez Mitsu, des quizz, des quizz !! :)

  11. Les quizz sont super intéressants car ils permettent de confronter les différentes idées personnes qui y participent et, de plus, ils permettent de progresser avec des questions dont on ignore la réponse. Donc oui 100% pour que tu continues dans ce sens. :)

  12. Petite question amusante sur C# 3: Le framework 3.5 apporte la méthode AsEnumerable. Quel est donc l’intérêt

  13. Salut Mitsu,

    Des heures de discussion avec Daniel G. sur ce sujet… Faut-il ou ne faut-il pas isoler les deux mondes ? Si la couche de présentation fait du Linq, soit elle se retrouve à construire des requêtes SQL sans le savoir, soit elle se retrouve à filtrer des énumérations, c’est à dire à refaire côté présentation du travail que devrait faire la BDD, avec tout ce que ça veut dire en terme de perfs…

    Ton astuce de reconstruire un itérateur au lieu de tout monter en mémoire est déjà un grand progrès. Mais dans ton exemple, mettons que le premier client habitant à Londres soit le 1 000 001 ème de la table ; en isolant on perd l’intelligence de la BDD, la possibilité d’utiliser un index de la BDD, on se retrouve à parcourir un million d’enregistrement avec tout ce que ça veut dire comme transfert entre la BDD et le serveur de présentation. Pour moi c’est bien plus nuisible qu’une brèche dans la sacro-sainte isolation entre le M et le V du MVC.

    Alors évidemment, la solution c’est de n’exposer à la couche de présentation que des énumérateurs "petits", c’est à dire prémacher le travail dans la couche métier de manière à ce qu’en aucun cas la couche de présentation ne se retrouve à devoir itérer sur de gros jeux de résultats. Mais au final je suis super admiratif de ce que Microsoft a fait avec Linq (à des années lumière de ce qu’on trouve côté Java, oui c’est moi qui dit ça) et je trouve dommage de se priver de ses capacités.

    Sur le projet sur lequel nous travaillons actuellement avec Daniel, le client impose l’utilisation de procédures stockées pour accéder à la BDD, ce qui tranche le débat  (interdit de générer des requêtes à la volée), mais dans un monde plus libre, à tout prendre, je préfèrerais ne pas isoler et manipuler directement l’ObjectQuery, avec toute la puissance que cela induit. Ou alors il faut interdire carrément l’utilisation de Linq dans la couche de présentation pour éviter le genre de problème indiqué ci-dessus :)

    @+

    Nicolas