Exposer les POCO Entity Framework 4 via un service WCF, résolution du problème de référence circulaire : 3ème partie#

Le problème de référence circulaire n’est pas propre à Entity Framework mais plutôt un problème de sérialisation WCF, dans cet article nous allons expliquer le problème et proposer une solution.

Soit le modèle edmx suivant :

image

Ce modèle a été très bien conçu et reflète bien la réalité du métier, par contre pour le développeur ce n’est pas vraiment la joie parce que s’il a envie de savoir à quel StudentGroup appartient un étudiant (Person_Student) il devra passer par 4 tables alors que c’est censé être l’opération métier la plus élémentaire. Passer par ces 4 tables est non seulement pénible pour le développeur mais en plus catastrophique au niveau perf pour au final une opération très simple. Après une discussion houleuse entre le Développeur et le DBA, le DBA accepte (une fois n’est pas coutume:)) d’ajouter un raccourci matérialisé en une table de lien entre Student et StudentGroup permettant de déterminer directement l’appartenance d’un étudiant à un groupe d’étudiants. Voici ce qu’est devenu le modèle :

image

Si le DBA était retissant au rajout de ce raccourci c’est à cause de ce cycle (entouré en rouge) introduit au sein du modèle. Ce cycle est sans conteste une mauvaise pratique en design mais dans quelques cas ça s’avère bien pratique.

Tout content (juste parce qu’il a eu gain de cause:)), le développeur se met à développer son service WCF en créant une méthode qui renvoie un étudiant en embarquant quelques données liées. Voici le corps de la méthode :

public Person_Student GetById(int id) 
{
       SuiviScolaireEntities context = new SuiviScolaireEntities();
       return context.Person_Student.Include("Pass")
                                  .Include("Pass.Exam")
                                  .Include("Pass.Exam.Teach")
                                  .Where(s => s.PersonId == id)
                                  .FirstOrDefault<Person_Student>();               
}

Il crée ensuite la partie client pour appeler le service et en exécutant il obtient l’erreur suivante :

image

Bon je vous l’accorde ce n’est pas très explicite comme erreur, pour y voir plus clair nous allons activer la trace WCF, voici la vrai erreur qui se cache derrière ce message :

There was an error while trying to serialize parameter http://tempuri.org/:GetStudentByIdResult. The InnerException message was 'Object graph for type 'xxxxxxx' contains cycles and cannot be serialized if reference tracking is disabled.'.  Please see InnerException for more details.

L’erreur dit que le modèle comporte un cycle et qu’il ne peut être sérialisé via WCF. Le développeur la ramène moins d’un coup mais se dit qu’il faut absolument résoudre ce problème pour ne pas avoir l’air con et que le DBA lui dise : je t’avais dit que ce n’est pas bien!

Analyse du problème

Le problème est que le DataContractSerializer va vouloir sérialiser explicitement les objets à renvoyer au client au lieu de travailler avec des références, supposons que l’on ai ce modèle :

public class Person
{
    public int Id { get; set; }

    public int FirstName { get; set; }

    public int LastName { get; set; }

    public List<Person> Enfants { get; set; }
}

Et que j’ai une List<Person> dans laquelle j’ai inséré des données de la manière suivante :

Person pEnfant1 = new Person { Id = 1, FirstName = "Thierry", LastName = "BOB" }; //Enfant
Person pEnfant2 = new Person { Id = 2, FirstName = "Marcus", LastName = "TIBH" }; //Enfant
Person pEnfant3 = new Person { Id = 3, FirstName = "Adrien", LastName = "MOLBE" }; //Enfant
Person pEnfant4 = new Person { Id = 4, FirstName = "Yam", LastName = "BOB" }; //Enfant
Person pParent1 = new Person
{
    Id = 5,
    FirstName = "Julien",
    LastName = "BOB",
    Enfants = new List<Person> { pEnfant1, pEnfant4 }
}; //Parent

Person pParent2 = new Person
{
    Id = 6,
    FirstName = "Gilian",
    LastName = "TIBH",
    Enfants = new List<Person> { pEnfant2 }
}; //Parent

Par défaut, le DataContractSerializer instancié par WCF sérialise les données de la manière suivante :

<ArrayOfPerson xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Thierry</FirstName>
    <Id>1</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Marcus</FirstName>
    <Id>2</Id>
    <LastName>TIBH</LastName>
  </Person>
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Adrien</FirstName>
    <Id>3</Id>
    <LastName>MOLBE</LastName>
  </Person>
  <Person>
    <Enfants i:nil="true"/>
    <FirstName>Yam</FirstName>
    <Id>4</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person>
    <Enfants>
      <Person>
        <Enfants i:nil="true"/>
        <FirstName>Thierry</FirstName>
        <Id>1</Id>
        <LastName>BOB</LastName>
      </Person>
      <Person>
        <Enfants i:nil="true"/>
        <FirstName>Yam</FirstName>
        <Id>4</Id>
        <LastName>BOB</LastName>
      </Person>
    </Enfants>
    <FirstName>Julien</FirstName>
    <Id>5</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person>
    <Enfants>
      <Person>
        <Enfants i:nil="true"/>
        <FirstName>Marcus</FirstName>
        <Id>2</Id>
        <LastName>TIBH</LastName>
      </Person>
    </Enfants>
    <FirstName>Gilian</FirstName>
    <Id>6</Id>
    <LastName>TIBH</LastName>
  </Person>
</ArrayOfPerson>

Remarquez dans ce flux XML généré par le DataContractSerializer que les personnes peuvent se répétées plusieurs fois dans le flux et que leur description entière est reprise à chaque fois qu’elles apparaissent. Marcus TIBH apparaît dans le flux 2 fois : Une fois comme “Person” sans enfants et ses informations sont reprises complètement en tant que "Enfant” de Gilian TIBH.

En effet, par défaut, WCF instancie le DataContractSerializer avec la propriété preserveObjectReferences = false. Cette propriété permet de spécifier de ne pas préserver les références d’objets lors de la sérialisation et du coup répéter à chaque fois entièrement les données.

WCF n’autorise pas les références circulaires avec ce mode de sérialisation et l’attribut preserveObjectReferences = false.

1ère Solution

La première solution est décrite dans le blog de sowmy et qui consiste à modifier le fonctionnement par défaut de WCF en instanciant le DataContractSerializer avec le preserveObjectReferences = true. Ceci est possible en héritant de DataContractSerializerOperationBehavior et en surchargeant sa méthode CreateSerializer qui renvoie le Serializer au moteur WCF. Nous pourrons par la suite faire de notre classe un attribut avec lequel nous allons décorer toutes les méthodes qui vont avoir besoin de ce mode de fonctionnement.

2ème solution

La deuxième méthode consiste à définir la propriété IsReference à True sur les classes qui vont être sérialisés de cette manière :

[DataContract(IsReference=true)]
public class Person
{
    [DataMember]
    public int Id { get; set; }

    [DataMember]
    public string FirstName { get; set; }

    [DataMember]
    public string LastName { get; set; }

    [DataMember]
    public List<Person> Enfants { get; set; }
}

De cette manière le DataContractSerializer va travailler avec les références à chaque fois qu’il s’agit de sérialiser un objet du type de cette classe.

Pour ces 2 méthodes, voici le XML résultant :

<ArrayOfPerson xmlns="http://schemas.datacontract.org/2004/07/ConsoleApplication1" xmlns:i="http://www.w3.org/2001/XMLSchema-instance">
  <Person z:Id="i1" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Thierry</FirstName>
    <Id>1</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person z:Id="i2" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Marcus</FirstName>
    <Id>2</Id>
    <LastName>TIBH</LastName>
  </Person>
  <Person z:Id="i3" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Adrien</FirstName>
    <Id>3</Id>
    <LastName>MOLBE</LastName>
  </Person>
  <Person z:Id="i4" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants i:nil="true"/>
    <FirstName>Yam</FirstName>
    <Id>4</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person z:Id="i5" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants>
      <Person z:Ref="i1"/>
      <Person z:Ref="i4"/>
    </Enfants>
    <FirstName>Julien</FirstName>
    <Id>5</Id>
    <LastName>BOB</LastName>
  </Person>
  <Person z:Id="i6" xmlns:z="http://schemas.microsoft.com/2003/10/Serialization/">
    <Enfants>
      <Person z:Ref="i2"/>
    </Enfants>
    <FirstName>Gilian</FirstName>
    <Id>6</Id>
    <LastName>TIBH</LastName>
  </Person>
</ArrayOfPerson>

Vous avez certainement remarqué les attributs z:Id et z:ref qui permettent de ne plus reprendre toutes les données d’une instance et de la référencer via son Id.

Enjoy ;)

Monday, March 15, 2010 8:13:00 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Exposer les POCO Entity Framework via un service WCF : 2ème partie#

Dans la première partie de cet article nous avons vu qu’Entity Framework 4 génère des Proxys qui héritent de nos classes POCO afin de leur ajouter des facultés de “Change Tracking” et de “Lazy loading” et nous renvoyait ces proxys. Nous avons vu que ces proxys posaient un problème lorsque nous avons voulu les sérialiser pour les exposer via un service WCF et nous avons contourné ce problème en mappant ce proxy en notre type original pendant la phase de sérialisation, de cette manière nous travaillons avec des POCO en bénéficiant de tous les apports d’Entity Framework comme le “Tracking” ou encore le “Lazy Loading” qui ne sont sincèrement pas un luxe.

Cette idée est néanmoins un “Work around” et j’ai tout de suite vu ces limites lorsque j’ai essayé de renvoyer un objet avec des entités liées => retour à la case départ problème de dé sérialisation … Il existe un autre moyen de travailler avec Entity Framework et des POCO que nous allons illustrer dans cet article.

En effet, nous pouvons choisir de dire au context que je ne veux pas qu’il génère des proxys de cette manière

context.ContextOptions.ProxyCreationEnabled = false;

Mais dans ce cas il faut assumer ces responsabilités :) Entity Framework ne fait plus de Tracking de changement sur les entités ni de Lazy Loading, les entités doivent donc accomplir ces fonctionnalités toutes seules, et c’est qui qui s’y colle? Bien évidemment ce n’est pas nous :). Je ne sais pas si vous vous en souvenez mais nous avons parlé dans cet article d’un Template T4 fournit par l’équipe ADO.NET qui génère les entités POCO à partir du modèle edmx, ce template nous génère avec nos POCO la partie qui s’occupe du Tracking de changements et les entités deviennent alors des STE : Self Tracking Entities, c’est à dire qu’elles embarquent la logique de Tracking. Et qu’en est il du lazy loading? Là par contre, si le lazy loading est un point crucial pour vous alors cette solution n’est pas faite pour vous, ou bien vous allez vous embarquer dans un développement custom.

Comment tester que le “Self Tracking” marche?

Pour tester il suffit de charger une entité de la base de données, modifier une de ses propriétés et appeler le SaveChanges du context en cours, si la modification est sauvegardée en base de données c’est que le Tracking de changement a bien fonctionné.

Récapitulons

Nous avons trois manières de travailler avec Entity Framework dans une applications n-tiers:

1 – Utiliser les entités Entity Framework (non POCO) dans toutes les couches de notre application :

- C’est mal !!! pour diverses raisons que nous avons parcouru dans un précédent article 

- Pas de problème de sérialisation des entités via un service WCF

- Nous Bénéficions du tracking de changements et du Lazy Loading

2 -  Utiliser des entités POCOs avec génération de proxy et le Template T4 C# POCO Entity Generator :

- Problème de sérialisation des entités via un service WCF

- Nous Bénéficions du tracking de changements et du Lazy Loading

3 -  Utiliser des entités POCOs sans génération de proxy et le Template T4 C# POCO Entity Generator :

- Pas de problème de sérialisation des entités via un service WCF

- Nous Bénéficions du Self Tracking

- Pas de Lazy loading

Faites votre choix selon vos besoins et vos exigences :)

Entity framework | POCO | T4 | WCF
Thursday, March 04, 2010 11:26:05 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Exposer les POCO Entity Framework via un service WCF#

Vous avez peut être choisi, comme moi, d’utiliser des entités POCO avec Entity Framework 4 et vous vous êtes certainement rendu compte qu’Entity Framework génère des Proxys qui héritent de ces types POCO pour nous les renvoyer par la suite, donc si notre type POCO s’appelle “Person” Entity Framework va nous renvoyer une instance de type “PersonProxy…”.  Jusqu’ici tout roule, mais là ou ça coince c’est lorsque vous essayez d’exposer vos POCO à travers un service WCF. WCF peut renvoyer les types dits “Known types”, ou plus précisément le DataContractSerializer utilisé par WCF ne peut sérialiser que les “Known types” qui sont les types connus par le service, or notre service ne connait pas les proxys et connait uniquement nos types POCO que nous avons déclaré à notre service, de ce fait l’appel de notre service plante au niveau de la sérialisation du résultat à renvoyer au client.

A ce stade nous pouvons avoir deux approches :

La première c’est de dire que de toutes les façons c’est mal d’exposer ces objets métier à travers un service et nous allons contourner le problème en créant des DTO (Data Transfers Object). Nous allons par la suite transformer nos POCO en DTO et exposer ces derniers. Pour que ces transformations de POCO en DTO ne soient pas lourdes et ennuyantes pour les développeurs nous pouvons utiliser un outil de mapping Objet – Objet comme l’auto mapper par exemple.

La deuxième approche consiste tout simplement à dire “Je veux pouvoir exposer mes objets métiers via WCF”. Dans ce cas nous allons nous aider de la classe DataContractResolver qui va nous permettre d’interagir avec les phases de sérialisation / dé sérialisation, nous allons donc en profiter pour mapper le type proxy généré à notre type POCO lors de ces deux phases. Je vous laisse donc lire le post de l’équipe ADO.NET pour voir le code de la solution complète et les explications qui vont avec.

Conclusion

Je déplore vraiment tous ces petits manques dans EF4 (le manque décrit ci-dessus mais aussi celui décrit dans le dernier post) mais ce qui est rassurant c’est que l’équipe ADO.NET semble à l’écoute et est très réactive puisqu’elle propose toujours des “Work around”

Sunday, February 28, 2010 11:31:40 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

Entity framework 4 et le support des POCOs#

Parmi les nouveautés les plus attendues de la version 4 d’Entity Framework il y a le support des POCOs. Dans ce post nous n’allons pas faire un nième HowTo pour expliquer comment utiliser Entity Framework pour mapper des entités POCO (vous pourrez trouver ce genre d’articles ici ou la) mais nous allons plutôt avoir un œil critique sur l’implémentation de cette fonctionnalité au sein d’EF4.

Qu’est ce qu’un POCO (Plain Old CLR Object) ?

Un POCO est une entité autonome, qui n’utilise aucun framework et aucune librairie externe au framework .NET. Ces entités ont donc l’avantage d’être Découplés et donc réutilisables sans aucun problème.

Pourquoi voulons nous qu’ Entity Framework supporte les POCOs?

C’est plus une question d’architecture, qui pourrait faire l’objet d’un article à elle même mais en résumé c’est essentiellement pour ne pas rendre toute notre application dépendante d’ Entity Framework. Si vous avez envie d’aller plus en détail sur cette question je vous conseille ce post.

L’implémentation du support des POCOs dans EF4

Pour explorer cette fonctionnalité j’ai créé un projet vide et dedans un modèle Entity framework vide. J’ai créé ensuite deux entités à travers le designer et regardé le code généré, je m’attendais bien évidemment à trouver des POCOs et à ma grande surprise ce n’est pas du tout le cas. Je me retourne donc vers la doc et c’est là que je vois qu’il faut non seulement avoir le designer mais il faut aussi créer les POCOs à la main à côté. Mais pourquoi le Designer ne génère pas directement des POCOs? je ne pourrais pas vous répondre à cette question mais je pense que l’équipe ADO.NET est entrain d’aller dans ce sens, enfin j’espère parce que c’est vraiment dommage.

Pour palier à ce manque : un Template T4

Microsoft propose un Template T4 permettant de générer les POCOs à partir de l’edmx. De cette manière, nous pourrons utiliser le designer pour concevoir nos classes puis, à partir du modèle, générer la base de données et les entités POCOs. La dernière version de ce template appelée “ADO.NET POCO Entity Generator” est disponible ici. Il faudra juste choisir le bon template (perso j’ai pris le “ADO.NET C# POCO Entity Generator”)

Pour aller plus loin …

Pour ceux qui veulent générer aussi les interfaces pour les POCOs générés par le Template, Matthieu MEZIL propose le Template modifié dans ce sens dans cet article.

Saturday, February 20, 2010 10:31:18 PM (Romance Standard Time, UTC+01:00) #    Comments [0]  | 

 

All content © 2012, Zied Nemili