Entity Framework'ün alt nesneleri kaydetmeye / eklemeye çalışmasını nasıl durdururum?


105

Varlık çerçevesine sahip bir varlığı kaydettiğimde, doğal olarak bunun yalnızca belirtilen varlığı kurtarmaya çalışacağını varsaydım. Ancak, o varlığın alt varlıklarını da kurtarmaya çalışıyor. Bu, her türlü bütünlük sorununa neden oluyor. EF'yi yalnızca kaydetmek istediğim varlığı kaydetmeye ve bu nedenle tüm alt nesneleri yoksaymaya nasıl zorlarım?

Özellikleri el ile null olarak ayarlarsam, bir hata alıyorum "İşlem başarısız oldu: Bir veya daha fazla yabancı anahtar özelliği null yapılamaz olduğundan ilişki değiştirilemedi." Bu, alt nesneyi özellikle null olarak ayarladığım için, EF onu yalnız bıraktığından, son derece ters etki yaratıyor.

Neden alt nesneleri kaydetmek / eklemek istemiyorum?

Bu, yorumlarda ileri geri tartışıldığı için, çocuğumun nesnelerinin neden yalnız bırakılmasını istediğime dair bazı gerekçeler vereceğim.

Oluşturduğum uygulamada, EF nesne modeli veritabanından yüklenmiyor, düz bir dosyayı ayrıştırırken doldurduğum veri nesneleri olarak kullanılıyor. Alt nesneler söz konusu olduğunda, bunların çoğu, ana tablonun çeşitli özelliklerini tanımlayan arama tablolarına atıfta bulunur. Örneğin, birincil varlığın coğrafi konumu.

Bu nesneleri kendim doldurduğum için, EF bunların yeni nesneler olduğunu ve üst nesneyle birlikte eklenmesi gerektiğini varsayar. Ancak, bu tanımlar zaten var ve veritabanında kopyalar oluşturmak istemiyorum. Bir arama yapmak ve ana tablo varlığımdaki yabancı anahtarı doldurmak için yalnızca EF nesnesini kullanıyorum.

Gerçek veri olan alt nesnelerle bile, önce ebeveyni kaydetmem ve bir birincil anahtar almam gerekiyor, yoksa EF, işleri berbat ediyor gibi görünüyor. Umarım bu biraz açıklama getirir.


Bildiğim kadarıyla alt nesneleri sıfırlamanız gerekecek.
Johan

Merhaba Johan. Çalışmıyor. Koleksiyonu boş bırakırsam hatalar atar. Nasıl yaptığıma bağlı olarak, anahtarların boş olduğundan veya koleksiyonumun değiştirildiğinden şikayet ediyor. Açıkçası, bunlar doğru, ama bunu bilerek yaptım, böylece dokunmaması gereken nesneleri yalnız bırakacaktı.
Mark Micallef

Öforik, bu tamamen faydasız.
Mark Micallef

@Euphoric Alt nesneleri değiştirmese bile, EF yine de bunları varsayılan olarak eklemeye çalışır ve onları yok saymaz veya günceller.
Johan

Beni gerçekten rahatsız eden şey, bu nesneleri gerçekten boşa çıkarmak için kendi yolumdan çekilirsem, onları yalnız bırakmak istediğimi fark etmek yerine şikayet ediyor olmasıdır. Bu alt nesnelerin tümü isteğe bağlı olduğundan (veritabanında null yapılabilir), EF'yi bu nesnelere sahip olduğumu unutmaya zorlamanın bir yolu var mı? yani bağlamını mı yoksa önbelleğini bir şekilde mi temizleyeceksiniz?
Mark Micallef

Yanıtlar:


57

Bildiğim kadarıyla iki seçeneğiniz var.

Seçenek 1)

Tüm alt nesneleri boş bırakın, bu EF'nin hiçbir şey eklememesini sağlar. Ayrıca veritabanınızdan hiçbir şey silinmez.

Seçenek 2)

Aşağıdaki kodu kullanarak alt nesneleri bağlamdan ayrılmış olarak ayarlayın

 context.Entry(yourObject).State = EntityState.Detached

A List/ 'yi ayıramayacağınızı unutmayın Collection. Listenizde bir döngü içinde dolaşmanız ve listenizdeki her öğeyi bu şekilde ayırmanız gerekecek.

foreach (var item in properties)
{
     db.Entry(item).State = EntityState.Detached;
}

Merhaba Johan, koleksiyonlardan birini ayırmayı denedim ve şu hatayı ortaya çıkardı: HashSet`1 varlık türü mevcut bağlam için modelin bir parçası değil.
Mark Micallef

@MarkyMark Koleksiyonu ayırmayın. Koleksiyonun üzerinden geçmeniz ve nesneyi nesne için ayırmanız gerekecek (cevabımı şimdi güncelleyeceğim).
Johan

11
Ne yazık ki, her şeyi çözecek bir döngü listesi olsa bile, EF hala bazı ilgili tablolara eklemeye çalışıyor gibi görünüyor. Bu noktada, EF'i kopyalamaya ve en azından mantıklı davranan SQL'e geri dönmeye hazırım. Ne acı.
Mark Micallef

2
Context.Entry (yourObject) .State'i eklemeden önce kullanabilir miyim?
Thomas Klammer

1
@ mirind4 Durum kullanmadım. Bir nesne eklersem, tüm alt öğelerin boş olduğundan emin olurum. Güncellemede, nesneyi ilk önce çocuk olmadan alırım.
Thomas Klammer

42

Uzun lafın kısası: Yabancı anahtarı kullanın ve gününüzü kurtaracaktır.

Bir Okul varlığınız ve Şehir varlığınız olduğunu varsayın ve bu, bir Şehrin birçok Okulunun olduğu ve bir Okulun da bir Şehre ait olduğu çoka bir ilişkidir. Ve şehirlerin arama tablosunda zaten mevcut olduğunu varsayın, böylece yeni bir okul eklerken yeniden eklenmelerini İSTEMİYORSUNUZ.

Başlangıçta varlıkları şu şekilde tanımlayabilirsiniz:

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [Required]
    public City City { get; set; }
}

Ve Okul eklemesini şu şekilde yapabilirsiniz ( yeni öğeye zaten Şehir mülkünüzün atandığını varsayın ):

public School Insert(School newItem)
{
    using (var context = new DatabaseContext())
    {
        context.Set<School>().Add(newItem);
        // use the following statement so that City won't be inserted
        context.Entry(newItem.City).State = EntityState.Unchanged;
        context.SaveChanges();
        return newItem;
    }
}

Yukarıdaki yaklaşım bu durumda mükemmel şekilde çalışabilir, ancak benim için daha açık ve esnek olan Yabancı Anahtar yaklaşımını tercih ediyorum . Aşağıdaki güncellenmiş çözüme bakın:

public class City
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class School
{
    public int Id { get; set; }
    public string Name { get; set; }

    [ForeignKey("City_Id")]
    public City City { get; set; }

    [Required]
    public int City_Id { get; set; }
}

Bu şekilde, Okulun yabancı bir anahtar City_Id olduğunu ve Şehir varlığına başvurduğunu açıkça tanımlarsınız . Dolayısıyla, Okul eklemesi söz konusu olduğunda şunları yapabilirsiniz:

    public School Insert(School newItem, int cityId)
    {
        if(cityId <= 0)
        {
            throw new Exception("City ID no provided");
        }

        newItem.City = null;
        newItem.City_Id = cityId;

        using (var context = new DatabaseContext())
        {
            context.Set<School>().Add(newItem);
            context.SaveChanges();
            return newItem;
        }
    }

Bu durumda, açıkça belirtmek City_Id yeni kaydın ve kaldırmak Şehir EF ile birlikte bağlamında eklemek için rahatsız etmez, böylece grafikten Okulu .

İlk izlenimde Yabancı anahtar yaklaşımı daha karmaşık görünse de, inanın bana çoktan çoğa bir ilişki eklemek söz konusu olduğunda size çok zaman kazandıracak (bir Okul ve Öğrenci ilişkiniz olduğunu ve Öğrenci bir Şehir mülkü vardır) vb.

Umarım sana yararlı olur.


Harika cevap, bana çok yardımcı oldu! Ancak, [Range(1, int.MaxValue)]özelliği kullanarak City_Id için minimum 1 değerini belirtmek daha iyi olmaz mı?
Dan Rayson

Bir rüya gibi!! Milyonlarca kez teşekkürler!
CJH

Bu, arayandaki School nesnesindeki değeri kaldırır. SaveChanges, boş gezinme özelliklerinin değerini yeniden yükler mi? Aksi takdirde, arayan kişi bu bilgiye ihtiyaç duyuyorsa, Insert () yöntemini çağırdıktan sonra City nesnesini yeniden yüklemelidir. Sık kullandığım kalıp budur, ancak birinin iyi bir modeli varsa, daha iyi kalıplara hala açığım.
19:38

1
Bu bana gerçekten yardımcı oldu. EntityState.Unchaged, tek bir işlem olarak kaydettiğim büyük bir nesne grafiğindeki arama tablosu yabancı anahtarını temsil eden bir nesneyi atamak için tam olarak ihtiyacım olan şeydi. EF Core, sezgisel olandan daha az bir hata iletisi IMO atar. Performans nedenleriyle nadiren değişen arama tablolarımı önbelleğe aldım. Tahmin edersiniz ki, arama tablosu nesnesinin PK'si zaten veritabanındakilerle aynı olduğundan, değişmediğini bildiği ve sadece FK'yi mevcut öğeye atadığıdır. Bunun yerine, arama tablosu nesnesini yeni olarak eklemeye çalışır.
tnk479

Nulling veya durumu değiştirmeden ayarlamanın hiçbir kombinasyonu benim için çalışmıyor. EF, üstteki bir skaler alanı güncellemek istediğimde yeni bir alt kayıt eklemesi gerektiğinde ısrar ediyor.
BobRz

21

Sadece bir üst nesnedeki değişiklikleri depolamak ve alt nesnelerinden herhangi birinde yapılan değişiklikleri depolamaktan kaçınmak istiyorsanız , o zaman neden aşağıdakileri yapmayasınız:

using (var ctx = new MyContext())
{
    ctx.Parents.Attach(parent);
    ctx.Entry(parent).State = EntityState.Added;  // or EntityState.Modified
    ctx.SaveChanges();
}

İlk satır, üst nesneyi ve bağımlı alt nesnelerinin tüm grafiğini Unchangeddurumdaki bağlama ekler .

İkinci satır, yalnızca üst nesnenin durumunu değiştirerek alt öğelerini durumda bırakır Unchanged.

Yeni oluşturulmuş bir bağlam kullandığımı unutmayın, bu nedenle bu, diğer değişiklikleri veritabanına kaydetmeyi önler.


2
Bu cevabın avantajı, eğer birisi daha sonra gelir ve bir alt nesne eklerse, mevcut kodu bozmaz. Bu, diğerlerinin alt nesneleri açıkça hariç tutmanızı gerektirdiği bir "tercih" çözümüdür.
Jim

Bu artık çekirdekte çalışmıyor. "İliştir: Erişilebilir bir varlığın depoda oluşturulmuş bir anahtara sahip olduğu ve hiçbir anahtar değerinin atanmadığı durumlar hariç, erişilebilir her varlığı ekler; bunlar eklendi olarak işaretlenir." Çocuklar da yeniyse, Eklenecekler.
mmix

14

Önerilen çözümlerden biri, navigasyon özelliğini aynı veritabanı bağlamından atamaktır. Bu çözümde, veritabanı bağlamının dışından atanan gezinme özelliği değiştirilecektir. Lütfen örnek için aşağıdaki örneğe bakın.

class Company{
    public int Id{get;set;}
    public Virtual Department department{get; set;}
}
class Department{
    public int Id{get; set;}
    public String Name{get; set;}
}

Veritabanına kaydetme:

 Company company = new Company();
 company.department = new Department(){Id = 45}; 
 //an Department object with Id = 45 exists in database.    

 using(CompanyContext db = new CompanyContext()){
      Department department = db.Departments.Find(company.department.Id);
      company.department = department;
      db.Companies.Add(company);
      db.SaveChanges();
  }

Microsoft bunu bir özellik olarak listeliyor, ancak ben bunu can sıkıcı buluyorum. Şirket nesnesiyle ilişkili departman nesnesinin veritabanında zaten var olan bir kimliği varsa, o zaman EF neden şirket nesnesini veritabanı nesnesiyle ilişkilendirmiyor? Derneğe neden kendi başımıza bakmalıyız? Yeni nesne eklerken navigasyon özelliğine dikkat etmek, veritabanı işlemlerini SQL'den C #'ye taşımak gibi bir şeydir, geliştiriciler için zahmetlidir.


% 100 katılıyorum, EF'in kimlik sağlandığında yeni bir kayıt oluşturmaya çalışmasının saçma olduğunu. Seçme listesi seçeneklerini gerçek nesne ile doldurmanın bir yolu olsaydı, işin içinde olurduk.
T3.0

11

Öncelikle, EF'de varlığı güncellemenin iki yolu olduğunu bilmeniz gerekir.

  • Ekli nesneler

Yukarıda açıklanan yöntemlerden birini kullanarak nesne bağlamına eklenen nesnelerin ilişkisini değiştirdiğinizde, Entity Framework yabancı anahtarları, başvuruları ve koleksiyonları eşitlenmiş durumda tutmalıdır.

  • Bağlantısı kesilen nesneler

Bağlantısı kesilen nesnelerle çalışıyorsanız, senkronizasyonu manuel olarak yönetmeniz gerekir.

Oluşturduğum uygulamada, EF nesne modeli veritabanından yüklenmiyor, düz bir dosyayı ayrıştırırken doldurduğum veri nesneleri olarak kullanılıyor.

Bu, bağlantısız bir nesneyle çalıştığınız anlamına gelir, ancak bağımsız ilişkilendirme veya yabancı anahtar ilişkilendirme kullanıp kullanmadığınız belirsizdir .

  • Ekle

    Mevcut alt nesne (veritabanında bulunan nesne) ile yeni varlık eklerken, alt nesne EF tarafından izlenmiyorsa, alt nesne yeniden eklenecektir. Önce alt nesneyi manuel olarak eklemediğiniz sürece.

      db.Entity(entity.ChildObject).State = EntityState.Modified;
      db.Entity(entity).State = EntityState.Added;
    
  • Güncelleme

    Varlığı değiştirilmiş olarak işaretleyebilirsiniz, ardından tüm skaler özellikler güncellenir ve gezinme özellikleri basitçe yok sayılır.

      db.Entity(entity).State = EntityState.Modified;
    

Grafik Farkı

Bağlantısı kesilmiş nesneyle çalışırken kodu basitleştirmek istiyorsanız, diff kitaplığının grafiğini çizmeyi deneyebilirsiniz .

İşte giriş, bir müstakil birimlerin bir grafiğin otomatikleştirilmiş güncellemeler izin - Varlık Framework Kod Birinci için GraphDiff Tanıtımı .

Basit kod

  • Var değilse varlık ekleyin, aksi takdirde güncelleyin.

      db.UpdateGraph(entity);
    
  • Var değilse varlık ekleyin, aksi takdirde güncelleyin VE mevcut değilse alt nesneyi ekleyin, aksi takdirde güncelleyin.

      db.UpdateGraph(entity, map => map.OwnedEntity(x => x.ChildObject));
    

3

Bunu yapmanın en iyi yolu, veri bağlamınızdaki SaveChanges işlevini geçersiz kılmaktır.

    public override int SaveChanges()
    {
        var added = this.ChangeTracker.Entries().Where(e => e.State == System.Data.EntityState.Added);

        // Do your thing, like changing the state to detached
        return base.SaveChanges();
    }

2

Profili kaydetmeye çalışırken de aynı sorunu yaşıyorum, zaten tablo selamlama ve profil oluşturmak için yeniyim. Profil eklediğimde selamlama da eklenir. Bu yüzden savechanges () önce böyle denedim.

db.Entry (Profile.Salutation) .State = EntityState.Unchanged;


1

Bu benim için çalıştı:

// temporarily 'detach' the child entity/collection to have EF not attempting to handle them
var temp = entity.ChildCollection;
entity.ChildCollection = new HashSet<collectionType>();

.... do other stuff

context.SaveChanges();

entity.ChildCollection = temp;

0

Yaptığımız şey, ebeveyni dbset'e eklemeden önce, alt koleksiyonları üst koleksiyondan ayırmak, daha sonra onlarla çalışmaya izin vermek için mevcut koleksiyonları diğer değişkenlere itmek ve ardından mevcut alt koleksiyonları yeni boş koleksiyonlarla değiştirmek. Çocuk koleksiyonlarını null olarak ayarlamak / hiçbir şey bizim için başarısız görünmüyordu. Bunu yaptıktan sonra ebeveyni dbset'e ekleyin. Bu şekilde çocuklar siz istemedikçe eklenmez.


0

Eski bir gönderi olduğunu biliyorum, ancak kod ilk yaklaşımı kullanıyorsanız, eşleme dosyanızda aşağıdaki kodu kullanarak istenen sonucu elde edebilirsiniz.

Ignore(parentObject => parentObject.ChildObjectOrCollection);

Bu temelde EF'e "ChildObjectOrCollection" özelliğini modelden dışlamasını söyler, böylece veritabanına eşlenmez.


"Yoksay" hangi bağlamda kullanılıyor? Varsayılan bağlamda var gibi görünmüyor.
T3.0

0

Entity Framework Core 3.1.0 kullanarak benzer bir zorluk yaşadım, repo mantığım oldukça genel.

Bu benim için çalıştı:

builder.Entity<ChildEntity>().HasOne(c => c.ParentEntity).WithMany(l =>
       l.ChildEntity).HasForeignKey("ParentEntityId");

Lütfen "ParentEntityId" nin alt varlıktaki yabancı anahtar sütun adı olduğunu unutmayın. Bu yönteme yukarıda belirtilen kod satırını ekledim:

protected override void OnModelCreating(ModelBuilder builder)...
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.