webentwicklung-frage-antwort-db.com.de

ASP.NET MVC - Das Anfügen einer Entität des Typs 'MODELNAME' ist fehlgeschlagen, da eine andere Entität desselben Typs bereits denselben Primärschlüsselwert hat

Kurz gesagt, die Ausnahme wird beim POSTing-Wrapper-Modell ausgelöst und der Status eines Eintrags wird in "Modified" geändert. Vor dem Ändern des Status wird der Status auf 'Detached' gesetzt, der Aufruf von Attach () löst jedoch den gleichen Fehler aus. Ich verwende EF6.

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

wirft Ausnahme:

Das Anhängen einer Entität vom Typ 'A' ist fehlgeschlagen, da eine andere Entität der Gleicher Typ hat bereits denselben Primärschlüsselwert. Dies kann passieren, wenn Verwenden Sie die 'Attach'-Methode oder setzen Sie den Status einer Entität auf 'Unverändert' oder 'Geändert', wenn Entitäten im Diagramm .__ haben. widersprüchliche Schlüsselwerte. Dies kann daran liegen, dass einige Entitäten neu sind und haben noch keine datenbankgenerierten Schlüsselwerte erhalten. In diesem Fall verwenden Sie die 'Add'-Methode oder der' Added'-Entity-Status, um das Diagramm und .__ zu verfolgen. Setzen Sie dann den Status von nicht neuen Entitäten auf "Unverändert" oder "Geändert" als angemessen.

Steht irgendjemand in meinem Code irgendetwas falsch oder versteht es unter welchen Umständen ein solcher Fehler während der Bearbeitung eines Modells?

102
Chris Ciszak

Problem gelöst!

Die Attach-Methode könnte möglicherweise jemandem helfen, aber sie würde in dieser Situation nicht helfen, da das Dokument bereits während des Ladens in die GET-Controller-Funktion nachverfolgt wurde. Attach würde genau den gleichen Fehler werfen.

Das Problem, dem ich hier begegne, wurde von der Funktion canUserAccessA() verursacht, die die A-Entität lädt, bevor der Status des Objekts a aktualisiert wird. Dadurch wurde das verfolgte Objekt durcheinander gebracht und der Status eines Objekts in Detached geändert.

Die Lösung bestand darin, canUserAccessA() so zu ändern, dass das von mir geladene Objekt nicht verfolgt wurde. Die Funktion AsNoTracking() sollte während der Abfrage des Kontextes aufgerufen werden.

// 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 .Find(aID) nicht mit AsNoTracking() verwenden, aber es spielt keine Rolle, da ich durch das Ändern der Abfrage dasselbe erreichen könnte.

Ich hoffe, das hilft allen, die ein ähnliches Problem haben!

133
Chris Ciszak

Interessant: 

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

Oder wenn Sie immer noch nicht generisch sind:

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

scheint mein Problem reibungslos zu lösen.

90
guneysus

Es scheint, dass die Entität, die Sie ändern möchten, nicht korrekt verfolgt wird und daher nicht als bearbeitet erkannt wird, sondern 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();

Ich möchte Sie auch warnen, dass Ihr Code eine potenzielle Sicherheitsanfälligkeit enthält. Wenn Sie die Entität direkt in Ihrem Ansichtsmodell verwenden, besteht das Risiko, dass jemand den Inhalt der Entität modifiziert, indem er dem übergebenen Formular richtig benannte Felder hinzufügt. Wenn der Benutzer beispielsweise ein Eingabefeld mit dem Namen "A.FirstName" hinzufügte und die Entität ein solches Feld enthielt, wäre der Wert an das Viewmodel gebunden und in der Datenbank gespeichert, auch wenn der Benutzer dies im normalen Betrieb der Anwendung nicht ändern darf .

Update:

Um die zuvor erwähnte Sicherheitsanfälligkeit zu überwinden, sollten Sie Ihr Domänenmodell niemals als Viewmodel verfügbar machen, sondern ein separates Viewmodel verwenden. Dann würde Ihre Aktion Viewmodel erhalten, das Sie mithilfe eines Mapping-Tools wie AutoMapper wieder dem Domänenmodell zuordnen können. Dies würde Sie vor dem Ändern der sensiblen Daten durch den Benutzer schützen. 

Hier ist eine erweiterte Erklärung:

http://www.stevefenton.co.uk/Content/Blog/Date/201303/Blog/Warum-Sie-Niemals-Zweck-Ihre-Domain-Model-As-Ihr-MVC-Model/

14
Kaspars Ozols

In meinem Fall hatte ich keinen direkten Zugriff auf den EF-Kontext von meiner MVC-App. 

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

Beispielcode (abstrakt):

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

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;
                }
8
add-Naan

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;

Ich dachte, ich würde meine Erfahrungen mit diesem teilen, auch wenn ich mich etwas dumm fühle, weil ich es nicht früher merkte.

Ich verwende das Repository-Muster mit den Repo-Instanzen, die in meine Controller eingefügt wurden. Die konkreten Repositorys instantiieren meinen ModelContext (DbContext), der die gesamte Lebensdauer des Repositorys hat, nämlich IDisposable und vom Controller entsorgt wird.

Das Problem für mich war, dass ich eine modifizierte Stempel- und Zeilenversion für meine Entitäten habe, also habe ich sie zuerst erhalten, um sie mit den Inbound-Headern zu vergleichen. Natürlich hat dies die Entität geladen und verfolgt, die anschließend aktualisiert wurde.

Das Update bestand einfach darin, das Repository so zu ändern, dass ein Kontext einmal im Konstruktor neu erstellt wurde, und zwar mit den folgenden Methoden:

    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

Dies ermöglicht den Repository-Methoden, ihre Kontextinstanz bei jeder Verwendung durch Aufruf von GetDbContext neu zu erstellen oder eine vorherige Instanz zu verwenden, wenn sie dies durch Angabe von true wünschen.

3
Luke Puplett

Ich habe diese Antwort nur hinzugefügt, weil das Problem basierend auf komplexeren Datenmustern erklärt wurde und ich es hier schwer verstehen konnte.

Ich habe eine ziemlich einfache Anwendung erstellt. Dieser Fehler ist in der Aktion "Bearbeiten POST" aufgetreten. Die Aktion akzeptierte ViewModel als Eingabeparameter. Der Grund für die Verwendung von ViewModel bestand darin, vor dem Speichern des Datensatzes einige Berechnungen durchzuführen.

Nachdem die Aktion die Validierung durchlaufen hatte, z. B. if(ModelState.IsValid), bestand mein Fehler 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 gespeichert.

Was mir später klar wurde, 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.

2
Celdor

Ich hatte dieses Problem mit local var und 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 trennen wir das Objekt zuerst und führen die Aktualisierung durch, um Konflikte zwischen zwei Objekten mit demselben Schlüssel zu vermeiden 

1
lvl4fi4

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 bekomme ich die Idee https://social.msdn.Microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-entity-framework ? forum = vcses ist in spanisch (suchen Sie nach der zweiten Antwort)

1
Suzume

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

  var result = dbcontext.YourModel.AsNoTracking().Where(x => x.aID == aID && x.UserID==userID).Count();
1

Hier was ich im ähnlichen Fall gemacht habe.

Diese Situation bedeutet, dass die gleiche Entität bereits im Kontext vorhanden ist. Nachfolgend kann dies hilfreich sein

Ü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
}
1
erhan355

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

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

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, bei dem ich den Dienst initialisiert hatte, 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();
    }
1
Jared Beach

ich kann das Problem beheben, indem ich den Status aktualisiere. Wenn Sie die Suche starten oder eine andere Abfrageoperation in demselben Datensatz ausführen, wurde sie geändert, und Sie müssen den Status auf Detached setzen. Dann können Sie Ihre Updateänderung auslösen 

     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;
            }
1
veeresh i

Dieses Problem kann auch während der ViewModel- zu EntityModel-Zuordnung (durch Verwendung von AutoMapper usw.) und beim Versuch, context.Entry().State und context.SaveChanges() einzufügen, wie in der folgenden Abbildung gezeigt, auftreten. Bitte beachten Sie, dass die context.SaveChanges()-Methode zwei Mal verwendet wird und nicht direkt nach if-block, da sie auch im 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...

1
Murat Yıldız

Ich bin auf diesen Fehler gestoßen

  • zwei Methoden, A & B, in einem einzelnen Controller verwendeten beide dieselbe Instanz eines ApplicationDbContext, und
  • methode A aufgerufen Methode B
    private ApplicationDbContext db;
    // api methods
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(g);
        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 verlasse mich nur auf das lokale db2 . Nach dem:

    private ApplicationDbContext db;    
    // api methods    
    public JsonResult methodA(string id){
        Resource resource = db.Resources.Find(g);
        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();
    }
0
colbybhearn

Ich hatte ein ähnliches Problem, nachdem ich 2-3 Tage lang herausgefunden hatte, dass ".AsNoTracking" entfernt werden sollte, da EF die Änderungen nicht verfolgt und davon ausgeht, dass es keine Änderungen gibt, sofern kein Objekt angehängt ist. Wenn wir .AsNoTracking nicht verwenden, weiß EF automatisch, welches Objekt gespeichert/aktualisiert werden soll, so dass keine Verwendung von Attach/Added erforderlich ist.

0
Prem Kumar