web-dev-qa-db-fra.com

Mettre à jour les relations lors de l'enregistrement des modifications des objets EF4 POCO

Entity Framework 4, objets POCO et ASP.Net MVC2. J'ai une relation plusieurs à plusieurs, disons entre BlogPost et les entités Tag. Cela signifie que dans ma classe POCO BlogPost générée par T4, j'ai:

public virtual ICollection<Tag> Tags {
    // getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;

Je demande un BlogPost et les balises associées à partir d'une instance de l'ObjectContext et je l'envoie à une autre couche (Afficher dans l'application MVC). Plus tard, je récupère le BlogPost mis à jour avec des propriétés et des relations modifiées. Par exemple, il avait les balises "A" "B" et "C", et les nouvelles balises sont "C" et "D". Dans mon exemple particulier, il n'y a pas de nouvelles balises et les propriétés des balises ne changent jamais, donc la seule chose qui doit être enregistrée est les relations modifiées. Maintenant, je dois l'enregistrer dans un autre ObjectContext. (Mise à jour: Maintenant, j'ai essayé de le faire dans la même instance de contexte et j'ai également échoué.)

Le problème: je ne peux pas faire enregistrer correctement les relations. J'ai essayé tout ce que j'ai trouvé:

  • Controller.UpdateModel et Controller.TryUpdateModel ne fonctionnent pas.
  • Récupérer l'ancien BlogPost à partir du contexte puis modifier la collection ne fonctionne pas. (avec différentes méthodes à partir du point suivant)
  • This fonctionnerait probablement, mais j'espère que ce n'est qu'une solution de contournement, pas la solution :(.
  • Fonctions Attach/Add/ChangeObjectState éprouvées pour BlogPost et/ou Tags dans toutes les combinaisons possibles. Échoué.
  • This ressemble à ce dont j'ai besoin, mais cela ne fonctionne pas (j'ai essayé de le réparer, mais je ne peux pas résoudre mon problème).
  • J'ai essayé ChangeState/Add/Attach/... les objets de relation du contexte. Échoué.

"Ne fonctionne pas" signifie dans la plupart des cas que j'ai travaillé sur la "solution" donnée jusqu'à ce qu'elle ne produise aucune erreur et enregistre au moins les propriétés de BlogPost. Ce qui se passe avec les relations varie: généralement, les balises sont à nouveau ajoutées à la table des balises avec de nouveaux PK et les articles BlogPost enregistrés font référence à ceux-ci et non aux originaux. Bien sûr, les balises retournées ont des PK, et avant les méthodes de sauvegarde/mise à jour, je vérifie les PK et ils sont égaux à ceux de la base de données, donc EF pense probablement que ce sont de nouveaux objets et que ces PK sont les temporaires.

Un problème que je connais et qui pourrait rendre impossible la recherche d'une solution simple automatisée: lorsque la collection d'un objet POCO est modifiée, cela devrait se produire par la propriété de collection virtuelle mentionnée ci-dessus, car l'astuce FixupCollection mettra à jour les références inverses à l'autre extrémité. de la relation plusieurs-à-plusieurs. Cependant, lorsqu'une vue "renvoie" un objet BlogPost mis à jour, cela ne s'est pas produit. Cela signifie qu'il n'y a peut-être pas de solution simple à mon problème, mais cela me rendrait très triste et je détesterais le triomphe d'EF4-POCO-MVC :(. Cela signifierait également qu'EF ne peut pas faire cela dans l'environnement MVC, quel que soit Les types d'objet EF4 sont utilisés :(. Je pense que le suivi des modifications basé sur un instantané devrait découvrir que le BlogPost modifié a des relations avec les balises avec les PK existants.

Btw: Je pense que le même problème se produit avec les relations un-à-plusieurs (Google et mon collègue le disent). Je vais essayer à la maison, mais même si cela fonctionne, cela ne m'aide pas dans mes six relations plusieurs à plusieurs dans mon application :(.

107
peterfoldi

Essayons de cette façon:

  • Attachez BlogPost au contexte. Après avoir attaché un objet au contexte, l'état de l'objet, tous les objets associés et toutes les relations sont définis sur Inchangés.
  • Utilisez context.ObjectStateManager.ChangeObjectState pour définir votre BlogPost sur Modified
  • Itérer dans la collection de balises
  • Utilisez context.ObjectStateManager.ChangeRelationshipState pour définir l'état de la relation entre le tag actuel et BlogPost.
  • Sauvegarder les modifications

Modifier:

Je suppose qu'un de mes commentaires vous a donné un faux espoir qu'EF fera la fusion pour vous. J'ai beaucoup joué avec ce problème et ma conclusion dit que EF ne le fera pas pour vous. Je pense que vous avez également trouvé ma question sur MSDN . En réalité, il existe de nombreuses questions de ce type sur Internet. Le problème est qu'il n'est pas clairement indiqué comment gérer ce scénario. Jetons donc un œil au problème:

Contexte du problème

EF doit suivre les changements sur les entités afin que la persistance sache quels enregistrements doivent être mis à jour, insérés ou supprimés. Le problème est qu'il est de la responsabilité d'ObjectContext de suivre les modifications. ObjectContext ne peut suivre les modifications que pour les entités attachées. Les entités créées en dehors de l'ObjectContext ne sont pas du tout suivies.

Description du problème

Sur la base de la description ci-dessus, nous pouvons clairement affirmer que EF est plus adapté aux scénarios connectés où l'entité est toujours attachée au contexte - typique pour l'application WinForm. Les applications Web nécessitent un scénario déconnecté dans lequel le contexte est fermé après le traitement de la demande et le contenu de l'entité est transmis en tant que réponse HTTP au client. La prochaine requête HTTP fournit un contenu modifié de l'entité qui doit être recréé, attaché à un nouveau contexte et conservé. Les loisirs se déroulent généralement en dehors de la portée du contexte (architecture en couches avec ignorance de la persistance).

Solution

Alors, comment faire face à un tel scénario déconnecté? Lorsque vous utilisez des classes POCO, nous avons 3 façons de gérer le suivi des modifications:

  • Instantané - nécessite le même contexte = inutile pour un scénario déconnecté
  • Proxy de suivi dynamique - nécessite le même contexte = inutile pour un scénario déconnecté
  • Synchronisation manuelle.

La synchronisation manuelle sur une seule entité est une tâche facile. Il vous suffit d'attacher une entité et d'appeler AddObject pour l'insertion, DeleteObject pour la suppression ou définir l'état dans ObjectStateManager sur Modified pour la mise à jour. La vraie douleur survient lorsque vous devez traiter avec un graphique d'objet au lieu d'une seule entité. Cette douleur est encore pire lorsque vous devez traiter avec des associations indépendantes (celles qui n'utilisent pas la propriété de clé étrangère) et de nombreuses relations. Dans ce cas, vous devez synchroniser manuellement chaque entité dans le graphique d'objet mais également chaque relation dans le graphique d'objet.

La synchronisation manuelle est proposée comme solution par la documentation MSDN: Attacher et détacher des objets dit:

Les objets sont attachés au contexte de l'objet dans un état inchangé. Si vous devez modifier l'état d'un objet ou la relation parce que vous savez que votre objet a été modifié à l'état détaché, appliquez l'une des méthodes suivantes.

Les méthodes mentionnées sont ChangeObjectState et ChangeRelationshipState de ObjectStateManager = suivi manuel des modifications. Une proposition similaire se trouve dans un autre article de documentation MSDN: Définir et gérer les relations dit:

Si vous travaillez avec des objets déconnectés, vous devez gérer manuellement la synchronisation.

De plus il y a article de blog lié à EF v1 qui critique exactement ce comportement d'EF.

Motif de la solution

EF a de nombreuses opérations et paramètres "utiles" tels que Actualiser , Charger , ApplyCurrentValues , ApplyOriginalValues , MergeOption etc. Mais d'après mon enquête, toutes ces fonctionnalités ne fonctionnent que pour une seule entité et n'affectent que les propriétés scalaires (= pas les propriétés et les relations de navigation). Je préfère ne pas tester ces méthodes avec des types complexes imbriqués dans une entité.

Autre solution proposée

Au lieu d'une véritable fonctionnalité de fusion, l'équipe EF fournit quelque chose appelé Entités de suivi automatique (STE) qui ne résout pas le problème. Tout d'abord, STE ne fonctionne que si la même instance est utilisée pour l'ensemble du traitement. Dans une application Web, ce n'est pas le cas, sauf si vous stockez l'instance dans l'état d'affichage ou la session. Pour cette raison, je suis très mécontent d'utiliser EF et je vais vérifier les fonctionnalités de NHibernate. La première observation indique que NHibernate a peut-être une telle fonctionnalité .

Conclusion

Je vais finir ces hypothèses avec un seul lien vers un autre question connexe sur le forum MSDN. Vérifiez la réponse de Zeeshan Hirani. Il est l'auteur de Entity Framework 4.0 Recipes . S'il dit que la fusion automatique des graphes d'objets n'est pas supportée, je le crois.

Mais il est toujours possible que je me trompe complètement et certaines fonctionnalités de fusion automatique existent dans EF.

Édition 2:

Comme vous pouvez le voir, cela a déjà été ajouté à MS Connect comme suggestion en 2007. MS l'a fermé comme quelque chose à faire dans la prochaine version, mais en fait rien n'a été fait pour améliorer cet écart sauf STE.

143
Ladislav Mrnka

J'ai une solution au problème décrit ci-dessus par Ladislav. J'ai créé une méthode d'extension pour le DbContext qui effectuera automatiquement les ajouts/mises à jour/suppressions en fonction d'un diff du graphique fourni et du graphique persistant.

Actuellement, en utilisant Entity Framework, vous devrez effectuer les mises à jour des contacts manuellement, vérifier si chaque contact est nouveau et ajouter, vérifier s'il est mis à jour et modifier, vérifier s'il est supprimé puis le supprimer de la base de données. Une fois que vous devez le faire pour quelques agrégats différents dans un grand système, vous commencez à réaliser qu'il doit y avoir un meilleur moyen, plus générique.

Veuillez jeter un oeil et voir si cela peut aider http://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates- d'un-graphe-d'entités-détachées /

Vous pouvez aller directement au code ici https://github.com/refactorthis/GraphDiff

19
brentmckendrick

Je sais qu'il est tard pour l'OP mais comme c'est un problème très courant, j'ai posté cela au cas où cela servirait quelqu'un d'autre. J'ai joué avec ce problème et je pense avoir une solution assez simple, ce que je fais est:

  1. Enregistrez l'objet principal (Blogs par exemple) en définissant son état sur Modifié.
  2. Recherchez dans la base de données l'objet mis à jour, y compris les collections que je dois mettre à jour.
  3. Recherchez et convertissez .ToList () les entités que je souhaite inclure dans ma collection.
  4. Mettez à jour la ou les collections de l'objet principal dans la liste que j'ai obtenue à l'étape 3.
  5. Sauvegarder les modifications();

Dans l'exemple suivant, "dataobj" et "_categories" sont les paramètres reçus par mon contrôleur "dataobj" est mon objet principal et "_categories" est un IEnumerable contenant les ID des catégories que l'utilisateur a sélectionnées dans la vue.

    db.Entry(dataobj).State = EntityState.Modified;
    db.SaveChanges();
    dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
    var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
    dataobj.Categories = it;
    db.SaveChanges();

Cela fonctionne même pour plusieurs relations

9
c0y0teX

L'équipe Entity Framework est consciente qu'il s'agit d'un problème d'utilisation et prévoit de le résoudre après l'EF6.

De l'équipe Entity Framework:

C'est un problème d'utilisabilité que nous connaissons et c'est quelque chose que nous avons pensé et prévoyons de faire plus de travail sur post-EF6. J'ai créé cet élément de travail pour suivre le problème: http://entityframework.codeplex.com/workitem/864 L'élément de travail contient également un lien vers l'élément vocal de l'utilisateur pour cela - je vous encourage voter pour si vous ne l'avez pas déjà fait.

Si cela vous impacte, votez pour la fonctionnalité sur

http://entityframework.codeplex.com/workitem/864

7
Eric J.

Toutes les réponses étaient excellentes pour expliquer le problème, mais aucune n'a vraiment résolu le problème pour moi.

J'ai constaté que si je n'utilisais pas la relation dans l'entité parent mais que j'ajoutais et supprimais les entités enfants, tout fonctionnait très bien.

Désolé pour le VB mais c'est dans cela que le projet dans lequel je travaille est écrit.

L'entité parent "Report" a une relation un à plusieurs avec "ReportRole" et possède la propriété "ReportRoles". Les nouveaux rôles sont transmis par une chaîne séparée par des virgules à partir d'un appel Ajax.

La première ligne supprimera toutes les entités enfants, et si j'utilisais "report.ReportRoles.Remove (f)" au lieu de "db.ReportRoles.Remove (f)" j'obtiendrais l'erreur.

report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))
1
Alan Bridges