Affichage des articles dont le libellé est Entity Framework. Afficher tous les articles
Affichage des articles dont le libellé est Entity Framework. Afficher tous les articles

26 janvier 2009

Entity Framework et les types complexes

Les types complexes font parti des fonctionnalités manquantes de l'éditeur de modèle EDM fourni avec Visual Studio 2008. Pour pouvoir en bénéficier, il est nécessaire de modifier les fichiers CSDL (schéma conceptuel) et MSL (schéma de liaison) ainsi que de modifier les classes d'entité.

Tout d'abord à quoi sert un type complexe ? Dans une entité, il peut être utile de séparer dans un nouveau type plusieurs variables cohérentes entre elles. Par exemple, la classe Client est composée des variables suivantes :
  • Id

  • Nom

  • Prenom

  • Telephone

  • Pays

  • Rue

  • CodePostal

  • Ville

Les 4 dernières variables sont en relation avec l'adresse du client. Il serait donc logique de les déplacer dans une classe appelée Adresse, d'où l'utilité du type complexe.

Dans Entity Framework, un type complexe ne contient aucun identifiant et il est forcément relié à une entité mère, Client pour notre exemple. Comme avec l'héritage "1 table -> plusieurs entités", c'est une autre manière de définir plusieurs entités pour une seule table.

Si vous souhaitez mettre en place un type complexe, vous serez obligé d'utiliser directement les fichiers CSDL, MSL et SSDL. L'éditeur de modèle EDM cache ses 3 fichiers dans l'assemblage du projet en tant que ressources embarquées. Pour les extraire, j'ai utilisé Reflector et l'addin FileDisassembler permettant d'extraire tous les fichiers composant un assemblage.

Une autre solution est de passer par l'utilitaire "EdmGen.exe" qui permet de générer les fichiers CSDL, MSL, SSDL. Cependant, vous perdez tout le bénéfice de définir graphiquement votre modèle via l'éditeur d'EDM (une erreur de frappe est vite arrivé dans un fichier XML …).

Pour l'exemple, je pars de la base de données utilisée dans mon article publié sur developpez.com. Vous trouverez les scripts SQL Serveur 2005 de la base dans l'archive, à la fin de ce billet.

En 1er lieu, voici le schéma logique (SSDL) :

<Schema Namespace="helloentityfxModel.Store" Alias="Self" Provider="System.Data.SqlClient" ProviderManifestToken="2005" xmlns:store="http://schemas.microsoft.com/ado/2007/12/edm/EntityStoreSchemaGenerator" xmlns="http://schemas.microsoft.com/ado/2006/04/edm/ssdl">
  <EntityContainer Name="CustomModelStoreContainer">
    <EntitySet Name="Client" EntityType="helloentityfxModel.Store.Client" store:Type="Tables" Schema="dbo" />
  </EntityContainer>
  <EntityType Name="Client">
    <Key>
      <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="int" Nullable="false" StoreGeneratedPattern="Identity" />
    <Property Name="Nom" Type="varchar" MaxLength="50" />
    <Property Name="Prenom" Type="varchar" MaxLength="50" /&gt
    <Property Name="Pays" Type="varchar" MaxLength="50" />
    <Property Name="Rue" Type="varchar" MaxLength="100" />
    <Property Name="CodePostal" Type="char" MaxLength="5" />
    <Property Name="Telephone" Type="char" MaxLength="10" />
    <Property Name="Ville" Type="varchar" MaxLength="50" />
  </EntityType>
</Schema>

Pour ce fichier, rien de spécial à signaler si ce n'est la description de la table et des propriétés (colonnes) qui la composent. Aucune référence au type complexe n'apparait. Ceci est tout à fait normal puisque c'est le schéma logique faisant référence à la structure de la base de données.

Passons maintenant au schéma conceptuel (CSDL) :

<Schema Namespace="CustomModel" Alias="Self" xmlns="http://schemas.microsoft.com/ado/2006/04/edm">
  <EntityContainer Name="Context">
    <EntitySet Name="Client" EntityType="CustomModel.Client" />
  </EntityContainer>
  <EntityType Name="Client">
    <Key>
      <PropertyRef Name="Id" />
    </Key>
    <Property Name="Id" Type="Int32" Nullable="false" />
    <Property Name="Nom" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
    <Property Name="Prenom" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
    <Property Name="Telephone" Type="String" MaxLength="10" Unicode="false" FixedLength="true" />
    <Property Name="Adresse" Type="Self.Adresse" Nullable="false" />
  </EntityType>
  <ComplexType Name="Adresse">
    <Property Name="Pays" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
    <Property Name="Rue" Type="String" MaxLength="100" Unicode="false" FixedLength="false" />
    <Property Name="CodePostal" Type="String" MaxLength="5" Unicode="false" FixedLength="true" />
    <Property Name="Ville" Type="String" MaxLength="50" Unicode="false" FixedLength="false" />
  </ComplexType>
</Schema>

Il comporte la définition de l'entité Client comprenant entre autre une propriété Adresse faisant référence au type complexe CustomModel.Adresse. Celui-ci est composé de 4 propriétés (Pays, Rue, CodePostal, Ville).

Pour finir, voici le schéma de liaison (MSL) :

<Mapping Space="C-S" xmlns="urn:schemas-microsoft-com:windows:storage:mapping:CS">
  <EntityContainerMapping StorageEntityContainer="CustomModelStoreContainer" CdmEntityContainer="Context">
    <EntitySetMapping Name="Client">
      <EntityTypeMapping TypeName="CustomModel.Client">
        <MappingFragment StoreEntitySet="Client">
          <ScalarProperty Name="Id" ColumnName="Id" />
          <ScalarProperty Name="Nom" ColumnName="Nom" />
          <ScalarProperty Name="Prenom" ColumnName="Prenom" />
          <ScalarProperty Name="Telephone" ColumnName="Telephone" />
          <ComplexProperty Name="Adresse" TypeName="CustomModel.Adresse">
            <ScalarProperty Name="Pays" ColumnName="Pays" />
            <ScalarProperty Name="Rue" ColumnName="Rue" />
            <ScalarProperty Name="CodePostal" ColumnName="CodePostal" />
            <ScalarProperty Name="Ville" ColumnName="Ville" />
          </ComplexProperty>
        </MappingFragment>
      </EntityTypeMapping>
    </EntitySetMapping>
  </EntityContainerMapping>
</Mapping>

On y remarque le mapping entre l'entité Client et la table Client, ainsi qu'entre le type complexe CustomModel.Adresse et la table Client.

Passons maintenant à la définition des classes et du contexte. 2 choix sont possibles, soit :
  • Utiliser le custom tool fourni par Visual Studio 2008 ou l'outil "edmgen.exe" pour générer les classes et le contexte à partir du fichier CSDL.

  • Définir/Modifier à la main vos propres classes d'entité. Cette méthode est utile lorsque vous possédez déjà vos classes métier. Cela consiste à implémenter pour chaque entité les interfaces IPOCO d'Entity Framework et d'ajouter quelques attributs.

Pour faire simple, voici la démarche pour utiliser le Custom Tool "EntityModelCodeGenerator" :
  • Sélectionnez le schéma conceptuel (fichier CSDL) dans Visual Studio.

  • Dans la fenêtre de propriétés, définissez la propriété Custom Tool à "EntityModelCodeGenerator".

  • A chaque sauvegarde du fichier CSDL, un fichier contenant les classes d'entité et le contexte est généré.

Et voila, si tout s'est bien passé, Client possède maintenant une variable de type Adresse, qui est notre type complexe.

Vous trouverez dans l'archive ci-dessous le code source de l'article et les scripts SQL Serveur 2005 de la base de données.

10 décembre 2008

POCO avec Entity Framework (1/2)

Qu'est-ce que POCO déjà ?

Cela signifie Plain Old Code Object. Le "C" peut vouloir dire aussi CSharp ou CLR Object. Dans le monde Java on retrouve ainsi l'acronyme POJO qui veut dire Plain Old Java Object, mais le sens est le même.

POCO signifie de laisser tel quel ses classes métiers lorsqu'on souhaite les rendre persistantes avec un outil de mapping objet-relationnel. C'est à dire que les classes métiers ne sont pas modifiées pour les faire fonctionner avec l'outil.

Entity Framework fonctionne avec des entités. Ce sont des classes dérivant de la classe System.Data.Objects.DataClasses.EntityObject. Une autre solution est d'implémenter 3 interfaces fournies par le framework .Le problème avec ce design, c'est que les classes métiers doivent être modifiées pour fonctionner avec Entity Framework. Du coup, on en déduie qu'Entity Framework ne supporte pas le concept POCO, en tout cas pas dans sa version 1.

L'équipe d'Entity Framework travaille sur une solution afin d'annuler la dépendance qu'il existe entre les classes métiers et le framework. Vous trouverez une partie de leur reflexion sur un de leur blog.

Une autre solution appellée PostSharp4EF a été proposée par Ruurd Boeke. Celle-ci est plutôt élégante car elle utilise la programmation orientée aspect (avec Postsharp) pour implémenter lors d'une 2ème compilation les interfaces requises pour rendre persistentes nos classes POCO.

PostSharp4EF exécute entre autres les tâches suivantes :
  • implémente les interfaces IPOCO : IEntityWithChangeTracker, IEntityWithKey et IEntityWithRelaships
  • Ajoute l'attribut EdmEntityType sur la classe, l'attribut EdmScallarProperty sur chaque propriété, l'attribut EdmRelashionShip et EdmSchema sur l'assembly

Toutes ces tâches sont exécutées grâce à PostSharp et à l'attribut [assembly: Poco("InheritanceTypeConnection")], celui-ci étant implémenté par PostSharp4EF.

Nous verrons dans un prochain billet un petit exemple d'implémentation.

27 novembre 2008

Peut-on utiliser des classes métiers n'héritant pas de "EntityObject" ?

La réponse est oui ! Au minimum, il est nécessaire que vos classes héritent des interfaces suivantes :

  • IEntityWithChangeTracker : Active le suivi des modifications.

  • IEntityWithKey. Facultatif : Expose une clé d'entité.

  • IEntityWithRelationships : Obligatoire pour les entités présentant des associations.



Pour plus d'informations, voici la documentation officielle.

Implémenter ces 3 interfaces sous entend que vos classes métiers doivent être modifiées pour utiliser la persistance avec Entity Framework. En d'autres termes, le principe de faible couplage / forte cohérence n'est pas respecté puisque les classes métiers contiennent à la fois de la logique métier et de la logique de persistance. De plus, dans certains scénarios, il n'est pas possible de modifier ses classes métiers.

On peut en tirer la conclusion suivante : Entity Framework ne permet pas d'utiliser des classes POCO (pour Plain Old Code Object). D'autres outils le permettent comme NHibernate avec l'utilisation d'objets proxy. Danny Simmons, développeur de l'équipe d'Entity Framework s'explique à ce sujet. Souhaitons que ce manque soit comblé dans les prochaines versions de l'outil.

Cependant, sachez qu'il est possible de contourner ce manque avec l'utilisation de la programmation orientée aspect. Sans rentrer dans les détails, PostSharp permet d'injecter du code lors d'une 2ème phase de compilation et donc d'implémenter les 3 interfaces nécessaires sur les classes POCO. Un projet a d'ailleurs été réalisé à ce sujet. Vous trouverez plus d'information sur son blog.

25 novembre 2008

Lecture des données

Entity Framework offre 3 types de possibilité pour requêter la source de données :

  • LinQ To Entities

  • Entity SQL

  • Les méthodes génératrices de requête de "ObjectQuery<T>"
LinQ To Entities permet d'écrire des requêtes syntaxiquement proche du SQL, et cela directement en C# ou VB.Net. L'avantage majeur vient du fait que les requêtes produites sont vérifiées à la compilation et non plus à l'exécution. Comme pour chaque implémentation de LinQ, chaque requête est transformée en un arbre d'expression (créé par l'objet ObjectContext) qui est traduit en SQL par le fournisseur de données final. Le résultat d'une requête LinQ to Entities est de type "IQueryable<T>" où T est le type spécifié dans la clause SELECT de la requête.
Voici donc un exemple de requête LinQ to Entities :

var requete = from publication in bdd.Publication
select publication;

Le résultat est de type "IQueryable<Publication>". Il est possible d'itérer sur les résultats avec une simple boucle "foreach". Vu que "IQueryable<T>" implémente un énumérateur, sachez que la requête vers la source de données est uniquement exécutée lors du parcours des résultats. Pour plus d'information, veuillez vous référencer à la documentation officielle de l'objet "IQueryable<Publication>".
Linq to Entities supporte un ensemble de méthodes pour requêter la source de données dont voici une liste non exhaustive (url de la liste complète) :

  • Select, SelectMany

  • Where

  • GroupJoin, Join

  • All, Any

  • Distinct

  • Except

  • Intersect

  • Union

  • OrderBy, OrderByDescending, ThenBy, ThenByDescending

  • GroupBy

  • Average

  • Count, LongCount

  • Max, Min, Sum

  • OfType, Cast

  • First, FirstOrDefault

  • Skip, Take


Voici l'exemple d'une requête sélectionnant seulement les publications qui contiennent le mot "C#" dans leur titre :

var requete = from publication in bdd.Publication
where publication.Titre.Contains("C#")
select publication;

Pour plus d'informations sur les requêtes LinQ, je vous invite à lire l'article de Thomas Lebrun.
Il est aussi possible de requêter la source de données grâce à Entity SQL. C'est un langage héritant du vocabulaire et de la grammaire de type SQL. Ce langage permet de récupérer des produits scalaires, des ensembles de résultat et des graphes d'objet. Pour une description du langage, veuillez vous référer à la documentation officielle.
Voici l'exemple d'une requête récupérant toutes les publications stockées en base :


string chaineRequete = "SELECT VALUE p FROM Publication AS p";

// Définition de la requête de type ObjectQuery<Publication>
ObjectQuery<Publication> requete = new ObjectQuery<Publication>(chaineRequete, bdd, MergeOption.NoTracking);

En dernier choix, vous pouvez utiliser les méthodes du générateur de requêtes de la classe "ObjectQuery<T>". En voici un exemple :

// Définition de la requête de type ObjectQuery<Publication>
var requete = bdd.Publication.SelectValue<Publication>("it");

La suite de l'article fera uniquement référence à LinQ to Entities, mais toutes les requêtes faites sur le modèle peuvent être réalisées avec Entity SQL ou grâce aux méthodes du générateur de requêtes de la classe "ObjectQuery<T>".
Chaque entité en relation avec une autre possède une collection d'entités connexes. Cependant, celle-ci n'est pas initialisée par défaut. Pour naviguer vers ces entités, il est nécessaire de les charger explicitement :

  • soit en utilisant la méthode "Include" de "ObjectQuery<T>",

  • soit en utilisant la méthode "Load" appartenant à la classe "EntityReference<T>" ou à "EntityCollection<T>".


L'exemple suivant utilise la méthode "Include" pour récupérer explicitement, lors de la requête, l'éditeur et les auteurs associés aux publications :

using (Modele bdd = new Modele())
{
var requete = from publication in bdd.Publication.Include("Auteur").Include("Editeur") select publication;

requete.ToList().ForEach((Publication p) =>
{
Console.WriteLine("Publication : ID = {0} Titre = {1}", p.Id, p.Titre);

Console.WriteLine(" Editeur : ID = {0} Nom = {1}", p.Editeur.Id, p.Editeur.Nom);

p.Auteur.ToList().ForEach(a => Console.WriteLine(" Auteur : ID = {0} Nom = {1} {2}",a.Id,a.Prenom,a.Nom));
});
}

Voici un autre exemple avec l'utilisation de la méthode "Load". Remarquez que chaque collection ou chaque référence liée à une entité possède une propriété "IsLoaded" permettant de savoir si la ou les entités connexes sont chargées.

using (Modele bdd = new Modele())
{
var requete = from publication in bdd.Publication select publication;

requete.ToList().ForEach((Publication p) =>
{
if (!p.EditeurReference.IsLoaded)
p.EditeurReference.Load();

Console.WriteLine(" Editeur : ID = {0} Nom = {1}", p.Editeur.Id, p.Editeur.Nom);

if (!p.Auteur.IsLoaded)
p.Auteur.Load();

p.Auteur.ToList().ForEach(a => Console.WriteLine(" Auteur : ID = {0} Nom = {1} {2}", a.Id, a.Prenom, a.Nom));
});
}

23 novembre 2008

Quels sont les SGBDR supportés ?

La couche "Object Services" d'Entity Framework s'appuie sur le fournisseur de données "EntityClient" pour qu'il exécute les requêtes LinQ transformées au préalable en arbres d'expression. "EntityClient" a pour but d'accéder aux données définies dans le modèle EDM décrits par les 3 fichiers CSDL, MSL et SSDL. Il génère ensuite un arbre d'expressions relatif aux données du schéma logique et l'envoie à un fournisseur de données spécialisées, afin que celui-ci transforme l'arbre en requêtes SQL.

Ce dernier fournisseur de données est spécifique à la base de données utilisée. Le fournisseur utilisé est défini par la chaine de connexion utilisée par l'objet "EntityConnection", qui a la responsable d'ouvrir la connexion de la source de données.

Voici un exemple de chaine de connexion, définit par l'assistant de Visual Studio 2008 :
metadata=res://*/modele.csdlres://*/modele.ssdlres://*/modele.msl;provider=System.Data.SqlClient;provider
connection string="Data Source=TLS-LOG-PO-PMO\SQLEXPRESS;Initial Catalog=helloentityfx;Integrated Security=True;MultipleActiveResultSets=True"


La 1ère partie concerne les métadonnées de la connexion. On y voit la référence aux 3 schémas du modèle EDM, et du fournisseur de données utilisé pour la base de données. Ici, c'est la classe "System.Data.SqlClient" qui permet de communiquer avec une base de type SQL Serveur 2005.

Il est donc possible de spécifier un autre fournisseur de données pour utiliser, par exemple, une base Oracle, MySQL ou PostgreSQL. Voici une liste non exhaustive des fournisseurs de données compatibles avec Entity Framework :

Il est aussi possible de créer son propre fournisseur de données. Un projet sur Codeplex fournit le code nécessaire.

20 novembre 2008

Concept d'Entity Framework

Entity Framework est la solution de mapping objet relationnel proposée par Microsoft. Son but est de fournir la couche d'abstraction nécessaire aux développeurs pour qu'ils n'accèdent plus directement à la base de données, mais par l'intermédiaire d'entités définies par un modèle appelé EDM (Entity Data Model).

Ce modèle est divisé en 3 parties :

  • Le schéma conceptuel : Il regroupe la définition des entités, des ensembles d'entités et des fonctions utilisées. Ces éléments sont définis dans un fichier XML appelé CSDL (Conceptual Schema Definition Language).

  • Le schéma logique : Celui-ci correspond à la définition des tables, vues et procédures stockées déclarées dans la base de données. Toutes ces informations sont regroupées dans un fichier XML appelé SSDL (Store Schema Definition Language).

  • Schéma de liaison : Il définit les liaisons entre le schéma conceptuel et le schéma logique. Il associe entre autre les entités déclarées dans le fichier CSDL aux tables définies dans le fichier SSDL. Ce mapping est inscrit dans le fichier XML MSL (Mapping Specification Language).



Visual Studio 2008 SP1 intègre aussi un assistant permettant de générer un modèle EDM, et ce de manière graphique. Il ressemble assez à l'assistant LinQ to SQL pour les connaisseurs et il offre un confort d'utilisation bien meilleur que l'outil en ligne de commande, au détriment d'un certain manque de fonctionnalités.

Par exemple, il n'est pas possible de définir de type complexe avec l'assistant, alors que "EdmGen.exe" le permet. Rassurez-vous, vous pouvez commencer à utiliser l'assistant (gain de temps évident) et utiliser par la suite l'outil en ligne de commande. Néanmoins, il s'avère regrettable d'avoir recours à ce genre de manipulation. La prochaine version de l'assistant devrait corriger cela; et bien d'autres points.