İlişki değiştirilemedi çünkü bir veya daha fazla yabancı anahtar özellik geçersizdir


192

Bir varlık üzerinde GetById () ve daha sonra MVC görünümünden gelen yeni listeme alt varlıklar koleksiyonu ayarlamak bu hatayı alıyorum.

İşlem başarısız oldu: Yabancı anahtar özelliklerinden biri veya daha fazlası geçersiz kılınamadığı için ilişki değiştirilemedi. Bir ilişkide değişiklik yapıldığında, ilgili yabancı anahtar özelliği boş değere ayarlanır. Yabancı anahtar null değerleri desteklemiyorsa, yeni bir ilişki tanımlanmalı, yabancı anahtar özelliğine başka bir null olmayan değer atanmalı veya ilgisiz nesne silinmelidir.

Bu çizgiyi tam olarak anlamıyorum:

İlişki değiştirilemedi çünkü bir veya daha fazla yabancı anahtar özelliği geçersiz kılınamadı.

Neden iki varlık arasındaki ilişkiyi değiştireyim? Tüm uygulamanın ömrü boyunca aynı kalmalıdır.

Kural dışı durumun oluştuğu kod, bir koleksiyondaki değiştirilmiş alt sınıfları varolan üst sınıfa atamak için basittir. Bu, umarım çocuk sınıflarının kaldırılması, yenilerinin eklenmesi ve değişikliklerin yapılmasını sağlayacaktır. Entity Framework'ün bunu ele aldığını düşünürdüm.

Kod satırları şu şekilde distile edilebilir:

var thisParent = _repo.GetById(1);
thisParent.ChildItems = modifiedParent.ChildItems();
_repo.Save();

Aşağıdaki makalede # 2 çözümünü kullanarak cevabımı satın aldım, temel olarak alt tabloya referans için alt tabloya bir birincil anahtar ekledim (böylece 2 birincil anahtar (ana tablo ve kimlik için yabancı anahtar var) çocuk masası için). c-sharpcorner.com/UploadFile/ff2f08/…
yougotiger

@jaffa, cevabımı burada buldum stackoverflow.com/questions/22858491/…
antonio

Yanıtlar:


159

Eski alt öğeleri thisParent.ChildItemsel ile tek tek silmelisiniz . Entity Framework bunu sizin için yapmaz. Son olarak, eski alt öğelerle ne yapmak istediğinize karar veremez - onları atmak istiyorsanız veya bunları saklamak ve diğer ebeveyn varlıklara atamak istiyorsanız. Varlık Çerçevesine kararınızı bildirmelisiniz. Ancak bu iki karardan biri, alt varlıklar veritabanındaki herhangi bir ebeveyne başvurmadan (yabancı anahtar kısıtlaması nedeniyle) tek başına yaşayamayacağı için vermeniz gereken kararlardan biri. Temelde istisna böyle söylüyor.

Düzenle

Alt öğeler eklenebilir, güncellenebilir ve silinebilirse ne yapmalıyım:

public void UpdateEntity(ParentItem parent)
{
    // Load original parent including the child item collection
    var originalParent = _dbContext.ParentItems
        .Where(p => p.ID == parent.ID)
        .Include(p => p.ChildItems)
        .SingleOrDefault();
    // We assume that the parent is still in the DB and don't check for null

    // Update scalar properties of parent,
    // can be omitted if we don't expect changes of the scalar properties
    var parentEntry = _dbContext.Entry(originalParent);
    parentEntry.CurrentValues.SetValues(parent);

    foreach (var childItem in parent.ChildItems)
    {
        var originalChildItem = originalParent.ChildItems
            .Where(c => c.ID == childItem.ID && c.ID != 0)
            .SingleOrDefault();
        // Is original child item with same ID in DB?
        if (originalChildItem != null)
        {
            // Yes -> Update scalar properties of child item
            var childEntry = _dbContext.Entry(originalChildItem);
            childEntry.CurrentValues.SetValues(childItem);
        }
        else
        {
            // No -> It's a new child item -> Insert
            childItem.ID = 0;
            originalParent.ChildItems.Add(childItem);
        }
    }

    // Don't consider the child items we have just added above.
    // (We need to make a copy of the list by using .ToList() because
    // _dbContext.ChildItems.Remove in this loop does not only delete
    // from the context but also from the child collection. Without making
    // the copy we would modify the collection we are just interating
    // through - which is forbidden and would lead to an exception.)
    foreach (var originalChildItem in
                 originalParent.ChildItems.Where(c => c.ID != 0).ToList())
    {
        // Are there child items in the DB which are NOT in the
        // new child item collection anymore?
        if (!parent.ChildItems.Any(c => c.ID == originalChildItem.ID))
            // Yes -> It's a deleted child item -> Delete
            _dbContext.ChildItems.Remove(originalChildItem);
    }

    _dbContext.SaveChanges();
}

Not: Bu test edilmemiştir. Alt öğe koleksiyonunun tür olduğunu varsayar ICollection. (Ben genellikle var IListve daha sonra kod biraz farklı görünüyor.) Ben de basit tutmak için tüm depo soyutlama çıkardı.

Bunun iyi bir çözüm olup olmadığını bilmiyorum, ancak navigasyon koleksiyonundaki her türlü değişikliğe dikkat etmek için bu çizgiler boyunca bir tür sıkı çalışmanın yapılması gerektiğine inanıyorum. Bunu yapmanın daha kolay bir yolunu da görmek beni mutlu eder.


Peki ya sadece bazıları değiştirilirse? Bu yine de onları kaldırıp tekrar eklemem gerektiği anlamına mı geliyor?
jaffa

@Jon: Hayır, elbette mevcut öğeleri de güncelleyebilirsiniz. Çocuk koleksiyonunu nasıl güncelleyeceğime bir örnek ekledim, yukarıdaki Düzenle bölümüne bakın.
Slauma

@Slauma: Lol, cevabını değiştireceğini bilseydim cevabımı yazmazdım ...
Ladislav Mrnka

@Ladislav: Hayır, hayır, kendi cevabınızı yazdığınıza sevindim. Şimdi en azından bunun tam olarak saçmalık olmadığını ve yukarıda yaptığımın çok karmaşık olduğunu biliyorum.
Slauma

1
Foreach içinde originalChildItem alınırken bir koşul eklerdim: ... Nerede (c => c.ID == childItem.ID && c.ID! = 0) aksi takdirde childItem.ID ise yeni eklenen çocukları döndürür == 0.
perfect_element

116

Bunun karşı karşıya kalmanızın nedeni, kompozisyon ve toplama arasındaki farktan kaynaklanmaktadır .

Kompozisyonda, üst nesne oluşturulduğunda alt nesne oluşturulur ve üst öğesi yok edildiğinde yok edilir . Böylece ömrü ebeveynleri tarafından kontrol edilir. Örneğin bir blog yazısı ve yorumları. Bir yayın silinirse, yorumları silinmelidir. Mevcut olmayan bir yayına yorum yapmak mantıklı değil. Siparişler ve sipariş öğeleri için aynı.

Toplamada, alt nesnesi üst öğesinden bağımsız olarak var olabilir . Üst öğe yok edilirse, alt nesne daha sonra farklı bir üst öğeye eklenebileceği için hala var olabilir. örneğin: bir çalma listesi ile o çalma listesindeki şarkılar arasındaki ilişki. Çalma listesi silinirse, şarkılar silinmemelidir. Farklı bir oynatma listesine eklenebilirler.

Varlık Çerçevesi'nin toplama ve kompozisyon ilişkilerini farklılaştırma şekli şöyledir:

  • Kompozisyon için: alt nesnenin bileşik bir birincil anahtara (ParentID, ChildID) sahip olmasını bekler. Çocukların kimlikleri ebeveynleri kapsamında olması gerektiği için bu tasarım gereğidir.

  • Toplama için: alt nesnede bulunan yabancı anahtar özelliğinin boş bırakılabilmesini bekler.

Bu nedenle, bu sorunu yaşamanızın nedeni, birincil anahtarınızı alt tablonuzda nasıl ayarladığınızdan kaynaklanmaktadır. Kompozit olmalı, ama değil. Bu nedenle, Entity Framework bu ilişkilendirmeyi toplama olarak görür, yani alt nesneleri kaldırdığınızda veya temizlediğinizde, alt kayıtları silmez. İlişkilendirmeyi kaldırır ve karşılık gelen yabancı anahtar sütununu NULL olarak ayarlar (böylece bu alt kayıtlar daha sonra farklı bir üst öğeyle ilişkilendirilebilir). Sütununuz NULL'a izin vermediğinden, belirttiğiniz istisnayı alırsınız.

Çözümler:

1- Bileşik anahtar kullanmak istememek için güçlü bir nedeniniz varsa, alt nesneleri açıkça silmeniz gerekir. Ve bu daha önce önerilen çözümlerden daha basit yapılabilir:

context.Children.RemoveRange(parent.Children);

2- Aksi takdirde, alt tablonuzda uygun birincil anahtarı ayarladığınızda, kodunuz daha anlamlı görünecektir:

parent.Children.Clear();

9
Bu açıklamayı çok yardımcı buldum.
Booji Boy

7
Kompozisyona karşı birleştirme ve varlık çerçevesinin kompozisyonla ilişkisi hakkında iyi bir açıklama.
Chrysalis

# 1, sorunu çözmek için gereken en az kod miktarıdır. Teşekkür ederim!
ryanulit

73

Bu çok büyük bir problem. Kodunuzda gerçekte olan şu:

  • ParentVeritabanından yükler ve ekli bir varlık alırsınız
  • Çocuk koleksiyonunu yeni müstakil çocuk koleksiyonuyla değiştirirsiniz
  • Değişiklikleri kaydedersiniz , ancak bu işlem sırasında tüm çocuklar EF'in bu zamana kadar bunları bilmediği için ek olarak kabul edilir . Böylece EF, yaşlı çocukların yabancı anahtarına null değerini ayarlamaya ve tüm yeni çocukları => yinelenen satırları eklemeye çalışır.

Şimdi çözüm gerçekten ne yapmak istediğinize ve nasıl yapmak istediğinize bağlıdır?

ASP.NET MVC kullanıyorsanız UpdateModel veya TryUpdateModel'i kullanmayı deneyebilirsiniz .

Mevcut çocukları manuel olarak güncellemek istiyorsanız, aşağıdakileri yapmanız yeterlidir:

foreach (var child in modifiedParent.ChildItems)
{
    context.Childs.Attach(child); 
    context.Entry(child).State = EntityState.Modified;
}

context.SaveChanges();

Ekleme aslında gerekli değildir (devletin Modifiedvarlığı da ekleyecek şekilde ayarlanması ), ancak süreci daha açık hale getirdiği için seviyorum.

Mevcut olanı değiştirmek, mevcut olanı silmek ve yeni alt öğe eklemek istiyorsanız aşağıdaki gibi bir şey yapmanız gerekir:

var parent = context.Parents.GetById(1); // Make sure that childs are loaded as well
foreach(var child in modifiedParent.ChildItems)
{
    var attachedChild = FindChild(parent, child.Id);
    if (attachedChild != null)
    {
        // Existing child - apply new values
        context.Entry(attachedChild).CurrentValues.SetValues(child);
    }
    else
    {
        // New child
        // Don't insert original object. It will attach whole detached graph
        parent.ChildItems.Add(child.Clone());
    }
}

// Now you must delete all entities present in parent.ChildItems but missing
// in modifiedParent.ChildItems
// ToList should make copy of the collection because we can't modify collection
// iterated by foreach
foreach(var child in parent.ChildItems.ToList())
{
    var detachedChild = FindChild(modifiedParent, child.Id);
    if (detachedChild == null)
    {
        parent.ChildItems.Remove(child);
        context.Childs.Remove(child); 
    }
}

context.SaveChanges();

1
Ancak kullanımı hakkında ilginç bir açıklama var .Clone(). A'nın ChildItembaşka bir alt alt gezinme özelliği olduğunu aklınızda bulunduruyor musunuz? Fakat bu durumda, tüm alt grafiğin içeriğe iliştirilmesini istemez miyiz, çünkü eğer çocuğun kendisi yeniyse tüm alt çocukların yeni nesneler olmasını bekler miyiz? (Peki, modelden modele farklı olabilir, ancak diyelim ki alt çocuklar, çocuk gibi ana babadan bağımlıdır.)
Slauma

Muhtemelen "akıllı" klon gerektirir.
Ladislav Mrnka

1
Bağlamınızda bir Çocuk koleksiyonuna sahip olmak istemezseniz ne olur? http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
Kirsten Greed

1
parent.ChildItems.Remove (çocuk); context.Childs.Remove (çocuk); Bu çift kaldırma sorunu düzeltilebilir TEŞEKKÜRLER. Neden her iki kaldırmaya da ihtiyacımız var? Neden sadece ebeveynden kaldırılıyor.Çocuklar sadece çocuk olarak yaşadıkları için çocuklukları yeterli değil?
Fernando Torres

40

Bu yanıtı aynı hata için çok daha yararlı buldum . Görünüşe göre EF kaldırdığınızda hoşlanmıyor, Sil'i tercih ediyor.

Bunun gibi bir kayda eklenmiş kayıt koleksiyonunu silebilirsiniz.

order.OrderDetails.ToList().ForEach(s => db.Entry(s).State = EntityState.Deleted);

Örnekte, bir Siparişe eklenmiş olan tüm Detay kayıtlarının durumları Sil olarak ayarlanmıştır. (Sipariş güncellemesinin bir parçası olarak Güncellenmiş Ayrıntılar geri ekleme hazırlığında)


Bunun doğru cevap olduğuna inanıyorum.
desmati

mantıklı ve anlaşılır çözüm.
sairfan

19

Diğer iki cevabın neden bu kadar popüler olduğunu bilmiyorum!

ORM çerçevesinin bununla başa çıkacağını varsaymakta haklı olduğunuzu düşünüyorum - sonuçta, sunmayı vaat ettiği şey bu. Aksi takdirde, etki alanı modeliniz kalıcılık kaygılarından dolayı bozulur. Kademeli ayarları doğru şekilde ayarlarsanız NHibernate bunu mutlu bir şekilde yönetir. Entity Framework'te de mümkündür, sadece veritabanı modelinizi ayarlarken, özellikle hangi basamaklandırmanın yapılması gerektiğini çıkarmaları gerektiğinde daha iyi standartları izlemenizi beklerler:

Yapmalisin alt ilişkisi - ebeveyn tanımlamak bir "kullanarak doğru belirlenmesi ilişkiyi ".

Bunu yaparsanız, Entity Framework alt nesnenin üst öğe tarafından tanımlandığını bilir ve bu nedenle bir "basamakla-sil-yetimler" durumu olmalıdır.

Yukarıdaki dışında, belki gerekir (NHibernate deneyiminden)

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

listeyi tamamen değiştirmek yerine.

GÜNCELLEME

@ Slauma'nın yorumu, müstakil varlıkların genel sorunun başka bir parçası olduğunu hatırlattı. Bunu çözmek için, bağlamdan yüklemeye çalışarak modellerinizi oluşturan özel bir model bağlayıcı kullanma yaklaşımını kullanabilirsiniz. Bu blog gönderisi ne demek istediğimin bir örneğini gösteriyor.


İlişki belirleme olarak kurulum burada yardımcı olmaz çünkü sorudaki senaryo müstakil varlıklarla ( "MVC görünümünden gelen yeni listem" ) ilgilenmek zorunda . Yine de orijinal alt öğeleri DB'den yüklemeniz, bu koleksiyondaki kaldırılmış öğeleri ayrılmış koleksiyona göre bulmanız ve sonra DB'den kaldırmanız gerekir. Tek fark, tanımlayıcı bir ilişki ile parent.ChildItems.Removebunun yerine arayabileceğinizdir _dbContext.ChildItems.Remove. Diğer yanıtlardaki gibi uzun kodlardan kaçınmak için EF'in yerleşik desteği hala yoktur (EF <= 6).
Slauma

Ne demek istediğini anlıyorum. Ancak, varlığı bağlamdan yükleyen veya yukarıdaki yaklaşımın işe yarayacağı yeni bir örnek döndüren özel bir model bağlayıcıyla inanıyorum. Bu çözümü önermek için cevabımı güncelleyeceğim.
Andre Luus

Evet, bir model bağlayıcı kullanabilirsiniz, ancak şimdi model bağlayıcıdaki diğer cevaplardan bir şeyler yapmak zorunda kaldınız. Sadece sorunu repo / service katmanından model bağlayıcısına taşır. En azından gerçek bir basitleştirme görmüyorum.
Slauma

Sadeleştirme, yetim kalan varlıkların otomatik olarak silinmesidir. Model bağlayıcıda ihtiyacınız olan tek şeyreturn context.Items.Find(id) ?? new Item()
Andre Luus

EF ekibi için iyi geri bildirim, ancak önerilen çözümünüz ne yazık ki EF ülkesinde hiçbir şeyi çözmüyor.
Chris Moschini

9

Aynı sınıfta Entity Framework ile AutoMapper kullanıyorsanız, bu soruna neden olabilirsiniz. Örneğin sınıfınız

class A
{
    public ClassB ClassB { get; set; }
    public int ClassBId { get; set; }
}

AutoMapper.Map<A, A>(input, destination);

Bu, her iki özelliği de kopyalamaya çalışır. Bu durumda, ClassBId boş bırakılamaz. AutoMapper kopyalayacağından destination.ClassB = input.ClassB;bu bir soruna neden olur.

AutoMapper'ınızı Yoksay ClassBözelliğine ayarlayın .

 cfg.CreateMap<A, A>()
     .ForMember(m => m.ClassB, opt => opt.Ignore()); // We use the ClassBId

AutoMapper ile benzer bir sorunla karşılaşıyorum, ancak bu benim için çalışmıyor :( Bkz. Stackoverflow.com/q/41430679/613605
J86

4

Aynı hatayla karşılaştım. Bir üst alt öğe ilişkisi olan iki tablo var, ancak alt tabloyu tablo tanımındaki yabancı anahtar sütununda bir "silme basamaklı" yapılandırdı. Veritabanındaki üst satırı (SQL aracılığıyla) el ile sildiğimde, alt satırları otomatik olarak siler.

Ancak bu EF çalışmadı, bu iş parçacığında açıklanan hata ortaya çıktı. Bunun nedeni, varlık veri modelimde (edmx dosyası), üst ve alt tablo arasındaki ilişkinin özelliklerinin doğru olmamasıydı. Bu End1 OnDeleteseçenek olarak yapılandırıldı none(modelimde "End1", 1 çokluğuna sahip sondur).

End1 OnDeleteSeçeneği manuel olarak değiştirdim Cascadeve işe yaradı. Modeli veritabanından güncellediğimde neden EF'i bulamıyor bilmiyorum (bir veritabanı ilk modelim var).

Tamlık için, silmek için kodum şöyle görünür:

   public void Delete(int id)
    {
        MyType myObject = _context.MyTypes.Find(id);

        _context.MyTypes.Remove(myObject);
        _context.SaveChanges(); 
   }    

Tanımlanmış basamaklı silme olmasaydı, üst satırı silmeden önce alt satırları el ile silmem gerekirdi.


4

Bunun nedeni, Alt Varlığın Silinmiş yerine Değiştirilmiş olarak işaretlenmiş olmasıdır.

Ve parent.Remove(child)yürütüldüğünde EF'nin Çocuk Varlığa yaptığı değişiklik , sadece üst öğeye referansı ayarlamaktır null.

İstisna gerçekleştiğinde, yürütme işleminden sonra Visual Studio'nun Anında Penceresine aşağıdaki kodu yazarak çocuğun EntityState öğesini kontrol edebilirsiniz SaveChanges():

_context.ObjectStateManager.GetObjectStateEntries(System.Data.EntityState.Modified).ElementAt(X).Entity

burada X, silinmiş Varlık ile değiştirilmelidir.

Erişiminizin yoksa ObjectContextyürütmek için _context.ChildEntity.Remove(child), alt masaya birincil anahtarın yabancı anahtar bir parçası yaparak bu sorunu çözebilir.

Parent
 ________________
| PK    IdParent |
|       Name     |
|________________|

Child
 ________________
| PK    IdChild  |
| PK,FK IdParent |
|       Name     |
|________________|

Bu şekilde, parent.Remove(child)yürütürseniz EF, Varlığı doğru olarak Silindi olarak işaretler.


2

Bu tür bir çözüm benim için hile yaptı:

Parent original = db.Parent.SingleOrDefault<Parent>(t => t.ID == updated.ID);
db.Childs.RemoveRange(original.Childs);
updated.Childs.ToList().ForEach(c => original.Childs.Add(c));
db.Entry<Parent>(original).CurrentValues.SetValues(updated);

Bunun tüm kayıtları sildiğini ve tekrar eklediğini söylemek önemlidir. Ama benim durumum için (10'dan az) tamam.

Umut ediyorum bu yardım eder.


Yeniden yerleştirme yeni kimliklerle mi oluyor yoksa çocuğun kimliklerini ilk etapta mı tutuyor?
Pepito Fernandez

2

Bugün bu problemle karşılaştım ve çözümümü paylaşmak istedim. Benim durumumda çözüm, üst öğe veritabanından almadan önce alt öğeleri silmekti.

Daha önce aşağıdaki kodda olduğu gibi yapıyordum. Daha sonra bu soruda listelenen aynı hatayı alacağım.

var Parent = GetParent(parentId);
var children = Parent.Children;
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

Benim için işe yarayan şey, önce parentId (yabancı anahtar) kullanarak alt öğelere ulaşmak ve daha sonra bu öğeleri silmek. Sonra veritabanından üst alabilir ve bu noktada artık herhangi bir alt öğe olmamalı ve yeni alt öğeler ekleyebilirim.

var children = GetChildren(parentId);
foreach (var c in children )
{
     Context.Children.Remove(c);
}
Context.SaveChanges();

var Parent = GetParent(parentId);
Parent.Children = //assign new entities/items here

2

ChildItems koleksiyonunu el ile temizlemeli ve yeni öğeler eklemelisiniz:

thisParent.ChildItems.Clear();
thisParent.ChildItems.AddRange(modifiedParent.ChildItems);

Bundan sonra yetim varlıklarla işlenecek DeleteOrphans uzantı yöntemini çağırabilirsiniz (DetectChanges ve SaveChanges yöntemleri arasında çağrılmalıdır).

public static class DbContextExtensions
{
    private static readonly ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>> s_navPropMappings = new ConcurrentDictionary< EntityType, ReadOnlyDictionary< string, NavigationProperty>>();

    public static void DeleteOrphans( this DbContext source )
    {
        var context = ((IObjectContextAdapter)source).ObjectContext;
        foreach (var entry in context.ObjectStateManager.GetObjectStateEntries(EntityState.Modified))
        {
            var entityType = entry.EntitySet.ElementType as EntityType;
            if (entityType == null)
                continue;

            var navPropMap = s_navPropMappings.GetOrAdd(entityType, CreateNavigationPropertyMap);
            var props = entry.GetModifiedProperties().ToArray();
            foreach (var prop in props)
            {
                NavigationProperty navProp;
                if (!navPropMap.TryGetValue(prop, out navProp))
                    continue;

                var related = entry.RelationshipManager.GetRelatedEnd(navProp.RelationshipType.FullName, navProp.ToEndMember.Name);
                var enumerator = related.GetEnumerator();
                if (enumerator.MoveNext() && enumerator.Current != null)
                    continue;

                entry.Delete();
                break;
            }
        }
    }

    private static ReadOnlyDictionary<string, NavigationProperty> CreateNavigationPropertyMap( EntityType type )
    {
        var result = type.NavigationProperties
            .Where(v => v.FromEndMember.RelationshipMultiplicity == RelationshipMultiplicity.Many)
            .Where(v => v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.One || (v.ToEndMember.RelationshipMultiplicity == RelationshipMultiplicity.ZeroOrOne && v.FromEndMember.GetEntityType() == v.ToEndMember.GetEntityType()))
            .Select(v => new { NavigationProperty = v, DependentProperties = v.GetDependentProperties().Take(2).ToArray() })
            .Where(v => v.DependentProperties.Length == 1)
            .ToDictionary(v => v.DependentProperties[0].Name, v => v.NavigationProperty);

        return new ReadOnlyDictionary<string, NavigationProperty>(result);
    }
}

Bu benim için iyi çalıştı. Sadece eklemem gerekiyordu context.DetectChanges();.
Andy Edinborough

1

Bu çözümleri ve diğerlerini denedim, ancak hiçbiri işe yaramadı. Bu Google'da ilk cevap olduğu için çözümümü buraya ekleyeceğim.

Benim için iyi çalışan yöntem, taahhütler sırasında ilişkileri resimden çıkarmaktı, bu yüzden EF'in berbat edeceği bir şey yoktu. Bunu DBContext üst nesneyi yeniden bulma ve silerek yaptı. Yeniden bulunan nesnenin gezinme özelliklerinin tümü boş olduğundan, kesinleştirme sırasında çocuk ilişkileri yoksayılır.

var toDelete = db.Parents.Find(parentObject.ID);
db.Parents.Remove(toDelete);
db.SaveChanges();

Bunun yabancı anahtarların ON DELETE CASCADE ile kurulduğunu varsaydığına dikkat edin, bu nedenle üst satır kaldırıldığında çocuklar veritabanı tarafından temizlenecektir.


1

Mosh'ın çözümünü kullandım , ancak kompozisyon anahtarının önce kodda doğru bir şekilde nasıl uygulanacağı bana açık değildi.

İşte çözüm:

public class Holiday
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int HolidayId { get; set; }
    [Key, Column(Order = 1), ForeignKey("Location")]
    public LocationEnum LocationId { get; set; }

    public virtual Location Location { get; set; }

    public DateTime Date { get; set; }
    public string Name { get; set; }
}

1

Aynı problemim vardı, ancak diğer durumlarda iyi çalıştığını biliyordum, bu yüzden sorunu buna indirgedim:

parent.OtherRelatedItems.Clear();  //this worked OK on SaveChanges() - items were being deleted from DB
parent.ProblematicItems.Clear();   // this was causing the mentioned exception on SaveChanges()
  • OtherRelatedItems , bileşik Birincil Anahtar'a (parentId + bazı yerel sütun) sahipti ve Tamam çalıştı
  • ProblematicItems'in kendi tek sütunlu Birincil Anahtarı vardı ve parentId yalnızca bir FK idi. Bu, Clear () öğesinden sonra özel duruma neden oluyordu.

Tek yapmam gereken Ebeveyn Kimliğini çocukların ebeveyn olmadan var olamayacağını göstermek için bileşik PK'nun bir parçası haline getirmekti . DB-ilk modelini kullandım, PK ekledim ve parentId sütununu EntityKey olarak işaretledim (bu yüzden hem DB hem de EF'de güncellemem gerekti - tek başına EF'nin yeterli olup olmadığından emin değilim).

RequestId'i PK'nin bir parçası yaptım Ve sonra EF modelini güncelleyin VE diğer özelliği Varlık Anahtarının bir parçası olarak ayarlayın

Bir kez düşündüğünüzde, EF'nin ebeveynleri olmadan çocukların "mantıklı" olup olmadığına karar vermek için kullandığı çok zarif bir ayrım (bu durumda Clear () onları silmez ve ParentId'i başka bir şeye / özel bir şeye ayarlamazsanız istisna alamaz ) veya - orijinal sorudaki gibi - öğelerin üst öğeden kaldırıldıktan sonra silinmesini bekliyoruz.


0

Bu sorun, üst tablo hala alt tablo verileri var silmeye çalıştığımız için ortaya çıkar. Sorunu basamaklı silme yardımı ile çözüyoruz.

Modelde dbcontext sınıfında Create method.

 modelBuilder.Entity<Job>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                .WithRequired(C => C.Job)
                .HasForeignKey(C => C.JobId).WillCascadeOnDelete(true);
            modelBuilder.Entity<Sport>()
                .HasMany<JobSportsMapping>(C => C.JobSportsMappings)
                  .WithRequired(C => C.Sport)
                  .HasForeignKey(C => C.SportId).WillCascadeOnDelete(true);

Bundan sonra, API Çağrımızda

var JobList = Context.Job                       
          .Include(x => x.JobSportsMappings)                                     .ToList();
Context.Job.RemoveRange(JobList);
Context.SaveChanges();

Basamakla silme seçeneği, bu basit kodla üst öğeyi ve üst öğeyle ilgili alt tabloyu silin. Bu basit şekilde deneyin.

Veritabanındaki kayıtların listesini silmek için kullanılan Aralığı Kaldır Teşekkürler


0

Ben de ilgili sorunu da hallettim Mosh cevabı ve düşündüm PeterB cevabı yabancı anahtar olarak bir enum kullanılan beri biraz oldu. Bu kodu ekledikten sonra yeni bir geçiş eklemeniz gerekeceğini unutmayın.

Bu blog gönderisini diğer çözümler için de önerebilirim:

http://www.kianryan.co.uk/2013/03/orphaned-child/

Kod:

public class Child
{
    [Key, Column(Order = 0), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int Id { get; set; }

    public string Heading { get; set; }
    //Add other properties here.

    [Key, Column(Order = 1)]
    public int ParentId { get; set; }

    public virtual Parent Parent { get; set; }
}

0

Slauma çözümünü kullanarak, alt nesneleri ve alt nesne koleksiyonlarını güncellemeye yardımcı olacak bazı genel işlevler oluşturdum.

Tüm kalıcı nesnelerim bu arayüzü uyguluyor

/// <summary>
/// Base interface for all persisted entries
/// </summary>
public interface IBase
{
    /// <summary>
    /// The Id
    /// </summary>
    int Id { get; set; }
}

Bununla depodaki bu iki işlevi uyguladım

    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public T AddOrUpdateEntry<T>(DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.Id == 0 || orgEntry == null)
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            Context.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public void AddOrUpdateCollection<T>(DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }

Kullanmak için aşağıdakileri yaparım:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);

Bu yardımcı olur umarım


EKSTRA: Ayrıca ayrı bir DbContextExtentions (veya kendi bağlam yüzünüz) sınıfı da yapabilirsiniz:

public static void DbContextExtentions {
    /// <summary>
    /// Check if orgEntry is set update it's values, otherwise add it
    /// </summary>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The collection</param>
    /// <param name="entry">The entry</param>
    /// <param name="orgEntry">The original entry found in the database (can be <code>null</code> is this is a new entry)</param>
    /// <returns>The added or updated entry</returns>
    public static T AddOrUpdateEntry<T>(this DbContext _dbContext, DbSet<T> set, T entry, T orgEntry) where T : class, IBase
    {
        if (entry.IsNew || orgEntry == null) // New or not found in context
        {
            entry.Id = 0;
            return set.Add(entry);
        }
        else
        {
            _dbContext.Entry(orgEntry).CurrentValues.SetValues(entry);
            return orgEntry;
        }
    }

    /// <summary>
    /// check if each entry of the new list was in the orginal list, if found, update it, if not found add it
    /// all entries found in the orignal list that are not in the new list are removed
    /// </summary>
    /// <typeparam name="T">The type of entry</typeparam>
    /// <param name="_dbContext">The context object</param>
    /// <param name="set">The database set</param>
    /// <param name="newList">The new list</param>
    /// <param name="orgList">The original list</param>
    public static void AddOrUpdateCollection<T>(this DbContext _dbContext, DbSet<T> set, ICollection<T> newList, ICollection<T> orgList) where T : class, IBase
    {
        // attach or update all entries in the new list
        foreach (T entry in newList)
        {
            // Find out if we had the entry already in the list
            var orgEntry = orgList.SingleOrDefault(e => e.Id != 0 && e.Id == entry.Id);

            AddOrUpdateEntry(_dbContext, set, entry, orgEntry);
        }

        // Remove all entries from the original list that are no longer in the new list
        foreach (T orgEntry in orgList.Where(e => e.Id != 0).ToList())
        {
            if (!newList.Any(e => e.Id == orgEntry.Id))
            {
                set.Remove(orgEntry);
            }
        }
    }
}

ve şöyle kullanın:

var originalParent = _dbContext.ParentItems
    .Where(p => p.Id == parent.Id)
    .Include(p => p.ChildItems)
    .Include(p => p.ChildItems2)
    .SingleOrDefault();

// Add the parent (including collections) to the context or update it's values (except the collections)
originalParent = _dbContext.AddOrUpdateEntry(_dbContext.ParentItems, parent, originalParent);

// Update each collection
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems, parent.ChildItems, orgiginalParent.ChildItems);
_dbContext.AddOrUpdateCollection(_dbContext.ChildItems2, parent.ChildItems2, orgiginalParent.ChildItems2);


0

Kaydımı silerken bazı sorunlarla karşılaştığımda aynı sorunla karşı karşıya kaldım, çünkü bu sorunun çözümü, sicil / master kaydını silmeden önce bir şey eksik olandan daha fazla bir şey eksik kayıt için kod yazmak zorunda olmasıdır Üstbilgi / Master önce ayrıntılarını silmek Umarım sorun çözülecektir.


-1

Birkaç saat önce bu problemle karşılaştım ve her şeyi denedim, ancak benim durumumda çözüm yukarıda listelenenlerden farklıydı.

Veritabanından önceden alınmış varlığı kullanırsanız ve bu alt öğeyi değiştirmeye çalışırsanız, hata oluşur, ancak varlığın veritabanından yeni bir kopyasını alırsanız herhangi bir sorun olmamalıdır. Bunu kullanmayın:

 public void CheckUsersCount(CompanyProduct companyProduct) 
 {
     companyProduct.Name = "Test";
 }

Bunu kullan:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
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.