ASP.NET MVC - Das Anhängen einer Entität vom Typ 'MODELNAME' ist fehlgeschlagen, da eine andere Entität desselben Typs bereits denselben Primärschlüsselwert hat

121

Kurz gesagt, die Ausnahme wird während des POSTing-Wrapper-Modells ausgelöst und der Status eines Eintrags in "Geändert" geändert. Vor dem Ändern des Status wird der Status auf "Abgelöst" gesetzt, aber das Aufrufen von Attach () löst denselben Fehler aus. Ich benutze EF6.

Unten finden Sie meinen Code (Modellnamen wurden geändert, um das Lesen zu erleichtern).

Modell

// Wrapper classes
        public class AViewModel
        {
            public A a { get; set; }
            public List<B> b { get; set; }
            public C c { get; set; }
        }   

Regler

        public ActionResult Edit(int? id)
        {
            if (id == null)
            {
                return new HttpStatusCodeResult(HttpStatusCode.BadRequest);
            }

            if (!canUserAccessA(id.Value))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            var aViewModel = new AViewModel();
            aViewModel.A = db.As.Find(id);

            if (aViewModel.Receipt == null)
            {
                return HttpNotFound();
            }

            aViewModel.b = db.Bs.Where(x => x.aID == id.Value).ToList();
            aViewModel.Vendor = db.Cs.Where(x => x.cID == aViewModel.a.cID).FirstOrDefault();

            return View(aViewModel);
        }

[HttpPost]
        [ValidateAntiForgeryToken]
        public ActionResult Edit(AViewModel aViewModel)
        {
            if (!canUserAccessA(aViewModel.a.aID) || aViewModel.a.UserID != WebSecurity.GetUserId(User.Identity.Name))
                return new HttpStatusCodeResult(HttpStatusCode.Forbidden);

            if (ModelState.IsValid)
            {
                db.Entry(aViewModel.a).State = EntityState.Modified; //THIS IS WHERE THE ERROR IS BEING THROWN
                db.SaveChanges();
                return RedirectToAction("Index");
            }
            return View(aViewModel);
        }

Wie oben gezeigt

db.Entry(aViewModel.a).State = EntityState.Modified;

löst eine Ausnahme aus:

Das Anhängen einer Entität vom Typ 'A' ist fehlgeschlagen, da eine andere Entität desselben Typs bereits denselben Primärschlüsselwert hat. Dies kann passieren, wenn Sie die Methode "Anhängen" verwenden oder den Status einer Entität auf "Unverändert" oder "Geändert" setzen, wenn Entitäten im Diagramm widersprüchliche Schlüsselwerte aufweisen. Dies kann daran liegen, dass einige Entitäten neu sind und noch keine von der Datenbank generierten Schlüsselwerte erhalten haben. Verwenden Sie in diesem Fall die Methode 'Hinzufügen' oder den Entitätsstatus 'Hinzugefügt', um das Diagramm zu verfolgen, und setzen Sie den Status nicht neuer Entitäten entsprechend auf 'Unverändert' oder 'Geändert'.

Hat jemand etwas Falsches in meinem Code gesehen oder verstanden, unter welchen Umständen ein solcher Fehler beim Bearbeiten eines Modells auftreten würde?

Chris Ciszak
quelle
Haben Sie versucht, Ihre Entität anzuhängen, bevor Sie die festlegen EntityState? Da Ihre Entität aus einer Post-Anfrage stammt, sollte sie nicht vom aktuellen Kontext erfasst werden. Ich denke, dass Sie versuchen, einen Artikel mit einer vorhandenen ID
hinzuzufügen
Ich habe es versucht und das Ergebnis ist genau das gleiche :( Aus irgendeinem Grund denkt der Kontext, dass ich ein neues Element erstelle, aber ich aktualisiere nur das vorhandene ...
Chris Ciszak
Ich überprüfe den Status von 'a', bevor der Fehler ausgelöst wird, und der Status dieses Objekts ist 'Detached', aber der Aufruf von db.As.Attach (aViewModel.a) löst genau dieselbe Nachricht aus. Irgendwelche Ideen?
Chris Ciszak
5
Ich habe gerade Ihr Update gesehen. Wie haben Sie Ihren Kontextlebensdauerbereich eingerichtet? Ist es auf Anfrage? Wenn die dbInstanz zwischen Ihren beiden Aktionen identisch ist, kann dies Ihr Problem erklären, da Ihr Element von der GET-Methode geladen wird (dann vom Kontext verfolgt wird) und die in Ihrer POST-Methode möglicherweise nicht als die zuvor abgerufene Entität erkennt .
Réda Mattar
1
Hat canUserAccessA()laden Sie die Einheit direkt oder als eine Beziehung von einem anderen Rechtsträger?
CodeCaster

Antworten:

154

Problem gelöst!

AttachDie Methode könnte möglicherweise jemandem helfen, würde aber in dieser Situation nicht helfen, da das Dokument bereits beim Laden in der Funktion GET-Controller bearbeiten verfolgt wurde. Anhängen würde genau den gleichen Fehler auslösen.

Das Problem, auf das ich hier stoße, wurde durch eine Funktion verursacht, canUserAccessA()die die A-Entität lädt, bevor der Status von Objekt a aktualisiert wird. Dies hat die verfolgte Entität vermasselt und den Status eines Objekts in geändert Detached.

Die Lösung bestand darin, Änderungen vorzunehmen, canUserAccessA()damit das von mir geladene Objekt nicht verfolgt wird. Die Funktion AsNoTracking()sollte aufgerufen werden, während der Kontext abgefragt wird.

// User -> Receipt validation
private bool canUserAccessA(int aID)
{
    int userID = WebSecurity.GetUserId(User.Identity.Name);
    int aFound = db.Model.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();

    return (aFound > 0); //if aFound > 0, then return true, else return false.
}

Aus irgendeinem Grund konnte ich nicht .Find(aID)mit verwenden, AsNoTracking()aber es spielt keine Rolle, da ich das gleiche erreichen könnte, indem ich die Abfrage ändere .

Hoffe das hilft jedem mit ähnlichen Problemen!

Chris Ciszak
quelle
10
etwas ordentlicher und performanter: if (db.As.AsNoTracking (). Any (x => x.aID == aID && x.UserID == userID))
Brent
11
Hinweis: Sie müssen using System.Data.Entity;verwenden AsNoTracking().
Maxime
In meinem Fall funktionierte das Aktualisieren nur der Felder mit Ausnahme der Entitäts-ID einwandfrei: var entity = context.Find (entity_id); entity.someProperty = newValue; context.Entry (entity) .Property (x => x.someProperty) .IsModified = true; context.SaveChanges ();
Anton Lyhin
3
Massive Hilfe. Ich habe das .AsNoTracking () vor meinem FirstOrDefault () hinzugefügt und es hat funktioniert.
Coggicc
109

Interessant:

_dbContext.Set<T>().AddOrUpdate(entityToBeUpdatedWithId);

Oder wenn Sie immer noch nicht generisch sind:

_dbContext.Set<UserEntity>().AddOrUpdate(entityToBeUpdatedWithId);

scheint mein Problem reibungslos gelöst zu haben.

Guneysos
quelle
1
Erstaunlich, das funktionierte perfekt in meinem Szenario, in dem ich Datensätze in vielen bis vielen mit einer benutzerdefinierten Verknüpfungstabelle in einer nicht verbundenen App aktualisieren musste. Selbst wenn die Entität aus der Datenbank abgerufen wurde, wurden Referenzfehler usw. angezeigt. Ich habe "context.Entry (score) .State = System.Data.Entity.EntityState.Modified" verwendet. aber das hat endlich geklappt! Danke dir!!
Feuerlandschaft
5
Das funktioniert. Alle anderen Vorschläge zum Anhängen und Verwenden von Notracking sind fehlgeschlagen, da ich bereits NoTracking durchgeführt habe. Danke für die Lösung.
Khainestar
3
Dies funktionierte für mich, während übergeordnete und untergeordnete Entitäten innerhalb derselben Arbeitseinheit aktualisiert wurden . Vielen Dank
Ian
55
Für alle, die suchen, AddOrUpdateist eine Erweiterungsmethode im System.Data.Entity.MigrationsNamespace.
Nick
1
@Artyomska Leider weiß ich nicht.
Guneysos
15

Es scheint, dass die Entität, die Sie ändern möchten, nicht korrekt verfolgt wird und daher nicht als bearbeitet erkannt, sondern stattdessen hinzugefügt wird.

Versuchen Sie Folgendes, anstatt den Status direkt festzulegen:

//db.Entry(aViewModel.a).State = EntityState.Modified;
db.As.Attach(aViewModel.a); 
db.SaveChanges();

Außerdem möchte ich Sie warnen, dass Ihr Code eine potenzielle Sicherheitslücke enthält. Wenn Sie die Entität direkt in Ihrem Ansichtsmodell verwenden, besteht das Risiko, dass jemand den Inhalt der Entität ändert, indem er korrekt übermittelte Felder in der übermittelten Form hinzufügt. Wenn der Benutzer beispielsweise ein Eingabefeld mit dem Namen "A.FirstName" hinzufügt und die Entität ein solches Feld enthält, wird der Wert an das Ansichtsmodell gebunden und in der Datenbank gespeichert, selbst wenn der Benutzer dies im normalen Betrieb der Anwendung nicht ändern darf .

Aktualisieren:

Um die zuvor erwähnte Sicherheitslücke zu überwinden, sollten Sie Ihr Domänenmodell niemals als Ansichtsmodell verfügbar machen, sondern stattdessen ein separates Ansichtsmodell verwenden. Dann würde Ihre Aktion ein Ansichtsmodell erhalten, das Sie mit einem Zuordnungstool wie AutoMapper wieder dem Domänenmodell zuordnen können. Dies würde Sie davor schützen, dass Benutzer vertrauliche Daten ändern.

Hier ist eine ausführliche Erklärung:

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Why-You-Never-Expose-Your-Domain-Model-As-Your-MVC-Model/

Kaspars Ozole
quelle
3
Hallo Kaspars, danke für die Eingabe. Die Attach-Methode wirft die gleichen Fehler aus, die in meiner Frage erwähnt wurden. Das Problem ist, dass die Funktion canUserAccessA () sowohl die Entität als auch den oben genannten CodeCaster lädt. Aber zu sagen, dass ich sehr an Ihrem Vorschlag in Bezug auf Sicherheit interessiert bin. Könnten Sie vorschlagen, was ich tun soll, um ein solches Verhalten zu verhindern?
Chris Ciszak
Meine Antwort wurde mit zusätzlichen Informationen zum Verhindern von Sicherheitslücken aktualisiert.
Kaspars Ozols
13

Versuche dies:

var local = yourDbContext.Set<YourModel>()
                         .Local
                         .FirstOrDefault(f => f.Id == yourModel.Id);
if (local != null)
{
  yourDbContext.Entry(local).State = EntityState.Detached;
}
yourDbContext.Entry(applicationModel).State = EntityState.Modified;
Cássio Batista Pereira
quelle
11

Für mich war die lokale Kopie die Ursache des Problems. das löste es

var local = context.Set<Contact>().Local.FirstOrDefault(c => c.ContactId == contact.ContactId);
                if (local != null)
                {
                    context.Entry(local).State = EntityState.Detached;
                }
Add-Naan
quelle
10

Mein Fall war, dass ich von meiner MVC-App aus keinen direkten Zugriff auf den EF-Kontext hatte.

Wenn Sie also eine Art Repository für die Entitätspersistenz verwenden, kann es angebracht sein, die explizit geladene Entität einfach zu trennen und dann den gebundenen EntityState auf Modified zu setzen.

Beispielcode (Zusammenfassung):

MVC

public ActionResult(A a)
{
  A aa = repo.Find(...);
  // some logic
  repo.Detach(aa);
  repo.Update(a);
}

Repository

void Update(A a)
{
   context.Entry(a).EntityState = EntityState.Modified;
   context.SaveChanges();
}

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}
Sephirot
quelle
Dies funktionierte für mich, obwohl ich mir nicht die Mühe gemacht habe, ein Repository zu verwenden, um auf die Zustände der Kontextentität zu verweisen.
Eckert
3

Ich dachte, ich würde meine Erfahrungen in diesem Fall teilen, obwohl ich mich ein bisschen albern fühle, weil ich es nicht früher bemerkt habe.

Ich verwende das Repository-Muster mit den Repo-Instanzen, die in meine Controller injiziert werden. Die konkreten Repositorys instanziieren meinen ModelContext (DbContext), der die Lebensdauer des Repositorys dauert, das IDisposablevom Controller bereitgestellt wird.

Das Problem für mich war, dass ich eine modifizierte Stempel- und Zeilenversion für meine Entitäten habe, sodass ich sie zuerst erhielt, um sie mit den eingehenden Headern zu vergleichen. Dadurch wurde natürlich die Entität geladen und verfolgt, die anschließend aktualisiert wurde.

Die Lösung bestand einfach darin, das Repository von der Neuerstellung eines Kontexts im Konstruktor auf die folgenden Methoden zu ändern:

    private DbContext GetDbContext()
    {
        return this.GetDbContext(false);
    }


    protected virtual DbContext GetDbContext(bool canUseCachedContext)
    {
        if (_dbContext != null)
        {
            if (canUseCachedContext)
            {
                return _dbContext;
            }
            else
            {
                _dbContext.Dispose();
            }
        }

        _dbContext = new ModelContext();

        return _dbContext;
    }

    #region IDisposable Members

    public void Dispose()
    {
        this.Dispose(true);
    }

    protected virtual void Dispose(bool isDisposing)
    {
        if (!_isDisposed)
        {
            if (isDisposing)
            {
                // Clear down managed resources.

                if (_dbContext != null)
                    _dbContext.Dispose();
            }

            _isDisposed = true;
        }
    }

    #endregion

Auf diese Weise können die Repository-Methoden ihre Kontextinstanz bei jeder Verwendung durch Aufrufen neu erstellen GetDbContextoder eine vorherige Instanz verwenden, wenn sie dies wünschen, indem sie true angeben.

Luke Puplett
quelle
2

Ich habe diese Antwort nur hinzugefügt, weil das Problem anhand eines komplexeren Datenmusters erklärt wird und ich es hier schwer zu verstehen fand.

Ich habe eine ziemlich einfache Anwendung erstellt. Dieser Fehler trat in der Aktion POST bearbeiten auf. Die Aktion akzeptierte ViewModel als Eingabeparameter. Der Grund für die Verwendung des ViewModel bestand darin, einige Berechnungen durchzuführen, bevor der Datensatz gespeichert wurde.

Nachdem die Aktion die Validierung durchlaufen hatte if(ModelState.IsValid), bestand mein Fehlverhalten darin, Werte aus ViewModel in eine völlig neue Instanz von Entity zu projizieren. Ich dachte, ich müsste eine neue Instanz erstellen, um aktualisierte Daten zu speichern, und dann eine solche Instanz speichern.

Was ich später festgestellt hatte, war, dass ich den Datensatz aus der Datenbank lesen musste:

Student student = db.Students.Find(s => s.StudentID == ViewModel.StudentID);

und dieses Objekt aktualisiert. Alles funktioniert jetzt.

Celdor
quelle
2

Ich hatte dieses Problem mit lokalem var und ich entferne es einfach so:

if (ModelState.IsValid)
{
    var old = db.Channel.Find(channel.Id);
    if (Request.Files.Count > 0)
    {
        HttpPostedFileBase objFiles = Request.Files[0];
        using (var binaryReader = new BinaryReader(objFiles.InputStream))
        {
            channel.GateImage = binaryReader.ReadBytes(objFiles.ContentLength);
        }

    }
    else
        channel.GateImage = old.GateImage;
    var cat = db.Category.Find(CatID);
    if (cat != null)
        channel.Category = cat;
    db.Entry(old).State = EntityState.Detached; // just added this line
    db.Entry(channel).State = EntityState.Modified;
    await db.SaveChangesAsync();
    return RedirectToAction("Index");
}
return View(channel);

Problemursachen für geladene Objekte mit demselben Schlüssel. Daher werden wir zuerst dieses Objekt trennen und die Aktualisierung durchführen, um Konflikte zwischen zwei Objekten mit demselben Schlüssel zu vermeiden

lvl4fi4
quelle
@Artjom B Problemursachen für geladene Objekte mit demselben Schlüssel, also werden wir zuerst dieses Objekt
trennen
2

Ich hatte ein ähnliches Problem, nachdem ich 2-3 Tage lang nachgeforscht hatte, dass ".AsNoTracking" entfernt werden sollte, da EF die Änderungen nicht verfolgt und davon ausgeht, dass es keine Änderungen gibt, es sei denn, ein Objekt ist angehängt. Auch wenn wir .AsNoTracking nicht verwenden, weiß EF automatisch, welches Objekt gespeichert / aktualisiert werden soll, sodass Attach / Added nicht verwendet werden muss.

Prem
quelle
2

Verwenden AsNoTracking()Sie, wo Sie Ihre Anfrage erhalten.

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
Abdus Salam Azad
quelle
2

Ich bin auf diesen Fehler gestoßen, wo

  • Zwei Methoden, A & B, in einem einzelnen Controller verwendeten beide dieselbe Instanz eines ApplicationDbContext und
  • Methode A heißt Methode B.
    private ApplicationDbContext db;
    // api methods
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return new JsonResult();
    }

Ich habe Methode B geändert, um eine using-Anweisung zu haben und mich nur auf die lokale db2 zu verlassen . Nach dem:

    private ApplicationDbContext db;    
    // api methods    
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(id);
        db.Entry(resource).State = EntityState.Modified;
        db.SaveChanges();
        return methodB()
    }

    public JsonResult methodB(string id){
        using (var db2 = new ApplicationDbContext())
        {
            Resource resource = db2.Resources.Find(id);
            db2.Entry(resource).State = EntityState.Modified;
            db2.SaveChanges();
        }
        return new JsonResult();
    }
colbybhearn
quelle
1

Ähnlich wie Luke Puplett sagt, kann das Problem dadurch verursacht werden, dass Sie Ihren Kontext nicht richtig entsorgen oder erstellen.

In meinem Fall hatte ich eine Klasse, die einen Kontext mit dem Namen akzeptierte ContextService:

public class ContextService : IDisposable
{
    private Context _context;

    public void Dispose()
    {
        _context.Dispose();
    }
    public ContextService(Context context)
    {
        _context = context;
    }
//... do stuff with the context

Mein Kontextdienst hatte eine Funktion, die eine Entität mithilfe eines instanziierten Entitätsobjekts aktualisiert:

        public void UpdateEntity(MyEntity myEntity, ICollection<int> ids)
        {
            var item = _context.Entry(myEntity);
            item.State = EntityState.Modified;
            item.Collection(x => x.RelatedEntities).Load();
            myEntity.RelatedEntities.Clear();
            foreach (var id in ids)
            {
                myEntity.RelatedEntities.Add(_context.RelatedEntities.Find(id));
            }
            _context.SaveChanges();
        }

All dies war in Ordnung, mein Controller, auf dem ich den Dienst initialisiert habe, war das Problem. Mein Controller sah ursprünglich so aus:

    private static NotificationService _service = 
        new NotificationService(new NotificationContext());
    public void Dispose()
    {
    }

Ich habe es geändert und der Fehler ging weg:

    private static NotificationService _service;
    public TemplateController()
    {
        _service = new NotificationService(new NotificationContext());
    }
    public void Dispose()
    {
        _service.Dispose();
    }
Jared Beach
quelle
1

Dieses Problem kann auch während gesehen werden , ViewModelum EntityModelMapping (unter Verwendung AutoMapper, etc.) und versuchen , schließen context.Entry().Stateund eine context.SaveChanges()solche Verwendung von Block wie unten gezeigt würde das Problem lösen. Bitte beachten Sie, dass die context.SaveChanges()Methode zweimal verwendet wird, anstatt sie direkt danach zu verwenden, if-blockda sie auch in block verwendet werden muss.

public void Save(YourEntity entity)
{
    if (entity.Id == 0)
    {
        context.YourEntity.Add(entity);
        context.SaveChanges();
    }
    else
    {
        using (var context = new YourDbContext())
        {
            context.Entry(entity).State = EntityState.Modified;
            context.SaveChanges(); //Must be in using block
        }
    }            
}

Hoffe das hilft...

Murat Yıldız
quelle
1

Hier was ich im ähnlichen Fall gemacht habe.

Diese Situation bedeutet, dass dieselbe Entität bereits im Kontext existiert hat. Das Folgende kann also helfen

Überprüfen Sie zunächst in ChangeTracker, ob sich die Entität im Kontext befindet

var trackedEntries=GetContext().ChangeTracker.Entries<YourEntityType>().ToList();

var isAlreadyTracked =
                    trackedEntries.Any(trackedItem => trackedItem.Entity.Id ==myEntityToSave.Id);

Wenn es existiert

  if (isAlreadyTracked)
            {
                myEntityToSave= trackedEntries.First(trackedItem => trackedItem.Entity.Id == myEntityToSave.Id).Entity;
            } 

else
{
//Attach or Modify depending on your needs
}
erhan355
quelle
1

Ich schaffe es, das Problem durch Aktualisieren des Status zu beheben. Wenn Sie die Suche auslösen oder eine andere Abfrageoperation auf demselben Datensatzstatus mit Änderungen aktualisiert wurde, müssen wir den Status auf "Getrennt" setzen, damit Sie Ihre Aktualisierungsänderung auslösen können

     ActivityEntity activity = new ActivityEntity();
      activity.name="vv";
    activity.ID = 22 ; //sample id
   var savedActivity = context.Activities.Find(22);

            if (savedActivity!=null)
            {
                context.Entry(savedActivity).State = EntityState.Detached;
                context.SaveChanges();

                activity.age= savedActivity.age;
                activity.marks= savedActivity.marks; 

                context.Entry(activity).State = EntityState.Modified;
                context.SaveChanges();
                return activity.ID;
            }
Veera Induvasi
quelle
1

Ich löse dieses Problem mit einem "using" -Block

using (SqlConnection conn = new SqlConnection(connectionString))

    {

       // stuff to do with data base
    }

    // or if you are using entity framework 
    using (DataBaseEntity data = new DataBaseEntity)
{

    }

Hier komme ich auf die Idee https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework?forum = vcses ist auf Spanisch (suchen Sie nach der zweiten Antwort)

Suzume
quelle
Seien Sie vorsichtig und verwenden Sie nur eine Instanz der Datenbankkonexion. Insbesondere wenn Sie das Entity Framework verwenden, wird der Fehler Entity Framework angezeigt. Entity Framework Ein Entity-Objekt kann nicht von mehreren Instanzen von IEntityChangeTracker
Suzume
1

Sie können eine hinzugefügte Methode wie verwenden;

_dbContext.Entry(modelclassname).State = EntityState.Added;

In vielen Fällen funktioniert dies jedoch nicht, wenn Sie zu diesem Zeitpunkt mehr als ein Modell verwenden möchten, da die Entität bereits mit einer anderen Entität verbunden ist. Zu diesem Zeitpunkt können Sie also die ADDOrUpdate-Entitätsmigrationsmethode verwenden, mit der Objekte einfach von einem zum anderen migriert werden, sodass Sie keinen Fehler erhalten.

_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);
Mihir Doshi
quelle
0

Löschen Sie alle Status

dbContextGlobalERP.ChangeTracker.Entries (). Where (e => e.Entity! = null) .ToList (). ForEach (e => e.State = EntityState.Detached);

xxxsenatorxxx
quelle