Est-il possible de vérifier si un objet est déjà attaché à un contexte de données dans Entity Framework?

86

J'obtiens l'erreur suivante en essayant d'attacher un objet qui est déjà attaché à un contexte donné via context.AttachTo(...):

Un objet avec la même clé existe déjà dans ObjectStateManager. ObjectStateManager ne peut pas suivre plusieurs objets avec la même clé.

Existe-t-il un moyen de réaliser quelque chose du genre:

context.IsAttachedTo(...)

À votre santé!

Éditer:

La méthode d'extension décrite par Jason est proche, mais elle ne fonctionne pas pour ma situation.

J'essaie de faire du travail en utilisant la méthode décrite dans la réponse à une autre question:

Comment supprimer une ou plusieurs lignes de ma table en utilisant Linq to Entities * sans * récupérer les lignes au préalable?

Mon code ressemble un peu à ceci:

var user = new User() { Id = 1 };
context.AttachTo("Users", user);
comment.User = user;
context.SaveChanges();

Cela fonctionne bien, sauf lorsque je fais autre chose pour cet utilisateur où j'utilise la même méthode et j'essaye d'attacher un Userobjet factice . Cela échoue car j'ai précédemment attaché cet objet utilisateur factice. Comment puis-je vérifier cela?

Joshcomley
la source

Réponses:

57

Voici ce avec quoi je me suis retrouvé, qui fonctionne très bien:

public static void AttachToOrGet<T>(this ObjectContext context, string entitySetName, ref T entity)
    where T : IEntityWithKey
{
    ObjectStateEntry entry;
    // Track whether we need to perform an attach
    bool attach = false;
    if (
        context.ObjectStateManager.TryGetObjectStateEntry
            (
                context.CreateEntityKey(entitySetName, entity),
                out entry
            )
        )
    {
        // Re-attach if necessary
        attach = entry.State == EntityState.Detached;
        // Get the discovered entity to the ref
        entity = (T)entry.Entity;
    }
    else
    {
        // Attach for the first time
        attach = true;
    }
    if (attach)
        context.AttachTo(entitySetName, entity);
}

Vous pouvez l'appeler comme suit:

User user = new User() { Id = 1 };
II.AttachToOrGet<Users>("Users", ref user);

Cela fonctionne très bien car c'est comme context.AttachTo(...)si vous pouviez utiliser l'astuce d'identification que j'ai citée ci-dessus à chaque fois. Vous vous retrouvez avec l'objet précédemment attaché ou votre propre objet attaché. L'appel CreateEntityKeysur le contexte garantit qu'il est agréable et générique et qu'il fonctionnera même avec des clés composites sans codage supplémentaire (car EF peut déjà le faire pour nous!).

Joshcomley
la source
J'avais un problème similaire, et cela vient de résoudre le mien - brillant, bravo! +1
RPM1984
4
Ce serait encore mieux lorsque le paramètre de chaîne est remplacé par une fonction de sélection pour la collection à laquelle appartient l'entité.
Jasper
1
Une idée pourquoi (T)entry.Entityretourne parfois null?
Tr1stan
1
Je ne peux pas comprendre à quoi je devrais définir mon entitySetName. Je reçois une exception. Je suis vraiment frustré parce que tout ce que je veux faire est de supprimer un utilisateur avec lequel je ne veux pas avoir à faire face à tant de non-sens caché qui fait exploser mon application.
Julie
1
si Tc'est déjà le cas IEntityWithKey, ne pouvez-vous pas simplement utiliser sa entity.EntityKeypropriété plutôt que de la reconstruire ou avoir besoin de deviner / fournir le EntitySetName?
drzaus
54

Une approche plus simple est:

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);
Mosh
la source
1
Cela a fonctionné pour moi, j'ai juste eu à utiliser "EntityState.Detached" au lieu de simplement "détaché" ...
Marcelo Myara
22
Hmm a essayé votre solution, mais pour moi, isDetached est vrai, mais toujours la même erreur lorsque j'essaie d'attacher une entrée au contexte
Prokurors
Excellente approche. Même cela a fonctionné avec mon référentiel générique.
ATHER
5
@Prokurors J'ai récemment appris la raison pour laquelle la vérification de Mosh n'est pas toujours suffisante pour éviter l'erreur, c'est que .Include()les propriétés de navigation ed sont également tentées d'être attachées lorsque vous appelez .Attachou définissez son EntityStatesur EntityState.Unchanged- et elles seront en conflit si l'une des entités fait référence au même entité. Je n'ai pas compris comment attacher uniquement l'entité de base, j'ai donc dû reconcevoir un peu le projet, pour utiliser des contextes séparés pour chaque "transaction commerciale", comme il a été conçu pour. Je vais le noter pour les projets futurs.
Aske B.
18

Essayez cette méthode d'extension (elle n'est pas testée et prête à l'emploi):

public static bool IsAttachedTo(this ObjectContext context, object entity) {
    if(entity == null) {
        throw new ArgumentNullException("entity");
    }
    ObjectStateEntry entry;
    if(context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry)) {
        return (entry.State != EntityState.Detached);
    }
    return false;
}

Compte tenu de la situation que vous décrivez dans votre modification, vous devrez peut-être utiliser la surcharge suivante qui accepte EntityKeyun objet au lieu d'un objet:

public static bool IsAttachedTo(this ObjectContext, EntityKey key) {
    if(key == null) {
        throw new ArgumentNullException("key");
    }
    ObjectStateEntry entry;
    if(context.ObjectStateManager.TryGetObjectStateEntry(key, out entry)) {
        return (entry.State != EntityState.Detached);
    }
    return false;
}

Pour créer un EntityKeydans votre situation, utilisez ce qui suit comme guide:

EntityKey key = new EntityKey("MyEntities.User", "Id", 1);

Vous pouvez obtenir le à EntityKeypartir d'une instance existante de Useren utilisant la propriété User.EntityKey(à partir de l'interface IEntityWithKey).

Jason
la source
C'est très bien, mais cela ne fonctionne pas pour moi dans ma situation ... Je vais mettre à jour la question avec des détails. ps vous voulez bool pas booléen, et statique, mais autre que cette méthode d'extension assez impressionnante!
joshcomley
@joshcomley: Je pense que vous pouvez résoudre en utilisant une surcharge de TryGetObjectStateEntryqui accepte un EntityKeyau lieu d'un object. J'ai modifié en conséquence. Faites-moi savoir si cela ne vous aide pas et nous reviendrons à la planche à dessin.
jason
Ah je viens de voir ceci - je travaillais sur une solution décrite dans une réponse que je viens de publier. +1 pour votre aide et vos conseils !!
joshcomley
Toutes les idées pourquoi j'obtiens une erreur, les détails sont ici stackoverflow.com/questions/6653050/…
Joper
6

À l'aide de la clé d'entité de l'objet que vous essayez de vérifier:

var entry = context.ObjectStateManager.GetObjectStateEntry("EntityKey");
if (entry.State == EntityState.Detached)
{
  // Do Something
}

La gentillesse,

Dan

Daniel Elliott
la source
0

Cela ne répond pas directement à la question des OP mais c'est ainsi que j'ai résolu le mien.

Ceci est pour ceux qui utilisent à la DbContextplace de ObjectContext.

    public TEntity Retrieve(object primaryKey)
    {
        return DbSet.Find(primaryKey);
    }

Méthode DbSet.Find :

Recherche une entité avec les valeurs de clé primaire données. Si une entité avec les valeurs de clé primaire données existe dans le contexte, elle est renvoyée immédiatement sans faire de demande au magasin. Sinon, une demande est faite au magasin pour une entité avec les valeurs de clé primaire données et cette entité, si elle est trouvée, est attachée au contexte et renvoyée. Si aucune entité n'est trouvée dans le contexte ou dans le magasin, la valeur null est renvoyée.

Fondamentalement, il renvoie l'objet attaché du donné, primaryKeyil vous suffit donc d'appliquer les modifications sur l'objet retourné pour conserver la bonne instance.

Lawrence
la source