È possibile verificare se un oggetto è già collegato a un contesto dati in Entity Framework?


86

Ricevo il seguente errore quando provo a collegare un oggetto che è già collegato a un determinato contesto tramite context.AttachTo(...):

Un oggetto con la stessa chiave esiste già in ObjectStateManager. ObjectStateManager non può tenere traccia di più oggetti con la stessa chiave.

C'è un modo per ottenere qualcosa sulla falsariga di:

context.IsAttachedTo(...)

Saluti!

Modificare:

Il metodo di estensione descritto da Jason è vicino, ma non funziona per la mia situazione.

Sto provando a fare un po 'di lavoro usando il metodo descritto nella risposta a un'altra domanda:

Come faccio a eliminare una o più righe dalla mia tabella utilizzando Linq to Entities * senza * recuperare prima le righe?

Il mio codice assomiglia un po 'a questo:

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

Funziona bene, tranne quando faccio qualcos'altro per quell'utente in cui utilizzo lo stesso metodo e provo ad allegare un Useroggetto fittizio . Questo non riesce perché ho precedentemente collegato quell'oggetto utente fittizio. Come posso verificarlo?

Risposte:


57

Ecco cosa ho ottenuto, che funziona molto bene:

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

Puoi chiamarlo come segue:

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

Funziona molto bene perché è proprio come context.AttachTo(...)tranne che puoi usare il trucco dell'ID che ho citato sopra ogni volta. Finisci con l'oggetto precedentemente collegato o il tuo oggetto allegato. Richiamare CreateEntityKeyil contesto si assicura che sia carino e generico e funzionerà anche con chiavi composte senza ulteriore codifica (perché EF può già farlo per noi!).


Stavo avendo un problema simile, e questo ha appena risolto il mio: brillante, applausi! +1
RPM1984

4
Sarebbe ancora meglio quando il parametro stringa viene sostituito da una funzione di selezione per la raccolta a cui appartiene l'entità.
Jasper

1
Qualche idea sul perché a (T)entry.Entityvolte restituisce null?
Tr1stan

1
Non riesco a capire su cosa dovrei impostare il mio entitySetName. Continuo a ricevere un'eccezione. Sono davvero frustrato perché tutto ciò che voglio fare è eliminare un utente che non voglio avere a che fare con così tanto non senso nascosto che fa saltare in aria la mia applicazione.
Julie

1
se lo Tè già IEntityWithKey, non puoi semplicemente usare la sua entity.EntityKeyproprietà piuttosto che ricostruirla o devi indovinare / fornire il EntitySetName?
drzaus

54

Un approccio più semplice è:

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

1
Questo ha funzionato per me, dovevo semplicemente usare "EntityState.Detached" invece di "disconnesso" ...
Marcelo Myara

22
Hmm ha provato la tua soluzione, ma per me isDetached è vero, ma rimane lo stesso errore quando provo ad allegare la voce al contesto
Prokurors

Eccellente valutazione. Anche ha funzionato con il mio archivio generico.
ATHER

5
@Prokurors Di recente ho appreso il motivo per cui il controllo di Mosh non è sempre sufficiente per prevenire l'errore, è che anche .Include()le proprietà di navigazione "ed" vengono tentate di essere allegate quando si chiama .Attacho si imposta EntityStatesu EntityState.Unchanged- e entreranno in conflitto se una qualsiasi delle entità si riferisce al stessa entità. Non ho capito come collegare solo l'entità di base, quindi ho dovuto ridisegnare un po 'il progetto, per utilizzare contesti separati per ogni "transazione commerciale", come era stato progettato per. Lo prenderò nota per i progetti futuri.
Aske B.

18

Prova questo metodo di estensione (questo non è testato e improvvisato):

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

Data la situazione che descrivi nella tua modifica, potresti dover utilizzare il seguente overload che accetta un EntityKeyinvece di un oggetto:

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

Per creare una EntityKeysituazione nella tua situazione, utilizza quanto segue come linea guida:

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

È possibile ottenere EntityKeyda un'istanza esistente di Userutilizzando la proprietà User.EntityKey(dall'interfaccia IEntityWithKey).


Questo è molto buono, ma non funziona per me nella mia situazione ... Aggiornerò la domanda con i dettagli. ps vuoi bool non booleano e statico, ma a parte questo fantastico metodo di estensione!
joshcomley

@ joshcomley: penso che tu possa affrontare usando un sovraccarico di TryGetObjectStateEntryche accetta un EntityKeyinvece di un object. Ho modificato di conseguenza. Fammi sapere se questo non aiuta e torneremo al tavolo da disegno.
jason

Ah, ho appena visto questo: stavo lavorando a una soluzione delineata in una risposta che ho appena pubblicato. +1 per il tuo aiuto e suggerimenti !!
joshcomley

Qualche idea sul perché ricevo un errore, i dettagli sono qui stackoverflow.com/questions/6653050/…
Joper

6

Utilizzando la chiave di entità dell'oggetto che stai tentando di controllare:

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

Gentilezza,

Dan


0

Questo non risponde direttamente alla domanda dell'OP, ma è così che ho risolto la mia.

Questo è per coloro che usano DbContextinvece di ObjectContext.

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

Metodo DbSet.Find :

Trova un'entità con i valori di chiave primaria forniti. Se nel contesto esiste un'entità con i valori di chiave primaria specificati, viene restituita immediatamente senza effettuare una richiesta al negozio. In caso contrario, viene effettuata una richiesta all'archivio per un'entità con i valori di chiave primaria specificati e questa entità, se trovata, viene allegata al contesto e restituita. Se nessuna entità viene trovata nel contesto o nell'archivio, viene restituito null.

Fondamentalmente, restituisce l'oggetto allegato del dato, primaryKeyquindi è sufficiente applicare le modifiche sull'oggetto restituito per mantenere l'istanza giusta.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.