Matthieu Mezil
Les bases d'Entity Data Model
Avec EDM, l'Entity Framework permet d'assurer l'indépendance entre les entités et le modèle de persistance et authorise un mapping très puissant
Par Matthieu Mezil publié le 24/03/2008 à 11:37, lu 6006 fois, 7 pages
 4 | Les manipulations possibles avec EDM
Dans un modèle relationnel, pour représenter une relation n->n, il faut passer par une table intermédiaire :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/many to many relationship Tables.jpg
 
Au niveau objet, pas besoin d'entité intermédiaire. Il est en effet tout à fait possible d'ajouter une propriété de type IEnumerable<CustomerDemographic> (ou une classe l'implémentant) à l'entité Customer et d'ajouter de la même manière une propriété de type IEnumerable<Customer> à l'entité CustomerDemographic. C'est exactement ce que va faire le wizard du designer d'EDM :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/many to many relationship Entities.jpg
 
Les propriétés Customers et CustomerDemographics sont en réalité des System.Data.Objects.DataClasses.EntityCollection<T> (qui implémente IEnumerable<T>).
Afin d'assurer la correspondance entre la table CustomerCustomerDemo et la relation n->n CustomerCustomerDemo, celle-ci est mappée sur la table CustomerCustomerDemo.
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/many to many relationship Mapping.jpg
 
Dans le cas d'une table intermédiaire qui possèderait plus de colonnes que les clés étrangères des deux tables, elles existent par défaut dans le modèle. Cependant, si ces colonnes supplémentaires ne sont pas obligatoires (ie : autorisent la valeur NULL), l'entité intermédiaire pourra être supprimée au profit d'une relation n->n dans le modèle conceptuel (csdl).
« L'entity splitting » est une technique qui consiste à regrouper des informations de plusieurs tables en une seule entité. Par exemple, dans le cas suivant :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/entity splitting Tables.jpg
 
La relation entre Members et Customers a la cardinalité 1->0..1.Dans le cas d'une application qui ne ferait que des statistiques géographiques sur les clients ayant une carte de membre, il serait intéressant de regrouper les informations de la table Members et les informations de la table Customers en une seule entité.Cela est extrêmement simple avec EDM et son designer, il suffit de mapper l'entité Member sur les deux tables :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/entity splitting Entities.jpg
 
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/entity splitting Mapping.jpg
 
Le Mapping permet de rajouter des conditions.
Dans le cas où les statistiques de l'application précédente ne concernent que les clients Londoniens, il serait pénible de systématiquement filtrer les données. L'idée est donc d'appliquer le filtre directement au niveau du modèle.
Dans le cas présent, il faudra appliquer un filtre « City = London ». Pour cela il faut supprimer la propriété City de l'entité Member (renommée en LondonMember) et rajouter la condition « City = London » :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/entity splitting entities with conditions.jpg
 
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/entity splitting Mapping with conditions.jpg
 
La raison pour laquelle il faut supprimer la propriété City de LondonMember est très simple : la classe LondonMember possède la contrainte City = London. Par conséquent, il ne faut pas pouvoir y déroger et la seule façon d'en être sûr c'est de ne pas permettre d'y accéder.
Avec City, il est préférable de retirer également Region et Country et de rajouter la condition « Country = UK ». Le fait de mettre cette dernière condition est très intéressante. Non pas pour éviter le risque d'une ville appelée London en dehors du Royaume-Uni mais pour l'insertion. En effet, lors de l'ajout d'un LondonMember, l'insert réalisé en base remplira également la colonne City et Country avec les valeurs utilisées par les conditions. (Dans le cas du Royaume-Uni, Region = NULL donc inutile de lui mettre une condition).
Dans bien d'autres cas, la notion d'héritage entre Member et Customer est très utile. Bien entendu, EDM répond également à ce besoin.
Pour cela, il suffit de supprimer la relation 1..1 entre Member et Customer, puis de supprimer également la propriété CustomerID de l'entité Member. Ensuite, il faut remplacer la relation 1..1 par une relation d'héritage et enfin mapper la colonne CustomerID sur la propriété CustomerID héritée de l'entité Customer :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPT Entities.jpg
 
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPT Mapping.jpg
 
Comme dit plus haut, l'ObjectContext référence les entités héritant directement de EntityObject. Dans le cas présent, cela signifie que l'ObjectContext aura une propriété Customers de type ObjectQuery<Customer> mais pas de propriétés Members. En effet, Member hérite de Customer. Ce qui est particulièrement intéressant, c'est le fait que la propriété Customers de l'ObjectContext va retourner des instances de Customer mais aussi des instances de Member avec par conséquent, toutes les possibilités induites par le polymorphisme notamment. Dans le cas où le développeur souhaiterait avoir une propriété Members sur son ObjectContext, il peut la rajouter en étendant sa classe partielle :

partial class NorthwindEFEntities

{

    public IQueryable<Member> Members

    {

        get { return Customers.OfType<Member>(); }

    }

}

L'idée ici est diviser une même table en plusieurs classes. Soit la table Employees :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPH Tables.jpg
 
Sur cette table, une colonne OutDate indique indirectement si l'employé est toujours en activité ou non. S'il ne l'est plus, la colonne OutType en donnera la raison.
Il est très facilement envisageable de vouloir distinguer les employés encore en activité des autres.
Avec EDM, il est possible d'ajouter une condition sur la nullité d'une colonne. De ce fait, on pourra facilement générer ce modèle :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPH Entities.jpg
 
Avec Employee définit comme abstract, EmployeInActivity mappé comme ceci :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPH EmployeeInActivity Mapping.jpg
 
Et OutEmployee comme ceci :
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPH OutEmployee Mapping.jpg
 
Vu que EmployeeInActivity et OutEmployee englobent l'ensemble des cas (null et non null), Employee doit forcément être abstraite. De plus, dans le cas d'une condition non null, la colonne devra obligatoirement être mappée et la propriété ne devra pas autoriser la valeur null.
Il est possible d'affiner encore la hiérarchie de classe en rajoutant par exemple une classe FiredEmployee qui héritera de OutEmployee avec la condition « OutType = F ».
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPH Entities v2.jpg
 
 
/content/5bdd0f35-3fca-491f-b418-986929ca4dcd/TPH FiredEmployee Mapping.jpg
 
Ainsi, une même table peut être associée à n entités.
Tout comme pour le TPT, la propriété Employees de l'ObjectContext retournera des instances d'EmployeeInActivity, de OutEmployee et de FiredEmployee.
Dans la base Northwind, les clients ont une adresse, les employés ont une adresse, les commandes ont une adresse de livraison et les fournisseurs ont une adresse. Pour rajouter un contrôle de cohérence entre le code postal et le pays par exemple, il est évident, qu'il faut une classe Address, encapsulée par les classes Customer, Employee, Order et Supplier. C'est justement le rôle des types complexes. Les complex types n'étant pas visibles dans la version 1 du designer d'EDM, il faudra modifier le XML de l'edmx à la main.
Dans le csdl, il est possible de définir ses propres classes :

<ComplexType Name="Address">

  <Property Name="Adress" Type="String" MaxLength="60" Nullable="true" />

  <Property Name="City" Type="String" MaxLength="15" Nullable="true" />

  <Property Name="Region" Type="String" MaxLength="15" Nullable="true" />

  <Property Name="PostalCode" Type="String" MaxLength="10" Nullable="true" />

  <Property Name="Country" Type="String" MaxLength="15" Nullable="true" />

</ComplexType>

Ensuite, il est possible de dire à une propriété qu'elle est de ce type :

<EntityType Name="Customer">

  <Key>

    <PropertyRef Name="CustomerID" />

  </Key>

  <Property Name="CustomerID" Type="String" Nullable="false" MaxLength="5" FixedLength="true" />

  <Property Name="CompanyName" Type="String" Nullable="false" MaxLength="40" />

  <Property Name="ContactName" Type="String" MaxLength="30" />

  <Property Name="ContactTitle" Type="String" MaxLength="30" />

  <Property Name="Address" Type="Self.Address" Nullable="false" />

  <Property Name="Phone" Type="String" MaxLength="24" />

  <Property Name="Fax" Type="String" MaxLength="24" />

  <NavigationProperty Name="Orders" Relationship="NorthwindEFModel.FK_Orders_Customers" 

                      FromRole="Customers" ToRole="Orders" />

  <NavigationProperty Name="CustomerDemographics" Relationship="NorthwindEFModel.CustomerCustomerDemo" 

                      FromRole="Customers" ToRole="CustomerDemographics" />

</EntityType>

Il faut ensuite définir le mapping :

<EntitySetMapping Name="Customers">

  <EntityTypeMapping TypeName="IsTypeOf(NorthwindEFModel.Customer)">

    <MappingFragment StoreEntitySet="Customers">

      <ScalarProperty Name="CustomerID" ColumnName="CustomerID" />

      <ScalarProperty Name="CompanyName" ColumnName="CompanyName" />

      <ScalarProperty Name="ContactName" ColumnName="ContactName" />

      <ScalarProperty Name="ContactTitle" ColumnName="ContactTitle" />

      <ComplexProperty Name="Address" TypeName="NorthwindEFModel.Address">

        <ScalarProperty Name="Adress" ColumnName="Address" />

        <ScalarProperty Name="City" ColumnName="City" />

        <ScalarProperty Name="Region" ColumnName="Region" />

        <ScalarProperty Name="PostalCode" ColumnName="PostalCode" />

        <ScalarProperty Name="Country" ColumnName="Country" />

      </ComplexProperty>

      <ScalarProperty Name="Phone" ColumnName="Phone" />

      <ScalarProperty Name="Fax" ColumnName="Fax" />

    </MappingFragment>

  </EntityTypeMapping>

</EntitySetMapping>

Grâce à cela et grâce aux classes partielles et aux méthodes partielles, il va être extrêmement simple d'effectuer le contrôle de cohérence :

partial class Address

{

    partial void OnPostalCodeChanging(string value)

    {

        Validate(value, Country);

    }

    partial void OnCountryChanging(string value)

    {

        Validate(PostalCode, value);

    }

    private void Validate(string postal, string country)

    {

        if (string.IsNullOrEmpty(postal) || string.IsNullOrEmpty(country))

            return;

        switch (country.ToUpper())

        {

            case "FRANCE":

                if (!Regex.IsMatch(postal, PostalPatterns.FrancePattern))

                    throw new PostalCountryException { PostalCode = postal, Country = country };

                break;

        }

    }

 

    public class PostalCountryException : InvalidOperationException

    {

        public string PostalCode { get; set; }

        public string Country { get; set; }

    }

}

 
» Démarrer une discussion
 
Discussion démarée par DadvDadv le 14/08/2008 à 00:38, 15 commentaire(s).
Discussion démarée par Frédéric Decréquy le 05/04/2008 à 12:00, 3 commentaire(s).
Discussion démarée par IDoCoX le 28/04/2009 à 12:22, 2 commentaire(s).
Discussion démarée par DadvDadv le 01/09/2008 à 22:57, 2 commentaire(s).
Discussion démarée par Aurelbeef le 04/03/2009 à 18:00, 2 commentaire(s).