1
votes

ChangeLog OriginalValue identique à CurrentValue sur EF Core Update Entity

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:

  • .NET Core 2.2.0
  • EF Core 2.2.3

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:

  1. J'obtiens l'entité de la base de données comme ceci

    _context.Anomalies .Include (a => a.Asset) .FirstOrDefaultAsync (a => a.Id == anomalyId)

  2. Modifiez l'entité dans le front-end, puis faites une requête PUT pour la mettre à jour;

  3. 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.

  4. 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 Avec suivi sur la GAUCHE <====> Sans suivi sur la DROITE

Tout conseil, opinion, nouvelle idée, commentaire est le bienvenu. Merci!


9 commentaires

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 .. :(


3 Réponses :


1
votes

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();


7 commentaires

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



2
votes

 entrez la description de l'image ici

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; -)

https://github.com/AutoMapper/AutoMapper


4 commentaires

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 :)



0
votes

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();  
}  


0 commentaires