J'ai un problème avec la mise à jour des entités dans EF Core , puis la journalisation de ces modifications dans un tableau . Les technologies utilisées sont:
Donc, je veux obtenir une entité de la base de données, la modifier dans le front-end, puis la mettre à jour sur le backend et enregistrer ces modifications dans la base de données. En plus de cela, j'ai une table appelée ChangeLogs
où les champs les plus importants sont From (mappé à partir de OldValues
) et À (mappé à partir de CurrentValues
). Eh bien, ces deux champs sont identiques (ce qui signifie qu'ils ont exactement les mêmes valeurs, les nouvelles valeurs) et la situation se présente comme suit:
J'obtiens l'entité de la base de données comme ceci
_context.Anomalies
.Include (a => a.Asset)
.FirstOrDefaultAsync (a => a.Id == anomalyId)
Modifiez l'entité dans le front-end, puis faites une requête PUT pour la mettre à jour;
Mettez à jour l'entité:
Pour que Update ()
fonctionne, je dois d'abord appeler ceci:
_context.DetachAllEntities ();
Sinon, j'obtiens une erreur indiquant qu'une entité avec le même identifiant est déjà suivie. Appelez ensuite Update ()
et SaveChanges ()
:
_context.Anomalies.Update (anomalie);
_context.SaveChanges ();
L'objet anomaly
est celui de la requête.
Pour la partie ChangeLog
, j'ai remplacé la méthode SaveChanges ()
, en suivant cet exemple , et les valeurs Old / Original et New / Current sont définies comme suit:
auditEntry.OldValues [propertyName] = property.OriginalValue;
auditEntry.NewValues [propertyName] = property.CurrentValue;
Fondamentalement, cela passe par toutes les entrées de ChangeTracker
, crée un AuditEntry
et après base.SaveChanges ()
il reviendra et sera défini EntityId pour cet AuditEntry car vous ne l'avez pas avant d'enregistrer les modifications (c'est au cas où vous ajoutez une nouvelle entité, pour mettre à jour rien ne se passe après l'enregistrement des modifications).
La méthode Update () fonctionne, les changements sont reflétés dans la base de données. Mais le seul problème est avec ChangeLogs
, l'entrée de ChangeTracker.Entries ()
ne connaît pas les OldValues
.
J'admets que je ne comprends pas parfaitement le système de suivi utilisé par EF, mais je suppose que cela devrait m'aider à mettre à jour les entités et ne pas créer de problèmes. Parce que je sais que l'appel de _context.DetachAllEntities ();
n'est pas correct. J'ai essayé d'utiliser AsNoTracking ()
et de supprimer le DetachAllEntities ()
mais le résultat semble être le même. J'ai pensé à prendre dbEntity, à copier chaque champ de l'entité de requête à l'entité de base de données, puis à Update (dbEntity)
mais cela semble être beaucoup de travail à faire pour un petit avantage. Mon entité Anomaly
a beaucoup de propriétés de navigation, il serait difficile de créer une méthode de copie pour elle et de la maintenir.
Le code DetachAllEntities () > est défini comme ceci:
public static void DetachAllEntities(this DbContext context) { var entries = context.ChangeTracker.Entries().ToList(); foreach (var entry in entries) { entry.State = EntityState.Detached; } }
DbContext
est défini sur Scoped
durée de vie, les gestionnaires également.
J'ai également essayé d'utiliser la méthode d'audit de ce tutoriel , mais le résultat est la même chose.
Je crains que tout le processus de mise à jour ne soit pas bien fait et donc ce problème ..
MISE À JOUR J'ai créé un exemple de projet pour illustrer ce problème. Vous pouvez vérifier le code source sur bitbucket . Le README a plus d'informations à ce sujet
J'ai sérialisé les objets récupérés du contexte.
Avec suivi sur la GAUCHE Sans suivi sur la DROITE
Tout conseil, opinion, nouvelle idée, commentaire est le bienvenu. Merci!
3 Réponses :
Lors de l'utilisation d'EF, les entités sont créées / remplies avec des informations de «suivi» qui relient l'entité au contexte (et à d'autres choses).
Pour mettre à jour une entité, tout ce que vous avez à faire est de rechercher une entité du contexte, modifiez les valeurs puis appelez savechanges. Il n'est pas nécessaire de détacher les entités (sauf si vous avez une raison spécifique pour le faire).
L'exemple suivant est tout ce dont vous avez besoin pour mettre à jour une entité.
EX:
var anomly = _context.Anomalies .Include(a => a.Asset) .FirstOrDefaultAsync(a => a.Id == anomalyId); anomly.Description = "I have changed the description"; _context.Dispose(); _context = new DBContext(); var databaseAnomoly = _context.Anomalies .Include(a => a.Asset) .FirstOrDefaultAsync(a => a.Id == anomaly.Id); //Update fields databaseAnomoly.Description = anomly.Description; _context.SaveChanges();
MODIFIER
Si vous fermez le contexte ou détachez, vous devez rechercher l'entité et la mettre à jour avec les nouvelles valeurs avant d'appeler savchanges.
EX:
var anomly = _context.Anomalies .Include(a => a.Asset) .FirstOrDefaultAsync(a => a.Id == anomalyId); anomly.Description = "I have changed the description"; _context.SaveChanges();
J'ai changé mon code en _context.Attach (anomalie); _context.SaveChanges ();
. Toujours la même erreur. D'après ce que je comprends, il essaie de parcourir toutes les propriétés de l'anomalie et de les attacher, mais si une propriété de navigation est ajoutée et plus tard une entité avec le même ID est trouvée, l'erreur apparaît. Même si la première entité ajoutée est Anomaly.Asset
et la seconde se trouve dans Anomaly.Inspection.Asset
(à titre d'exemple)
Modification et suppression des pièces jointes, car cela ne faisait pas ce à quoi je pensais :)
Ok, maintenant je comprends exactement ce que tu voulais dire. Mais un problème ici, mon entité est grande, comme beaucoup de propriétés. Ce serait un cauchemar de copier champ par champ .. J'y ai réfléchi depuis le début mais cela ne semble pas être une bonne solution, plutôt une solution de contournement
J'ai rencontré un problème similaire avec une tonne de champs et je l'ai résolu en surchargeant l'opérateur +. Alors maintenant, nous faisons simplement databaseAnomoly + = anomly. Vous finissez toujours par écrire tout le code pour copier le champ dans le champ, mais vous ne le faites qu'une seule fois.
@Simonca - EF fera ce que vous voulez qu'il fasse, sa compréhension de celui-ci est un peu décalée. Il y a trop de variables (pardonnez le jeu de mots) pour donner une réponse correcte - êtes-vous en mesure d'afficher votre code actuel tel quel afin que nous puissions mieux vous aider?
Oui @Robert, j'avoue que ma compréhension est peut-être un peu décevante, j'apprends encore en travaillant. J'ai téléchargé un exemple de code ici: bitbucket.org/marian_simonca/ef-core- update / src / master (même lien que sur la question mise à jour)
Ah ok ... Je vais jeter un œil au code et publier ma réponse ci-dessous ... Je suis sûr que c'est quelque chose de simple
Je pense que votre problème est ici. Comme les deux objets existent dans des contextes différents, il s'agit fondamentalement d'un objet différent. Si vous modifiez votre code afin de récupérer l'entité de la base de données, mettez à jour ses valeurs, puis utilisez cet objet pour passer à la méthode SaveChangesAndAudit (). Vous obtiendrez le résultat que vous attendez. J'ai ajusté le code dans la capture d'écran ci-jointe. Vous rencontrerez également un problème fondamental que la plupart des développeurs trouvent, et c'est la cartographie des objets - des outils comme Automapper vous facilitent la vie avec ce genre de chose - donc si vos entités sont grandes, cela vaut la peine d'y jeter un coup d'œil pour vous aider; -)
Fondamentalement, je n'ai aucune chance que de mapper le webEntity avec dbEntity champ par champ? C'est tellement décourageant. Je connais le mappeur automatique, mais j'ai vraiment pensé qu'il pourrait y avoir une meilleure solution
Vous en aurez besoin car EF ne pourra pas suivre les modifications. Il doit avoir l'objet dans son contexte afin de pouvoir capturer les méthodes Set () sur ses propriétés et enregistrer ce qui a changé. Ce n'est qu'au moment où ces valeurs sont modifiées, qu'il est suivi. C'est juste fondamentalement comment cela fonctionne malheureusement
Je sais que cela peut ne pas sembler une bonne nouvelle - mais c'est la bonne réponse à cette question: -s
Merci beaucoup Robert, j'essaierai votre solution et utilisera automapper, si ça se termine bien je vous en dois un gros :)
Le problème est peut-être que votre entité ne provient pas à l'origine de la base de données, donc DbContext ne connaît pas l'ancienne valeur des propriétés. Cela peut donc être simplement résolu en extrayant l'entité de la base de données et en la mettant à jour, soit manuellement comme dans l'exemple de code ci-dessous, soit en utilisant Automapper.
public async Task<IActionResult> OnPostEditAsync(int id, Product value) { var product = dbContext.Products.Find(p => p.Id == id); //Map exisiting product with new values (idealy with Automapper) product.Name = value.Name; product.Price = value.Price; product.Description = value.Description; dbContext.Update(product); await dbContext.SaveChangesAsync(); }
Je pense que "DetachEntities" est la source de votre problème. Le détachement supprimera les entités du suivi des modifications et par conséquent, la valeur d'origine et les valeurs modifiées ne changeront pas.
D'accord. Mais j'ai essayé sans cela et j'obtiens une erreur lorsque j'appelle Update (): «L'entité est déjà en cours de suivi». Donc, sans cela, je ne peux même pas enregistrer mes modifications
Il vaut la peine de jeter un œil à cette bibliothèque pour voir comment cela a été fait - ou même simplement y faire référence et l'utiliser directement - cela vous évitera probablement des maux de tête: github.com/Arch/AutoHistory - Pour vous donner une réponse plus précise à votre problème particulier - nous aurions probablement besoin de voir plus de code, en contexte pour comprendre ce qui se passe
Je vais jeter un œil à cette bibliothèque. Merci beaucoup! Aussi, je vais essayer de fournir un petit projet reproduisant le problème après le travail
La question a été mise à jour avec un exemple de projet pour illustrer le problème
Y a-t-il une raison pour laquelle vous devez appeler la mise à jour? Lorsque vous effectuez des modifications avec EF, tout ce que vous avez à faire est de modifier les champs de votre entité et d'appeler savechanges. Une mise à jour n'est pas nécessaire pour effectuer une mise à jour.
Je viens de supprimer l'appel
Update ()
mais maintenant l'entité n'est pas mise à jour..AsNoTracking ()
est commentéSupprimer le détachement aussi :)
Supprimé, ne fonctionne toujours pas .. :(