97 votes

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

J'obtiens l'erreur suivante lorsque j'essaie 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 l'ObjectStateManager. L'ObjectStateManager ne peut pas suivre plusieurs objets avec la même clé.

Y a-t-il un moyen d'obtenir quelque chose du type :

context.IsAttachedTo(...)

A la vôtre !

Edit :

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

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

Comment puis-je 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 quelque chose d'autre pour cet utilisateur où j'utilise la même méthode et essaie d'attacher un faux User objet. Cela échoue parce que j'ai précédemment attaché cet objet utilisateur fictif. Comment puis-je vérifier cela ?

63voto

joshcomley Points 9308

Voici ce que j'ai obtenu, 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 parce que c'est juste comme si context.AttachTo(...) sauf que vous pouvez utiliser l'astuce de l'identification que j'ai citée plus haut à chaque fois. Vous vous retrouvez soit avec l'objet précédemment attaché, soit avec votre propre objet attaché. Appeler CreateEntityKey sur le contexte permet de s'assurer qu'il est agréable et générique et qu'il fonctionnera même avec des clés composites sans aucun codage supplémentaire (car EF peut déjà le faire pour nous !).

Editer, douze ans plus tard (Déc 2021)... oof !

Voici ce que j'utilise dans EF Core :

public static class EfExtensions
{
    public static T AttachToOrGet<T>(this DbContext context, Func<T,bool> predicate, Func<T> factory)
        where T : class, new()
    {
        var match = context.Set<T>().Local.FirstOrDefault(predicate);
        if (match == null)
        {
            match = factory();
            context.Attach(match);
        }

        return match;
    }
}

Uso:

var item = db.AttachToOrGet(_ => _.Id == someId, () => new MyItem { Id = someId });

Vous pourriez remanier cela pour qu'il fonctionne avec la clé de l'entité, mais c'est suffisant pour que tout le monde se lance !

0 votes

J'avais un problème similaire, et ceci vient de le résoudre - génial, merci ! +1

4 votes

Ce serait encore mieux si le paramètre chaîne était remplacé par une fonction de sélection pour la collection à laquelle l'entité appartient.

1 votes

Une idée de la raison (T)entry.Entity renvoie parfois un résultat nul ?

62voto

Mosh Points 1436

Une approche plus simple est :

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);

1 votes

Cela a fonctionné pour moi, mais j'ai dû utiliser "EntityState.Detached" au lieu de "detached"...

25 votes

Hmm a essayé votre solution, mais pour moi isDetached est vrai, mais toujours la même erreur quand j'essaie d'attacher l'entrée au contexte

0 votes

Excellente évaluation. Il a même fonctionné avec mon Generic Repository.

18voto

Jason Points 125291

Essayez cette méthode d'extension (non testée et improvisée) :

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 édition, vous pourriez avoir besoin d'utiliser la surcharge suivante, qui accepte un fichier EntityKey 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 construire un EntityKey dans votre situation, utilisez ce qui suit comme ligne directrice :

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

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

0 votes

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. p.s. vous voulez bool et non boolean, et static, mais à part cela une méthode d'extension assez impressionnante !

0 votes

@joshcomley : Je pense que vous pouvez aborder l'utilisation d'une surcharge de TryGetObjectStateEntry qui accepte un EntityKey au lieu d'un object . J'ai édité en conséquence. Faites-moi savoir si cela ne vous aide pas et nous retournerons à la planche à dessin.

0 votes

Ah, je viens de voir ça - je travaillais sur une solution décrite dans une réponse que je viens de poster. +1 pour votre aide et vos conseils !

7voto

Daniel Elliott Points 13253

En utilisant 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

0voto

Lawrence Points 97

Cela ne répond pas directement à la question de l'OP mais c'est ainsi que j'ai résolu la mienne.

C'est pour ceux qui utilisent DbContext au lieu de ObjectContext .

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

Méthode DbSet.Find :

Trouve 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, alors elle est retournée immédiatement sans faire de demande au magasin. Sinon, une demande est faite au magasin pour une entité avec les valeurs de la clé primaire données et cette entité, si elle est trouvée, est attachée au contexte et renvoyée. retournée. Si aucune entité n'est trouvée dans le contexte ou le magasin, null est renvoyé.

Fondamentalement, il retourne l'objet attaché de l'objet donné. primaryKey il suffit donc d'appliquer les changements sur l'objet retourné pour conserver la bonne instance.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X