web-dev-qa-db-fra.com

EF Core «une autre instance est déjà en cours de suivi»

J'ai un problème pour mettre à jour une entité dans .Net Core 2.2.0 à l'aide d'EF Core 2.2.3.

Une erreur s'est produite lors de l'enregistrement des modifications. Détails de l'erreur: l'instance du type d'entité 'Asset' ne peut pas être suivie car une autre instance avec la même valeur de clé pour {'Id'} est déjà en cours de suivi. Lorsque vous attachez des entités existantes, assurez-vous qu'une seule instance d'entité avec une valeur de clé donnée est attachée. Pensez à utiliser

Voici comment le contexte DB est enregistré:

services.AddDbContext (options =>

options.UseSqlServer(Configuration.GetConnectionString("DbConnection")), ServiceLifetime.Scoped);

La durée de vie de Scoped est définie par défaut mais je l'ai écrite pour être plus facile à comprendre.

L'objet Anomaly est obtenu comme ceci:

public IQueryable<Anomaly> GetAll()
    {return _context.Anomalies.Include(a => a.Asset).Include(a => a.Level)
}

public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
{
    var anomaly = await GetAll()
        .FirstOrDefaultAsync(a => a.Id == anomalyId);

    return anomaly;
}

Et la méthode Update() ressemble à ceci:

using (var transaction = _context.Database.BeginTransaction())
{
    try
    {
        _context.Anomalies.Update(anomaly);
        _context.SaveChanges();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        throw;
    }
}

Il contient quelques vérifications avant cette transaction, mais pas suffisamment pertinentes dans ce contexte.

C'est là que j'obtiens l'erreur avec l'instance est déjà en cours de suivi. Je ne comprends pas comment cela se produit. Si le contexte est Scoped, alors

... "une nouvelle instance du service sera créée pour chaque portée", dans ce cas, pour chaque demande

Si mon contexte sur la demande PUT est différent du contexte de la demande GET, comment l'entité est-elle déjà suivie? Comment cela fonctionne-t-il aux niveaux les plus élémentaires?

La seule façon de le faire fonctionner est de définir l'état de toutes les entrées de ChangeTracker à EntityState.Detached. Ensuite, cela fonctionne .. mais cela n'a aucun sens, du moins à ma connaissance actuelle ..

J'ai trouvé cette question mais sans réponse valide, uniquement avec des solutions de contournement et des hypothèses sur la façon dont EF effectue le suivi.


[~ # ~] update [~ # ~] Voici un lien vers bitbucket avec un exemple recréant ce problème: EF Core Update Sample

J'ai sérialisé les objets récupérés du contexte.

Avec suivi à gauche <====> sans suivi à droite With Tracking on the LEFT <====> With NO tracking on the RIGHT

8
Simonca

Par défaut, lorsque vous récupérez des entités, elles sont suivies et puisqu'elles sont suivies, vous pouvez simplement appeler SaveChanges et non appeler Update. Vous pouvez également récupérer des entités sans les suivre à l'aide de .AsNoTracking ()

appeler Update est nécessaire s'il n'est pas déjà suivi, donc si vous utilisez AsNoTracking, vous devez utiliser Update avant SaveChanges

public IQueryable<Anomaly> GetAll()
{    return _context.Anomalies
    .Include(a => a.Asset)
    .Include(a => a.Level);
}

public async Task<Anomaly> GetAnomaly(int anomalyId, User user)
{
    var anomaly = await GetAll()
        .AsNoTracking()
        .FirstOrDefaultAsync(a => a.Id == anomalyId);

    return anomaly;
}

Vous pouvez également vérifier si l'entité est suivie pour savoir s'il faut appeler Update ou non:

using (var transaction = _context.Database.BeginTransaction())
{
    try
    {

        bool tracking = _context.ChangeTracker.Entries<Anomaly>().Any(x => x.Entity.Id == anomaly.Id);
        if (!tracking)
        {
            _context.Anomalies.Update(anomaly);
        }

        _context.SaveChanges();

        transaction.Commit();
    }
    catch (Exception ex)
    {
        transaction.Rollback();
        throw;
    }
}
5
Joe Audette

J'essayais de mettre à jour les mêmes données sans le remarquer. Il m'a fallu dix heures pour le réaliser. Si vous avez des valeurs en double comme la mienne, je suggère de supprimer ces données ... La personne qui lit cette réponse a peut-être essayé toutes les solutions sur Internet comme moi, a ruiné le projet, a bloqué la même erreur et doublon omis données comme moi. Vous n'êtes pas seul mon ami.

model.GroupBy(gb => gb.ID).Select(s=>s.First()).ToList();//remove duplicates!!!!!
1
Haktan Enes Biçer

Donc, à la fin, nous avons fini par utiliser une méthode UpdateEntity personnalisée pour enregistrer nos modifications sur certaines entités. Cette méthode passe par chaque propriété, propriété de navigation et propriété de collection d'une entité, s'assure qu'il n'y a pas de chaîne et met à jour les objets une seule fois.

Cela fonctionne pour les gros objets et nous ne l'utilisons que dans ces cas. Pour les opérations simples, nous continuons à utiliser la simple mise à jour

Voici le lien vers un dépôt bitbucket avec le code source

Pour utiliser cette méthode, vous devrez d'abord obtenir l'entité db. Appelez ensuite la méthode UpdateEntity avec l'entité db et votre entité reçue par la demande.

J'espère que ça vous aide comme ça m'a aidé. À votre santé!

0
Simonca

J'ai eu le même problème mais j'essayais d'ajouter une deuxième ligne à la même entité. Dans mon cas, c'était parce que je supposais que la clé primaire était une identité int et générée automatiquement. Comme je ne lui assignais aucune valeur, la deuxième ligne avait le même ID, qui est cero default. Leçon apprise, lorsque vous voyez cette erreur lors d'une opération Add () dans EF, assurez-vous que votre clé est IDENTITY si c'est votre intention.

0
Javier Alvarez