web-dev-qa-db-fra.com

L'instance du type d'entité ne peut pas être suivie car une autre instance de ce type avec la même clé est déjà suivie

J'ai un objet de service Update 

public bool Update(object original, object modified)
{
    var originalClient = (Client)original;
    var modifiedClient = (Client)modified;
    _context.Clients.Update(originalClient); //<-- throws the error
    _context.SaveChanges();
    //Variance checking and logging of changes between the modified and original
}

C'est ici que j'appelle cette méthode:

public IActionResult Update(DetailViewModel vm)
{
    var originalClient = (Client)_service.GetAsNoTracking(vm.ClientId);
    var modifiedClient = (Client)_service.Fetch(vm.ClientId.ToString());
    // Changing the modifiedClient here
    _service.Update(originalClient, modifiedClient);
}

Voici la méthode GetAsNotTracking:

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObject(id).AsNoTracking().FirstOrDefault();
}

Fetch méthode:

public object Fetch(string id)
{
   long fetchId;
   long.TryParse(id, out fetchId);
   return GetClientQueryableObject(fetchId).FirstOrDefault();
}

GetClientQueryableObject:

private Microsoft.Data.Entity.Query.IIncludableQueryable<Client, ActivityType> GetClientQueryableObject(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .Include(x => x.Industry)
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType);
 }

Des idées?

J'ai regardé les articles/discussions suivants. En vain: ASP.NET GitHub Numéro 3839

METTRE À JOUR:

Voici les modifications apportées à GetAsNoTracking:

public Client GetAsNoTracking(long id)
{
    return GetClientQueryableObjectAsNoTracking(id).FirstOrDefault();
}

GetClientQueryableObjectAsNoTracking:

private IQueryable<Client> GetClientQueryableObjectAsNoTracking(long searchId)
{
    return _context.Clients
        .Where(x => x.Id == searchId)
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.BusinessUnit)
        .AsNoTracking()
        .Include(x => x.Opportunities)
        .ThenInclude(x => x.Probability)
        .AsNoTracking()
        .Include(x => x.Industry)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.User)
        .AsNoTracking()
        .Include(x => x.Activities)
        .ThenInclude(x => x.ActivityType)
        .AsNoTracking();
}
19
Rijnhardt

Sans remplacer le système de piste EF, vous pouvez également détacher l'entrée «locale» et attacher votre entrée mise à jour avant de sauvegarder:

// 
var local = _context.Set<YourEntity>()
    .Local
    .FirstOrDefault(entry => entry.Id.Equals(entryId));

// check if local is not null 
if (!local.IsNull()) // I'm using a extension method
{
    // detach
    _context.Entry(local).State = EntityState.Detached;
}
// set Modified flag in your entry
_context.Entry(entryToUpdate).State = EntityState.Modified;

// save 
_context.SaveChanges();

UPDATE: Pour éviter la redondance du code, vous pouvez utiliser une méthode d'extension:

public static void DetachLocal<T>(this DbContext context, T t, string entryId) 
    where T : class, IIdentifier 
{
    var local = context.Set<T>()
        .Local
        .FirstOrDefault(entry => entry.Id.Equals(entryId));
    if (!local.IsNull())
    {
        context.Entry(local).State = EntityState.Detached;
    }
    context.Entry(t).State = EntityState.Modified;
}

Mon interface IIdentifier a juste une propriété de chaîne Id.

Quelle que soit votre entité, vous pouvez utiliser cette méthode dans votre contexte:

_context.DetachLocal(tmodel, id);
_context.SaveChanges();
14
Andres Talavera

Il semble que vous souhaitiez simplement suivre les modifications apportées au modèle, et non garder réellement un modèle non suivi en mémoire. Puis-je suggérer une autre approche qui éliminerait complètement le problème?

EF suivra automatiquement les modifications pour vous. Pourquoi ne pas utiliser cette logique intégrée?

Survolez SaveChanges() dans votre DbContext.

    public override int SaveChanges()
    {
        foreach (var entry in ChangeTracker.Entries<Client>())
        {
            if (entry.State == EntityState.Modified)
            {
                // Get the changed values.
                var modifiedProps = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).GetModifiedProperties();
                var currentValues = ObjectStateManager.GetObjectStateEntry(entry.EntityKey).CurrentValues;
                foreach (var propName in modifiedProps)
                {
                    var newValue = currentValues[propName];
                    //log changes
                }
            }
        }

        return base.SaveChanges();
    }

De bons exemples peuvent être trouvés ici:

Entity Framework 6: audit/suivi des modifications

Implémentation du journal d'audit/Historique des modifications avec MVC et Entity Framework

EDIT:Client peut facilement être remplacé par une interface. Disons ITrackableEntity. Ainsi, vous pouvez centraliser la logique et consigner automatiquement toutes les modifications apportées à toutes les entités implémentant une interface spécifique. L'interface elle-même n'a pas de propriétés spécifiques.

    public override int SaveChanges()
    {
        foreach (var entry in ChangeTracker.Entries<ITrackableClient>())
        {
            if (entry.State == EntityState.Modified)
            {
                // Same code as example above.
            }
        }

        return base.SaveChanges();
    }

Jetez également un coup d'œil à l'excellente suggestion de eranga de s'abonner au lieu de remplacer SaveChanges ().

4
smoksnes

Dans mon cas, la colonne id de la table n'a pas été définie en tant que colonne d'identité.

0
daviesdoesit

Si vos données ont changé une fois, vous remarquerez de ne pas tracer la table.Par exemple, un identifiant de mise à jour de table ([clé]) utilisant tigger.Si vous tracez, vous obtiendrez le même id et le problème.

0
menxin