Code First: associations indépendantes vs associations de clés étrangères?

102

J'ai un débat mental avec moi-même chaque fois que je commence à travailler sur un nouveau projet et que je conçois mes POCO. J'ai vu de nombreux tutoriels / exemples de code qui semblent favoriser les associations de clés étrangères :

Association de clé étrangère

public class Order
{
    public int ID { get; set; }
    public int CustomerID { get; set; } // <-- Customer ID
    ...
}

Contrairement aux associations indépendantes :

Association indépendante

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

J'ai travaillé avec NHibernate dans le passé et utilisé des associations indépendantes, qui non seulement se sentent plus OO, mais aussi (avec chargement paresseux) ont l'avantage de me donner accès à l'ensemble de l'objet Customer, au lieu de juste son ID. Cela me permet, par exemple, de récupérer une instance Order, puis de ne Order.Customer.FirstNamepas avoir à faire une jointure explicitement, ce qui est extrêmement pratique.

Donc, pour récapituler, mes questions sont:

  1. Le recours à des associations indépendantes présente-t-il des inconvénients importants? et...
  2. S'il n'y en a pas, quelle serait la raison d'utiliser des associations de clés étrangères?
Daniel Liuzzi
la source

Réponses:

106

Si vous souhaitez profiter pleinement de l'ORM, vous utiliserez certainement la référence Entity:

public class Order
{
    public int ID { get; set; }
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Une fois que vous avez généré un modèle d'entité à partir d'une base de données avec des FK, il génère toujours des références d'entité. Si vous ne souhaitez pas les utiliser, vous devez modifier manuellement le fichier EDMX et ajouter des propriétés représentant les FK. Au moins, c'était le cas dans Entity Framework v1 où seules les associations indépendantes étaient autorisées.

Entity Framework v4 propose un nouveau type d'association appelé association de clé étrangère. La différence la plus évidente entre l'association de clé indépendante et étrangère réside dans la classe Order:

public class Order
{
    public int ID { get; set; }
    public int CustomerId { get; set; }  // <-- Customer ID
    public Customer Customer { get; set; } // <-- Customer object
    ...
}

Comme vous pouvez le voir, vous avez à la fois une propriété FK et une référence d'entité. Il y a plus de différences entre deux types d'associations:

Association indépendante

  • Il est représenté comme un objet séparé dans ObjectStateManager. Il a le sien EntityState!
  • Lorsque vous créez une association, vous avez toujours besoin de droits des deux extrémités de l'association
  • Cette association est mappée de la même manière que l'entité.

Association de clé étrangère

  • Il n'est pas représenté comme un objet séparé dans ObjectStateManager. Pour cette raison, vous devez suivre certaines règles spéciales.
  • Lorsque vous créez une association, vous n'avez pas besoin des deux extrémités de l'association. Il suffit d'avoir une entité enfant et une PK de l'entité parente, mais la valeur PK doit être unique. Ainsi, lorsque vous utilisez une association de clés étrangères, vous devez également attribuer des ID uniques temporaires aux entités nouvellement générées utilisées dans les relations.
  • Cette association n'est pas mappée mais définit à la place des contraintes référentielles.

Si vous souhaitez utiliser une association de clé étrangère, vous devez cocher Inclure les colonnes de clé étrangère dans le modèle dans l'Assistant Modèle de données d'entité.

Éditer:

J'ai trouvé que la différence entre ces deux types d'associations n'est pas très bien connue, j'ai donc écrit un court article couvrant cela avec plus de détails et ma propre opinion à ce sujet.

Ladislav Mrnka
la source
Merci pour votre réponse très perspicace, et aussi pour la mise au point sur la terminologie correcte, qui m'a aidé à trouver beaucoup de ressources sur le sujet et les avantages / inconvénients des deux techniques.
Daniel Liuzzi
1
Je viens de tomber sur votre article, Ladislav. Lecture très intéressante et excellente ressource pour mieux comprendre la différence entre ces deux approches. À votre santé.
Daniel Liuzzi
1
@GaussZ: Comme je le sais, il n'y a pas eu de changement dans la manière dont les associations sont gérées depuis EF4 (où les associations FK ont été introduites).
Ladislav Mrnka
1
Ceci et les autres réponses ne semblent pas toucher aux problèmes de performance. Cependant, selon la section 2.2 Facteurs qui affectent les performances de View Generation à partir d'un article MSDN, l'utilisation de l'association indépendante semble augmenter le coût de View Generation par rapport aux associations de clés étrangères.
Veverke
1
@LadislavMrnka: pouvez-vous vérifier que le lien vers l'article que vous mentionnez ci-dessus fonctionne? Je ne peux pas y accéder.
Veverke
34

Utilise les deux. Et rendez vos références d'entité virtuelles pour permettre un chargement paresseux. Comme ça:

public class Order
{
  public int ID { get; set; }
  public int CustomerID { get; set; }
  public virtual Customer Customer { get; set; } // <-- Customer object
  ...
}

Cela évite les recherches inutiles dans la base de données, permet un chargement paresseux et vous permet de voir / définir facilement l'ID si vous savez ce que vous voulez qu'il soit. Notez que les deux ne modifient en aucun cas la structure de votre table.

Seth Paulson
la source
5
D'accord. C'est ce que j'ai fini par faire, comme l'a suggéré Ladislav. Cela vous donne vraiment le meilleur des deux mondes; l'objet entier lorsque vous avez besoin de toutes ses propriétés, et son ID lorsque vous n'avez besoin que du PK et que vous ne vous souciez pas du reste.
Daniel Liuzzi
9

L'association indépendante ne fonctionne pas bien avec ce AddOrUpdatequi est généralement utilisé dans la Seedméthode. Lorsque la référence est un élément existant, elle sera réinsérée.

// Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, Customer = customer };
db.Set<Order>().AddOrUpdate(order);

Le résultat est que le client existant sera réinséré et un nouveau client (réinséré) sera associé à une nouvelle commande.


Sauf si nous utilisons l'association de clé étrangère et attribuons l'id.

 // Existing customer.
var customer = new Customer { Id = 1, Name = "edit name" };
db.Set<Customer>().AddOrUpdate(customer);

// New order.
var order = new Order { Id = 1, CustomerId = customer.Id };
db.Set<Order>().AddOrUpdate(order);

Nous avons le comportement attendu, le client existant sera associé à une nouvelle commande.

Yuliam Chandra
la source
2
C'est une bonne découverte. Cependant, la solution de contournement (et je pense que la bonne manière) pour attacher un client à la commande consiste à le charger à partir du contexte db comme ceci: var order = new Order { Id = 1, Customer = db.Customers.Find(1) }; ou vous pouvez utiliser la méthode Select pour charger le client à partir du contexte db. Cela fonctionne avec une association indépendante.
tala9999
4

Je privilégie l'approche objet pour éviter les recherches inutiles. Les objets de propriété peuvent être tout aussi facilement remplis lorsque vous appelez votre méthode de fabrique pour construire l'entité entière (en utilisant un simple code de rappel pour les entités imbriquées). Il n'y a pas d'inconvénients que je peux voir à l'exception de l'utilisation de la mémoire (mais vous mettriez en cache vos objets, n'est-ce pas?). Donc, tout ce que vous faites est de remplacer la pile par le tas et de gagner en performances en n'effectuant pas de recherches. J'espère que cela a du sens.

CarneyCode
la source