ASP.NET MVC - 'MODELNAME' türünde bir varlık ekleme başarısız oldu çünkü aynı türden başka bir varlık zaten aynı birincil anahtar değerine sahip


122

Özetle, istisna, POSTing sarıcı modeli sırasında ve bir girişin durumunu 'Değiştirilmiş' olarak değiştirirken atılır. Durumu değiştirmeden önce, durum 'Ayrıldı' olarak ayarlanır, ancak Attach () çağrısı aynı hatayı verir. EF6 kullanıyorum.

Lütfen aşağıda kodumu bulun (model isimleri okumayı kolaylaştırmak için değiştirildi)

model

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

kontrolör

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

Yukarıdaki satırda gösterildiği gibi

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

istisna atar:

Aynı türden başka bir varlık zaten aynı birincil anahtar değerine sahip olduğundan 'A' türünde bir varlık ekleme başarısız oldu. Bu, grafikteki herhangi bir varlığın çakışan anahtar değerlerine sahip olması durumunda, "Ekle" yöntemini kullanırken veya bir varlığın durumunu "Değiştirilmedi" veya "Değiştirildi" olarak ayarladığınızda olabilir. Bunun nedeni, bazı varlıkların yeni olması ve henüz veritabanı tarafından üretilen anahtar değerlerini almamış olması olabilir. Bu durumda grafiği izlemek için 'Ekle' yöntemini veya 'Eklendi' varlık durumunu kullanın ve ardından yeni olmayan varlıkların durumunu uygun şekilde 'Değiştirilmedi' veya 'Değiştirildi' olarak ayarlayın.

Kodumda yanlış bir şey gören veya bir modeli düzenlerken hangi durumlarda böyle bir hata vereceğini anlayan var mı?


'Yi ayarlamadan önce varlığınızı eklemeyi denediniz EntityStatemi? Varlığınız bir gönderi talebinden geldiğinden, mevcut bağlam tarafından izlenmemelidir, sanırım mevcut bir kimliğe sahip bir öğe eklemeye çalıştığınızı düşünür
Réda Mattar

Bunu denedim ve sonuç tamamen aynı :( Her nedense içerik yeni bir öğe oluşturduğumu düşünüyor, ancak sadece mevcut olanı güncelliyorum ...
Chris Ciszak

Hata atılmadan ve bu nesnenin durumu 'Ayrılmış' olmadan önce 'a' durumunu kontrol ediyorum, ancak db.As.Attach (aViewModel.a) 'yı çağırmak tam olarak aynı mesajı mı atıyor? Herhangi bir fikir?
Chris Ciszak

5
Güncellemenizi az önce gördüm, bağlam yaşam boyu kapsamınızı nasıl ayarladınız? İstek başına mı? Eğer dbörnek verilen iki eylemler arasında aynıdır öğe GET yöntemi (daha sonra bağlam tarafından izlenen) tarafından yüklendiği için, bu sorunu açıklayabilir ve varlık önce getirilen olarak sizin POST yöntemiyle bir tane tanımayabilir .
Réda Mattar

1
canUserAccessA()Varlığı doğrudan mı yoksa başka bir yetkili ile ilişkisi olarak mı yüklüyor?
CodeCaster

Yanıtlar:


155

Sorun çözüldü!

Attachyöntemi potansiyel olarak birine yardımcı olabilir, ancak bu durumda yardımcı olmaz, çünkü GET denetleyicisini Düzenle işlevine yüklenirken belge zaten izleniyordu. Attach tam olarak aynı hatayı verir.

Burada karşılaştığım sorun canUserAccessA(), a nesnesinin durumunu güncellemeden önce A varlığını yükleyen işlevden kaynaklanıyordu . Bu, izlenen varlığı mahvediyordu ve bir nesnenin durumunu olarak değiştiriyordu Detached.

Çözüm, canUserAccessA()yüklediğim nesnenin izlenmemesi için değişiklik yapmaktı. AsNoTracking()Bağlam sorgulanırken işlev çağrılmalıdır.

// 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.
}

Bazı sebebi edemedim kullanım için .Find(aID)birlikte AsNoTracking()gerçekten ben sorguyu değiştirerek aynı elde edebiliriz olarak önemli değil ama.

Umarım bu, benzer sorunu olan herkese yardımcı olur!


10
biraz daha düzgün ve daha performanslı: if (db.As.AsNoTracking (). Any (x => x.aID == aID && x.UserID == userID))
Brent

11
Not: using System.Data.Entity;kullanmanız gerekir AsNoTracking().
Maxime

Benim durumumda varlık kimliği dışındaki alanların güncellenmesi iyi çalıştı: var varlık = bağlam.Find (varlık_kimliği); entity.someProperty = newValue; context.Entry (varlık) .Property (x => x.someProperty) .IsModified = true; context.SaveChanges ();
Anton Lyhin

3
Büyük Yardım. .AsNoTracking () 'i FirstOrDefault ()' dan önce ekledim ve işe yaradı.
coggicc

110

İlginçtir:

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

Veya hala jenerik değilseniz:

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

sorunumu sorunsuz bir şekilde çözmüş görünüyor.


1
Şaşırtıcı, bu, bağlantısız bir uygulamada özel bir birleştirme tablosuyla kayıtları çoktan çoğa güncellemem gereken senaryomda mükemmel çalıştı. Varlık veritabanından alınmış olsa bile, referans hataları vb. Alıyordum. "Context.Entry (score) .State = System.Data.Entity.EntityState.Modified;" ama bu nihayet işe yaradı! Teşekkür ederim!!
ateş kese

5
Bu çalışıyor. Ekleme ve izleme ile ilgili diğer tüm öneriler, zaten noTracking yapmadığım için başarısız oldu. Çözüm için teşekkürler.
Khainestar

3
Bu , aynı iş birimindeki üst ve alt varlıkları güncellerken benim için çalıştı . çok teşekkürler
Ian

55
Arayan herkes AddOrUpdateiçin, System.Data.Entity.Migrationsad alanında bir uzantı yöntemidir .
Nick

1
@Artyomska Maalesef bilmiyorum.
guneysus

15

Görünüşe göre değiştirmeye çalıştığınız varlık doğru bir şekilde izlenmiyor ve bu nedenle düzenlenmiş olarak tanınmıyor, bunun yerine ekleniyor.

Doğrudan durumu ayarlamak yerine, aşağıdakileri yapmayı deneyin:

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

Ayrıca, kodunuzun olası güvenlik açığı içerdiği konusunda sizi uyarmak isterim. Varlığı doğrudan görünüm modelinizde kullanıyorsanız, gönderilen forma doğru adlandırılmış alanlar ekleyerek birisinin varlığın içeriğini değiştirmesi riskini alırsınız. Örneğin, kullanıcı "A.FirstName" adında bir giriş kutusu eklediyse ve varlık böyle bir alan içeriyorsa, bu durumda, kullanıcının uygulamanın normal çalışmasında bunu değiştirmesine izin verilmese bile, değer görüntüleme modeline bağlanır ve veritabanına kaydedilir. .

Güncelleme:

Daha önce bahsedilen güvenlik açığını aşmak için, etki alanı modelinizi hiçbir zaman görünüm modeliniz olarak göstermemelisiniz, bunun yerine ayrı bir görünüm modeli kullanmalısınız. Ardından eyleminiz, AutoMapper gibi bazı haritalama araçlarını kullanarak etki alanı modeline geri eşleyebileceğiniz görünüm modelini alır. Bu, kullanıcının hassas verileri değiştirmesini engeller.

İşte genişletilmiş açıklama:

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


3
Merhaba Kaspars, girdi için teşekkürler. Attach yöntemi sorumda bahsedildiği gibi aynı hataları atar. Sorun canUserAccessA () işlevinin varlığı ve yukarıda fark edilen CodeCaster'ı yüklemesidir. Ama güvenlik konusundaki önerinizle çok ilgilendiğimi söylüyorum. Böyle bir davranışı önlemek için ne yapmam gerektiğini önerebilir misiniz?
Chris Ciszak

Cevabım, güvenlik açıklarının nasıl önleneceği hakkında ek bilgilerle güncellendi.
Kaspars Ozols

13

Bunu dene:

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;

11

benim için yerel kopya sorunun kaynağıydı. bu onu çözdü

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

10

Benim durumum, MVC uygulamamdan EF bağlamına doğrudan erişimimin olmamasıydı.

Bu nedenle, varlık kalıcılığı için bir tür depo kullanıyorsanız, açıkça yüklenen varlığı ayırmak ve ardından bağlı EntityState'i Modified olarak ayarlamak uygun olabilir.

Örnek (soyut) kod:

MVC

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

depo

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

void Detach(A a)
{
   context.Entry(a).EntityState = EntityState.Detached;
}

Bağlam varlık durumlarına başvurmak için bir depo kullanmakla uğraşmasam da bu benim için çalıştı.
Eckert

3

Daha önce fark etmediğim için biraz aptal hissetmeme rağmen, bu konudaki deneyimlerimi paylaşacağımı düşündüm.

Depo modelini, denetleyicilerime enjekte edilen depo örnekleri ile kullanıyorum. Somut depolar IDisposable, denetleyici tarafından atılan ve deponun ömrünü uzatan benim ModelContext'i (DbContext) başlatır .

Benim için sorun, varlıklarımda değiştirilmiş bir damga ve satır sürümüne sahip olmamdı, bu yüzden gelen üstbilgilerle karşılaştırmak için onları ilk önce alıyordum. Elbette, bu, daha sonra güncellenmekte olan varlığı yükledi ve izledi.

Düzeltme basitçe, depoyu, yapıcıda bir kez bir bağlamı yenileyerek aşağıdaki yöntemlere sahip olacak şekilde değiştirmekti:

    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

Bu, arşiv yöntemlerinin her kullanımda bağlam örneğini çağırarak GetDbContextveya eğer arzu ederlerse true belirleyerek önceki bir örneği kullanmalarına izin verir .


2

Bu yanıtı ekledim çünkü sorun daha karmaşık veri modeline göre açıklanıyor ve burada anlaşılması zor.

Oldukça basit bir uygulama yarattım. Bu hata POST Düzenle eyleminde meydana geldi. İşlem, ViewModel'i bir girdi parametresi olarak kabul etti. ViewModel'i kullanmanın nedeni, kayıt kaydedilmeden önce bazı hesaplamalar yapmaktı.

Eylem bir kez doğrulamadan geçtikten sonra if(ModelState.IsValid), benim yanlış yaptığım şey ViewModel'den değerleri tamamen yeni bir Varlık örneğine yansıtmaktı. Güncellenen verileri depolamak için yeni bir örnek oluşturmam gerektiğini düşündüm ve ardından böyle bir örneği kaydettim.

Daha sonra anladığım şey, kaydı veritabanından okumak zorunda olduğumdu:

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

ve bu nesneyi güncelledi. Artık her şey çalışıyor.


2

Yerel var ile bu sorunu yaşadım ve şu şekilde ayırdım:

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

Aynı Anahtara sahip yüklenen nesnelerin sorun nedenleri, bu nedenle önce o nesneyi çıkaracağız ve aynı Anahtara sahip iki nesne arasında çakışmayı önlemek için güncellemeyi yapacağız.


@Artjom B Aynı Anahtara sahip yüklenen nesnelerin sorun nedenleri, bu nedenle önce o nesneyi çıkaracağız ve aynı Anahtarla iki nesne arasındaki çakışmayı önlemek için güncellemeyi yapacağız
lvl4fi4

2

Benzer bir sorunla karşılaştım, 2-3 gün inceledikten sonra ".AsNoTracking" kaldırılmalıdır çünkü EF değişiklikleri izlemez ve bir nesne eklenmedikçe değişiklik olmadığını varsayar. Ayrıca .AsNoTracking'i kullanmazsak, EF hangi nesnenin kaydedileceğini / güncelleneceğini otomatik olarak bilir, bu nedenle Ekle / Eklendi kullanımına gerek kalmaz.


2

Kullanım AsNoTracking()sorgunuzu alıyorsanız nerede.

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

2

Bu hatayla karşılaştım burada

  • Tek bir denetleyicideki iki yöntem, A & B, her ikisi de aynı ApplicationDbContext örneğini kullandı ve
  • yöntem A yöntemi B yöntemi olarak adlandırılır
    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();
    }

B yöntemini bir using ifadesine sahip olacak ve yalnızca yerel db2'ye güvenecek şekilde değiştirdim . Sonra:

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

1

Luke Puplett'in söylediği gibi, sorun, bağlamınızı düzgün bir şekilde düzenlememekten veya oluşturmamaktan kaynaklanabilir.

Benim durumumda, şu bağlamı kabul eden bir sınıfım vardı ContextService:

public class ContextService : IDisposable
{
    private Context _context;

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

Bağlam hizmetim, somutlaştırılmış bir varlık nesnesi kullanarak bir varlığı güncelleyen bir işleve sahipti:

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

Tüm bunlar iyiydi, sorunu başlattığım kontrolörüm vardı. Denetleyicim aslında şuna benziyordu:

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

Bunu buna değiştirdim ve hata gitti:

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

1

Bu sorun, içinde görülebilir ViewModeliçin EntityModel(kullanarak haritalama AutoMapperve bunlar arasında çalışırken, vs.) context.Entry().Stateve context.SaveChanges()sorunu çözmek aşağıda gösterildiği gibi bir blok kullanılarak. Lütfen context.SaveChanges()yöntemin hemen sonra kullanmak yerine iki kez kullanıldığını unutmayın, if-blockçünkü aynı zamanda blok kullanırken de olması gerekir.

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

Bu yardımcı olur umarım...


1

İşte benzer durumda yaptığım şey.

Bu durum, aynı varlığın bağlamda zaten var olduğu anlamına gelir.

Varlığın bağlamda olup olmadığını önce ChangeTracker'dan kontrol edin

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

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

Eğer varsa

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

else
{
//Attach or Modify depending on your needs
}

1

Durumu güncelleyerek sorunu çözebilirim. Bulmayı tetiklediğinizde veya aynı kayıt üzerinde başka herhangi bir sorgu işlemi değiştirilerek güncellendi, bu nedenle durumu Ayrıldı olarak ayarlamamız gerekiyor, ardından güncelleme değişikliğinizi tetikleyebilirsiniz.

     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

Bu sorunu bir "kullanma" bloğu ile çözüyorum

using (SqlConnection conn = new SqlConnection(connectionString))

    {

       // stuff to do with data base
    }

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

    }

İşte fikir https://social.msdn.microsoft.com/Forums/sqlserver/es-ES/b4b350ba-b0d5-464d-8656-8c117d55b2af/problema-al-modificar-en-entity-framework? = vcses ispanyolcadır (ikinci cevabı arayın)


sadece dikkatli olun ve yalnızca 1 veritabanı conexion örneğini kullanın, özellikle bunu yapmazsanız, varlık çerçevesi kullanıyorsanız, Entity Framework hatası alırsınız Bir varlık nesnesine, IEntityChangeTracker'ın birden çok örneği tarafından başvurulamaz
Suzume

1

aşağıdaki gibi ek yöntemleri kullanabilirsiniz;

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

ancak çoğu durumda, o anda birden fazla model kullanmak istiyorsanız, varlık zaten başka bir varlığa bağlı olduğundan bu işe yaramaz. Bu nedenle, o anda nesneyi birinden diğerine geçiren ADDOrUpdate Varlık Geçişi yöntemini kullanabilirsiniz ve sonuç olarak herhangi bir hata almazsınız.

_dbContext.Set<modelclassname>().AddOrUpdate(yourmodel);

0

Tüm Durumu temizle

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.