Ist es möglich zu überprüfen, ob ein Objekt bereits in Entity Framework an einen Datenkontext angehängt ist?

86

Beim Versuch, ein Objekt anzuhängen, das bereits an einen bestimmten Kontext angehängt ist, wird folgende Fehlermeldung angezeigt context.AttachTo(...):

Im ObjectStateManager ist bereits ein Objekt mit demselben Schlüssel vorhanden. Der ObjectStateManager kann nicht mehrere Objekte mit demselben Schlüssel verfolgen.

Gibt es eine Möglichkeit, etwas in der Art zu erreichen:

context.IsAttachedTo(...)

Prost!

Bearbeiten:

Die von Jason beschriebene Erweiterungsmethode ist nah, funktioniert aber in meiner Situation nicht.

Ich versuche, mit der in der Antwort auf eine andere Frage beschriebenen Methode zu arbeiten:

Wie lösche ich eine oder mehrere Zeilen mit Linq to Entities * aus meiner Tabelle, ohne * zuerst die Zeilen abzurufen?

Mein Code sieht ein bisschen so aus:

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

Dies funktioniert einwandfrei, außer wenn ich etwas anderes für diesen Benutzer mache, bei dem ich dieselbe Methode verwende und versuche, ein Dummy- UserObjekt anzuhängen . Dies schlägt fehl, weil ich dieses Dummy-Benutzerobjekt zuvor angehängt habe. Wie kann ich das überprüfen?

Joshcomley
quelle

Antworten:

57

Folgendes habe ich erreicht, was sehr gut funktioniert:

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

Sie können es wie folgt nennen:

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

Dies funktioniert sehr gut, da es nur so ist, als ob context.AttachTo(...)Sie den oben genannten ID-Trick jedes Mal verwenden können. Am Ende wird entweder das zuvor angehängte Objekt oder Ihr eigenes Objekt angehängt. Das Aufrufen CreateEntityKeydes Kontexts stellt sicher, dass er nett und allgemein ist und auch mit zusammengesetzten Schlüsseln ohne weitere Codierung funktioniert (da EF dies bereits für uns tun kann!).

Joshcomley
quelle
Ich hatte ein ähnliches Problem, und das löste gerade mein Problem - brillant, Prost! +1
RPM1984
4
Wäre noch besser, wenn der Zeichenfolgenparameter durch eine Auswahlfunktion für die Sammlung ersetzt wird, zu der die Entität gehört.
Jasper
1
Irgendeine Idee, warum (T)entry.Entitymanchmal null zurückgegeben wird?
Tr1stan
1
Ich kann nicht herausfinden, auf was ich meinen entitySetName setzen soll. Ich bekomme immer eine Ausnahme. Ich bin wirklich frustriert, weil ich nur einen Benutzer löschen möchte, mit dem ich mich nicht mit so viel verstecktem Unsinn auseinandersetzen möchte, der meine Anwendung in die Luft jagt.
Julie
1
Wenn dies Tbereits der Fall ist IEntityWithKey, können Sie die entity.EntityKeyEigenschaft nicht einfach verwenden, anstatt sie zu rekonstruieren, oder müssen Sie die erraten / bereitstellen EntitySetName?
Drzaus
53

Ein einfacherer Ansatz ist:

 bool isDetached = context.Entry(user).State == EntityState.Detached;
 if (isDetached)
     context.Users.Attach(user);
Mosh
quelle
1
Dies funktionierte für mich, nur musste ich "EntityState.Detached" verwenden, anstatt nur "losgelöst" ...
Marcelo Myara
22
Hmm hat Ihre Lösung ausprobiert, aber für mich ist "Getrennt" wahr, aber immer noch der gleiche Fehler, wenn ich versuche, einen Eintrag an den Kontext
anzuhängen
Hervorragender Ansatz. Sogar es hat mit meinem generischen Repository funktioniert.
ATHER
4
@Prokurors Ich habe kürzlich erfahren, dass die Überprüfung von Mosh nicht immer ausreicht, um den Fehler zu verhindern. Es wird .Include()auch versucht, angehängte Navigationseigenschaften anzuhängen, wenn Sie sie aufrufen .Attachoder EntityStateauf setzen EntityState.Unchanged- und sie treten in Konflikt, wenn sich eine der Entitäten auf die bezieht gleiche Entität. Ich habe nicht herausgefunden, wie man nur die Basisentität anfügt, daher musste ich das Projekt ein wenig neu gestalten, um separate Kontexte für jede "Geschäftstransaktion" zu verwenden, für die sie entworfen wurde. Ich werde dies für zukünftige Projekte zur Kenntnis nehmen.
Fragen Sie B.
18

Probieren Sie diese Erweiterungsmethode aus (dies ist ungetestet und ohne Manschette):

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

In Anbetracht der Situation, die Sie in Ihrer Bearbeitung beschreiben, müssen Sie möglicherweise die folgende Überladung verwenden, die EntityKeyein Objekt anstelle eines Objekts akzeptiert :

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

EntityKeyVerwenden Sie Folgendes als Richtlinie, um eine in Ihrer Situation zu erstellen :

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

Sie können das EntityKeyvon einer vorhandenen Instanz von Userabrufen, indem Sie die Eigenschaft User.EntityKey(von der Schnittstelle IEntityWithKey) verwenden.

Jason
quelle
Das ist sehr gut, aber es funktioniert in meiner Situation nicht ... Ich werde die Frage mit Details aktualisieren. ps du willst bool nicht boolesch und statisch, aber anders als diese ziemlich tolle Erweiterungsmethode!
Joshcomley
@joshcomley: Ich denke, dass Sie mit einer Überladung adressieren können TryGetObjectStateEntry, die eine EntityKeyanstelle einer akzeptiert object. Ich habe entsprechend bearbeitet. Lassen Sie mich wissen, wenn dies nicht hilft, und wir kehren zum Zeichenbrett zurück.
Jason
Ich habe das gerade gesehen - ich habe an einer Lösung gearbeitet, die in einer Antwort beschrieben ist, die ich gerade gepostet habe. +1 für deine Hilfe und Hinweise !!
Joshcomley
Irgendwelche Ideen, warum ich einen Fehler erhalte
Joper
6

Verwenden Sie den Entitätsschlüssel des Objekts, das Sie überprüfen möchten:

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

Freundlichkeit,

Dan

Daniel Elliott
quelle
0

Dies beantwortet die Frage des OP nicht direkt, aber so habe ich meine gelöst.

Dies ist für diejenigen, die DbContextanstelle von verwenden ObjectContext.

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

DbSet.Find-Methode :

Findet eine Entität mit den angegebenen Primärschlüsselwerten. Wenn eine Entität mit den angegebenen Primärschlüsselwerten im Kontext vorhanden ist, wird sie sofort zurückgegeben, ohne eine Anforderung an das Geschäft zu senden. Andernfalls wird eine Anforderung an den Speicher für eine Entität mit den angegebenen Primärschlüsselwerten gestellt, und diese Entität wird, falls gefunden, an den Kontext angehängt und zurückgegeben. Wenn im Kontext oder im Speicher keine Entität gefunden wird, wird null zurückgegeben.

Grundsätzlich wird das angehängte Objekt des angegebenen Objekts zurückgegeben, primaryKeysodass Sie nur die Änderungen auf das zurückgegebene Objekt anwenden müssen, um die richtige Instanz beizubehalten.

Lawrence
quelle