संबंध नहीं बदला जा सका क्योंकि विदेशी-कुंजी का एक या अधिक गुण गैर-अशक्त है


192

मुझे यह त्रुटि तब हो रही है जब मैं एक इकाई पर GetById () और फिर मेरी नई सूची में बाल संस्थाओं के संग्रह को सेट करता हूं जो MVC दृश्य से आता है।

ऑपरेशन विफल: संबंध को परिवर्तित नहीं किया जा सका क्योंकि विदेशी कुंजी गुणों में से एक या अधिक गैर-अशक्त है। जब किसी रिश्ते में बदलाव किया जाता है, तो संबंधित विदेशी-कुंजी संपत्ति शून्य मान पर सेट होती है। यदि विदेशी-कुंजी शून्य मानों का समर्थन नहीं करती है, तो एक नए संबंध को परिभाषित किया जाना चाहिए, विदेशी-कुंजी संपत्ति को एक और गैर-शून्य मान सौंपा जाना चाहिए, या असंबंधित ऑब्जेक्ट को हटा दिया जाना चाहिए।

मुझे यह लाइन समझ में नहीं आई:

संबंध नहीं बदला जा सका क्योंकि विदेशी-कुंजी का एक या अधिक गुण गैर-अशक्त है।

मैं 2 संस्थाओं के बीच संबंध क्यों बदलूंगा? यह पूरे आवेदन के पूरे जीवनकाल के दौरान समान रहना चाहिए।

जिस कोड पर अपवाद होता है, वह मौजूदा मूल वर्ग के संग्रह में संशोधित बाल कक्षाओं को सरल रूप से असाइन करना है। यह बच्चे की कक्षाओं को हटाने, नए लोगों को जोड़ने और संशोधनों के लिए उम्मीद करेगा। मैंने सोचा होगा कि एंटिटी फ्रेमवर्क इसे संभालता है।

कोड की पंक्तियों को आसवित किया जा सकता है:

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

मैंने नीचे दिए गए लेख में समाधान # 2 का उपयोग करके अपना उत्तर पाया, मूल रूप से मैंने मूल तालिका के संदर्भ के लिए बाल तालिका में एक प्राथमिक कुंजी जोड़ी है (इसलिए इसमें 2 प्राथमिक कुंजी हैं (मूल तालिका के लिए विदेशी कुंजी और आईडी) बच्चे की मेज के लिए)। c-sharpcorner.com/UploadFile/ff2f08/…
yougotiger

@ जफ्फा, मैंने अपना जवाब यहां दिया stackoverflow.com/questions/22858491/…
antonio

जवाबों:


159

आपको पुराने चाइल्ड आइटम को thisParent.ChildItemsएक-एक करके मैन्युअल रूप से डिलीट करना चाहिए । इकाई फ्रेमवर्क आपके लिए ऐसा नहीं करता है। यह अंत में यह तय नहीं कर सकता है कि आप पुरानी बाल वस्तुओं के साथ क्या करना चाहते हैं - यदि आप उन्हें फेंकना चाहते हैं या यदि आप उन्हें अन्य माता-पिता संस्थाओं को रखना और असाइन करना चाहते हैं। आपको एंटिटी फ्रेमवर्क को अपना निर्णय बताना होगा। लेकिन इन दो फैसलों में से एक आप के लिए है क्योंकि बाल संस्थाएं डेटाबेस में किसी भी माता-पिता के संदर्भ के बिना अकेले नहीं रह सकती हैं (विदेशी कुंजी बाधा के कारण)। यह मूल रूप से अपवाद कहता है।

संपादित करें

यदि बाल आइटम जोड़े, अपडेट किए गए और हटाए जा सकते हैं तो मैं क्या करूंगा:

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

नोट: यह परीक्षण नहीं किया गया है। यह मान लिया गया है कि बाल आइटम संग्रह प्रकार का है ICollection। (मैं आमतौर पर है IListऔर फिर कोड थोड़ा अलग दिखता है।) मैंने इसे सरल रखने के लिए सभी रिपॉजिटरी एब्स्ट्रक्शन भी छीन लिए हैं।

मुझे नहीं पता कि यह एक अच्छा समाधान है, लेकिन मेरा मानना ​​है कि नेविगेशन लाइन में सभी प्रकार के परिवर्तनों का ध्यान रखने के लिए इन पंक्तियों के साथ कुछ प्रकार की कड़ी मेहनत करनी चाहिए। मुझे ऐसा करने का एक आसान तरीका देखकर खुशी होगी।


तो क्या होगा अगर कुछ को केवल बदल दिया जाए? क्या इसका मतलब है कि मुझे अभी भी उन्हें हटाकर फिर से जोड़ना होगा?
जफ्फा

@Jon: नहीं, आप मौजूदा आइटम को भी अपडेट कर सकते हैं। मैंने एक उदाहरण जोड़ा है कि मैं संभवतः बच्चे के संग्रह को कैसे अपडेट करूंगा, ऊपर अनुभाग संपादित करें देखें।
सलुमा

@ सलूमा: लोल, अगर मुझे पता था कि आप अपना जवाब संशोधित करने जा रहे हैं, तो मैं अपना उत्तर नहीं लिखूंगा ...
लद्दिस्लाव मृका

@ लादिस्लाव: नहीं, नहीं, मुझे खुशी है कि आपने अपना जवाब लिखा। अब कम से कम मुझे पता है कि यह पूरी तरह से बकवास नहीं है और बहुत अधिक जटिल है जो मैंने ऊपर किया था।
सल्लूमा

1
जब मैं मूल स्थिति को पुनः प्राप्त करूंगा, तो मैं एक शर्त जोड़ दूंगा: ... जहां (c => c.ID == childItem.ID && c.ID! = 0) अन्यथा यह नए जोड़े गए बच्चों को वापस लौटा देगा यदि childemem.ID! == 0.
परफेक्ट_लेमेंट

116

आपके द्वारा इसका सामना करने का कारण रचना और एकत्रीकरण के बीच का अंतर है ।

रचना में, बच्चे की वस्तु तब बनाई जाती है जब माता-पिता पैदा होते हैं और जब उसके माता-पिता को नष्ट कर दिया जाता है । इसलिए इसका जीवनकाल अपने माता-पिता द्वारा नियंत्रित होता है। उदा ए ब्लॉग पोस्ट और उसकी टिप्पणियाँ। यदि कोई पोस्ट हटा दी जाती है, तो उसकी टिप्पणियों को हटा दिया जाना चाहिए। यह उस पोस्ट के लिए टिप्पणी करने का कोई मतलब नहीं है जो मौजूद नहीं है। ऑर्डर और ऑर्डर आइटम के लिए समान।

एकत्रीकरण में, बच्चा वस्तु अपने माता-पिता के बावजूद मौजूद हो सकती है । यदि माता-पिता नष्ट हो जाते हैं, तो बच्चे की वस्तु अभी भी मौजूद हो सकती है, क्योंकि इसे बाद में एक अलग माता-पिता में जोड़ा जा सकता है। उदाहरण: एक प्लेलिस्ट और उस प्लेलिस्ट के बीच का संबंध। यदि प्लेलिस्ट हटा दी जाती है, तो गाने को हटाया नहीं जाना चाहिए। उन्हें एक अलग प्लेलिस्ट में जोड़ा जा सकता है।

एंटिटी फ्रेमवर्क जिस तरह से एकत्रीकरण और संरचना संबंधों को अलग करता है, वह इस प्रकार है:

  • रचना के लिए: यह उम्मीद करता है कि बच्चे के पास एक समग्र प्राथमिक कुंजी (पेरेंटिड, चाइल्डआईडी) है। यह डिजाइन द्वारा है क्योंकि बच्चों की आईडी उनके माता-पिता के दायरे में होनी चाहिए।

  • एकत्रीकरण के लिए: यह उम्मीद करता है कि बच्चे की वस्तु में विदेशी प्रमुख संपत्ति अशक्त होगी।

इसलिए, आपके पास यह समस्या होने का कारण यह है कि आपने अपनी प्राथमिक कुंजी को अपने बच्चे की तालिका में कैसे सेट किया है। यह समग्र होना चाहिए, लेकिन यह नहीं है। तो, एंटिटी फ्रेमवर्क इस संघ को एकत्रीकरण के रूप में देखता है, जिसका अर्थ है, जब आप बच्चे की वस्तुओं को हटाते हैं या साफ़ करते हैं, तो यह बच्चे के रिकॉर्ड को हटाने वाला नहीं है। यह केवल एसोसिएशन को हटा देगा और इसी विदेशी कुंजी कॉलम को NULL पर सेट करेगा (इसलिए उन बच्चे के रिकॉर्ड को बाद में एक अलग माता-पिता के साथ जोड़ा जा सकता है)। चूंकि आपका कॉलम NULL की अनुमति नहीं देता है, इसलिए आपको वह अपवाद मिल जाता है जिसका आपने उल्लेख किया था।

समाधान:

1-यदि आपके पास समग्र कुंजी का उपयोग नहीं करने का एक मजबूत कारण है, तो आपको स्पष्ट रूप से बच्चे की वस्तुओं को हटाने की आवश्यकता है। और यह पहले सुझाए गए समाधानों की तुलना में सरल किया जा सकता है:

context.Children.RemoveRange(parent.Children);

2- अन्यथा, आपके बच्चे की मेज पर उचित प्राथमिक कुंजी सेट करने से, आपका कोड अधिक सार्थक दिखेगा:

parent.Children.Clear();

9
मुझे यह स्पष्टीकरण सबसे अधिक उपयोगी लगा।
बूजी बॉय

7
रचना बनाम एकत्रीकरण के लिए अच्छी व्याख्या और इकाई संरचना कैसे संबंधित है।
क्रिसलिस

# 1 समस्या को ठीक करने के लिए आवश्यक कम से कम कोड था। धन्यवाद!
राण्युलित

73

यह एक बहुत बड़ी समस्या है। आपके कोड में वास्तव में क्या होता है:

  • आप Parentडेटाबेस से लोड करते हैं और एक संलग्न इकाई प्राप्त करते हैं
  • आप अलग-अलग बच्चों के नए संग्रह के साथ इसके बाल संग्रह को प्रतिस्थापित करते हैं
  • आप परिवर्तन सहेजते हैं, लेकिन इस ऑपरेशन के दौरान सभी बच्चों को माना जाता है क्योंकि इस समय तक EF उनके बारे में नहीं जानता था। इसलिए EF पुराने बच्चों की विदेशी कुंजी को सेट करने और सभी नए बच्चों को डालने की कोशिश करता है => डुप्लिकेट पंक्तियों को।

अब समाधान वास्तव में इस बात पर निर्भर करता है कि आप क्या करना चाहते हैं और आप इसे कैसे करना चाहते हैं?

यदि आप ASP.NET MVC का उपयोग कर रहे हैं तो आप UpdateModel या TryUpdateModel का उपयोग करने का प्रयास कर सकते हैं ।

यदि आप मौजूदा बच्चों को मैन्युअल रूप से अपडेट करना चाहते हैं, तो आप बस कुछ ऐसा कर सकते हैं:

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

context.SaveChanges();

संलग्न करना वास्तव में आवश्यक नहीं है (राज्य को Modifiedभी इकाई को संलग्न करना होगा) लेकिन मुझे यह पसंद है क्योंकि यह प्रक्रिया को और अधिक स्पष्ट करता है।

यदि आप मौजूदा को संशोधित करना चाहते हैं, तो मौजूदा को हटा दें और नए चिल्ड सम्मिलित करें, आपको कुछ ऐसा करना होगा:

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
लेकिन उपयोग करने के बारे में आपकी दिलचस्प टिप्पणी है .Clone()। क्या आपके पास ऐसा मामला है, ChildItemजिसमें अन्य उप-बाल नेविगेशन गुण हैं? लेकिन उस मामले में, क्या हम नहीं चाहेंगे कि पूरा उप-ग्राफ़ संदर्भ से जुड़ा हो क्योंकि हम उम्मीद करेंगे कि सभी उप-चिल्ड नई वस्तुएं हैं यदि बच्चा खुद नया है? (ठीक है, मॉडल से मॉडल में भिन्न हो सकते हैं, लेकिन चलो यह मान लेते हैं कि उप-चिल्ड बच्चे से "आश्रित" होते हैं, जैसे कि बच्चे माता-पिता से निर्भर होते हैं।)
सूलुमा

इसे संभवतः "स्मार्ट" क्लोन की आवश्यकता होगी।
लादिस्लाव Mrnka

1
यदि आप अपने संदर्भ में बाल संग्रह नहीं करना चाहते हैं तो क्या होगा? http://stackoverflow.com/questions/20233994/do-i-need-to-create-a-dbset-for-every-table-so-that-i-can-persist-child-entitie
कर्स्टन लालच

1
parent.ChildItems.Remove (बच्चे); context.Childs.Remove (बच्चे); यह डबल निकालें फिक्स्ड जारी कर सकता है, धन्यवाद। हमें दोनों को हटाने की आवश्यकता क्यों है? सिर्फ माता-पिता से क्यों हटाया जा रहा है। बच्चों के लिए यह पर्याप्त नहीं है क्योंकि बच्चे केवल बच्चों के रूप में रहते हैं?
फर्नांडो टॉरेस

40

मुझे यह मिल गया उत्तर उसी त्रुटि के लिए अधिक उपयोगी लगा। ऐसा लगता है कि जब आप हटाते हैं तो ईएफ इसे पसंद नहीं करता है, यह डिलीट को प्राथमिकता देता है।

आप इस तरह से रिकॉर्ड से जुड़े रिकॉर्ड का संग्रह हटा सकते हैं।

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

उदाहरण में, ऑर्डर से जुड़े सभी डिटेल रिकॉर्ड्स को डिलीट करने के लिए उनका स्टेट सेट है। (ऑर्डर अपडेट के हिस्से के रूप में अद्यतन विवरण जोड़ने की तैयारी में)


मेरा मानना ​​है कि यह उचित उत्तर है।
देस्मती

तार्किक और सीधा समाधान।
सायरन

19

मुझे नहीं पता कि अन्य दो उत्तर इतने लोकप्रिय क्यों हैं!

मेरा मानना ​​है कि ओआरएम ढांचे को संभालने में आप सही थे - आखिरकार, यह वही है जो देने का वादा करता है। अन्यथा आपका डोमेन मॉडल दृढ़ता चिंताओं से दूषित हो जाता है। यदि आप कैस्केड सेटिंग्स को सही तरीके से सेट करते हैं, तो NHibernate इसे खुशी से प्रबंधित करता है। एंटिटी फ्रेमवर्क में भी यह संभव है, वे आपसे अपेक्षा करते हैं कि आप अपने डेटाबेस मॉडल को सेट करते समय बेहतर मानकों का पालन करें, खासकर जब उन्हें अनुमान लगाना होगा कि कैस्केडिंग क्या किया जाना चाहिए:

आपको " पहचान वाले रिश्ते " का उपयोग करके माता-पिता के बाल संबंधों को सही ढंग से परिभाषित करना होगा " ।

यदि आप ऐसा करते हैं, तो एंटिटी फ्रेमवर्क जानता है कि बच्चे की वस्तु की पहचान की गई है माता-पिता द्वारा जाता है, और इसलिए इसे "कैस्केड-डिलीट-अनाथ" स्थिति होना चाहिए।

उपरोक्त के अलावा, आपको (NHibernate अनुभव से) की आवश्यकता हो सकती है

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

इसके बजाय पूरी तरह से सूची की जगह।

अपडेट करें

@ सलुमा की टिप्पणी ने मुझे याद दिलाया कि अलग-अलग संस्थाएँ समग्र समस्या का एक और हिस्सा हैं। इसे हल करने के लिए, आप कस्टम मॉडल बाइंडर का उपयोग करने का दृष्टिकोण ले सकते हैं जो आपके मॉडल को संदर्भ से लोड करने का प्रयास करके निर्माण करता है। यह ब्लॉग पोस्ट मेरे उदाहरण का एक उदाहरण दिखाता है।


रिश्ते की पहचान के रूप में सेटअप यहां मदद नहीं करेगा क्योंकि प्रश्न में परिदृश्य को अलग - थलग संस्थाओं ( "मेरी नई सूची जो एमवीसी दृश्य से आती है" ) से निपटना होगा । आपको अभी भी मूल बच्चों को डीबी से लोड करना है, हटाए गए संग्रह के आधार पर उस संग्रह में हटाए गए आइटम ढूंढें और फिर डीबी से हटा दें। फर्क सिर्फ इतना है कि एक पहचान वाले रिश्ते के parent.ChildItems.Removeबजाय आप कॉल कर सकते हैं _dbContext.ChildItems.Remove। अब भी है (EF <= 6) अन्य उत्तरों में से एक जैसे लंबे कोड से बचने के लिए EF से कोई अंतर्निहित समर्थन नहीं है।
सलुमा

मैं आपकी बात समझता हूं। हालाँकि, मैं एक कस्टम मॉडल बाइंडर के साथ विश्वास करता हूं जो इकाई को संदर्भ से लोड करता है या एक नया उदाहरण देता है जो ऊपर दिए गए दृष्टिकोण से काम करेगा। मैं उस समाधान का सुझाव देने के लिए अपने उत्तर को अपडेट करूंगा।
आंद्रे लुस

हां, आप एक मॉडल बाइंडर का उपयोग कर सकते हैं, लेकिन आपको अब मॉडल बाइंडर में अन्य उत्तरों से सामान करना होगा। यह सिर्फ रेपो / सर्विस लेयर से समस्या को मॉडल बाइंडर में ले जाता है। कम से कम, मुझे एक वास्तविक सरलीकरण दिखाई नहीं देता है।
सलुमा S

सरलीकरण अनाथ संस्थाओं का स्वत: विलोपन है। आप सभी को मॉडल बांधने की मशीन की आवश्यकता हैreturn context.Items.Find(id) ?? new Item()
आंद्रे लुस

EF टीम के लिए अच्छी प्रतिक्रिया, लेकिन आपका प्रस्तावित समाधान EF भूमि में दुर्भाग्य से कुछ भी हल नहीं करता है।
क्रिस मोसचिनी

9

यदि आप एक ही वर्ग पर इकाई फ्रेमवर्क के साथ AutoMapper का उपयोग कर रहे हैं, तो आप इस समस्या को मार सकते हैं। उदाहरण के लिए यदि आपकी कक्षा है

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

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

यह दोनों गुणों को कॉपी करने का प्रयास करेगा। इस मामले में, ClassBId गैर अशक्त है। चूंकि AutoMapper कॉपी करेगाdestination.ClassB = input.ClassB; यह समस्या पैदा करेगा।

ClassBसंपत्ति को नजरअंदाज करने के लिए अपने AutoMapper सेट करें ।

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

मैं AutoMapper के साथ एक समान समस्या का सामना कर रहा हूं, लेकिन यह मेरे लिए काम नहीं करता है :( देखें stackoverflow.com/q/41430679/613605
J86

4

मेरे पास बस वही त्रुटि थी। मेरे पास माता-पिता के बच्चे के रिश्ते के साथ दो तालिकाओं हैं, लेकिन मैंने चाइल्ड टेबल की तालिका परिभाषा में विदेशी कुंजी कॉलम पर "डिलीट कैस्केड" पर कॉन्फ़िगर किया है। इसलिए जब मैं डेटाबेस में मूल पंक्ति (SQL के माध्यम से) को मैन्युअल रूप से हटाता हूं तो यह स्वचालित रूप से चाइल्ड रो को हटा देगा।

हालाँकि, यह EF में काम नहीं करता था, इस थ्रेड में वर्णित त्रुटि दिखाई दी। इसका कारण यह था, कि मेरी इकाई डेटा मॉडल (edmx फ़ाइल) में माता-पिता और बच्चे की तालिका के बीच संबंध के गुण सही नहीं थे। End1 OnDeleteविकल्प हो करने के लिए कॉन्फ़िगर किया गया थाnone (अपने मॉडल में "END1" अंत जो 1 की बहुलता है)।

मैंने मैन्युअल रूप से End1 OnDeleteविकल्प को बदल दिया Cascadeऔर इससे काम किया। मुझे नहीं पता कि ईएफ इसे क्यों नहीं उठा पा रहा है, जब मैं डेटाबेस से मॉडल को अपडेट करता हूं (मेरे पास एक डेटाबेस पहला मॉडल है)।

पूर्णता के लिए, यह मेरा कोड हटाने का तरीका है:

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

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

यदि मुझे परिभाषित नहीं किया गया है, तो मुझे मूल पंक्ति हटाने से पहले चाइल्ड रो को डिलीट करना होगा।


4

ऐसा इसलिए होता है क्योंकि चाइल्ड एंटिटी को हटाए गए के बजाय संशोधित के रूप में चिह्नित किया जाता है।

और ईएफएफ parent.Remove(child)को निष्पादित करने पर बाल इकाई को जो संशोधन होता है, वह बस अपने माता-पिता के लिए संदर्भ निर्धारित करता है null

निष्पादन के बाद, अपवाद होने पर आप विजुअल स्टूडियो की तत्काल विंडो में निम्न कोड लिखकर बच्चे के एंटिटीस्टैट की जांच कर सकते हैं SaveChanges():

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

जहां X को हटाए गए एंटिटी द्वारा प्रतिस्थापित किया जाना चाहिए।

यदि आपके पास ObjectContextनिष्पादन के लिए पहुंच नहीं है _context.ChildEntity.Remove(child), तो आप इस मुद्दे को चाइल्ड टेबल पर विदेशी कुंजी को प्राथमिक कुंजी का हिस्सा बनाकर हल कर सकते हैं।

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

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

इस तरह, यदि आप निष्पादित करते हैं parent.Remove(child), तो ईएफ सही ढंग से हटाए गए के रूप में इकाई को चिह्नित करेगा।


2

इस प्रकार के समाधान ने मेरे लिए चाल चली:

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

यह कहना महत्वपूर्ण है कि यह सभी रिकॉर्ड हटा देता है और उन्हें फिर से सम्मिलित करता है। लेकिन मेरे मामले के लिए (कम तो 10) ठीक है।

मुझे उम्मीद है यह मदद करेगा।


क्या पुनर्मूल्यांकन नई आईडी के साथ होता है या यह बच्चे की आईडी रखता है जो उनके पास पहले था?
पेपिटो फर्नांडीज

2

मैं आज इस समस्या में भाग गया और अपना समाधान साझा करना चाहता था। मेरे मामले में, समाधान माता-पिता को डेटाबेस से प्राप्त करने से पहले बाल वस्तुओं को हटाने के लिए था।

पहले मैं इसे नीचे दिए गए कोड की तरह कर रहा था। फिर मुझे इस प्रश्न में सूचीबद्ध समान त्रुटि मिलेगी।

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

मेरे लिए जो काम किया गया है, वह है कि बच्चों को पहले आइटम मिलें, पेरेंटड (विदेशी कुंजी) का उपयोग करें और फिर उन वस्तुओं को हटा दें। फिर मैं जनक को डेटाबेस से प्राप्त कर सकता हूं और उस बिंदु पर, इसके पास अब कोई भी बच्चा आइटम नहीं होना चाहिए और मैं नए बच्चों के आइटम जोड़ सकता हूं।

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

आपको चाइल्डइम्स संग्रह को मैन्युअल रूप से साफ़ करना होगा और उसमें नई वस्तुओं को जोड़ना होगा:

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

उसके बाद आप DeleteOrphans एक्सटेंशन मेथड को कॉल कर सकते हैं जो अनाथ संस्थाओं के साथ काम करेगा (इसे DetectChanges और SaveChanges के तरीकों के बीच कहा जाना चाहिए)।

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

इसने मेरे लिए अच्छा काम किया। मुझे बस जोड़ने की जरूरत थी context.DetectChanges();
एंडी एडिनबरो

1

मैंने इन समाधानों और कई अन्य लोगों की कोशिश की है, लेकिन उनमें से कोई भी काफी काम नहीं करता है। चूंकि यह Google पर पहला उत्तर है, मैं अपना समाधान यहां जोड़ूंगा।

मेरे लिए अच्छी तरह से काम करने वाला तरीका था, कमिट के दौरान रिश्तों को तस्वीर से बाहर निकालना, इसलिए EF के लिए कुछ भी नहीं था। मैंने DBContext में पैरेंट ऑब्जेक्ट को री-फाइंड करके और डिलीट करके ऐसा किया। चूंकि पुनः पाया गया ऑब्जेक्ट का नेविगेशन गुण सभी अशक्त हैं, इसलिए प्रतिबद्ध होने के दौरान बच्चों के रिश्तों को अनदेखा किया जाता है।

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

ध्यान दें कि यह मानती है कि विदेशी कुंजियाँ ON DELETE CASCADE के साथ सेटअप की गई हैं, इसलिए जब मूल पंक्ति को हटा दिया जाता है, तो बच्चों को डेटाबेस द्वारा साफ कर दिया जाएगा।


1

मैंने मॉश के समाधान का उपयोग किया , लेकिन यह मेरे लिए स्पष्ट नहीं था कि पहले कोड में रचना की कुंजी को सही तरीके से कैसे लागू किया जाए।

तो यहाँ समाधान है:

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

मुझे भी यही समस्या थी, लेकिन मुझे पता था कि इसने अन्य मामलों में ठीक काम किया है, इसलिए मैंने इस समस्या को कम कर दिया:

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 के पास एक समग्र प्राथमिक कुंजी थी (parentId + कुछ स्थानीय स्तंभ) और ठीक काम किया
  • ProblematicItems अपने स्वयं के एकल-स्तंभ प्राथमिक कुंजी थी, और parentId था केवल एक FK। यह स्पष्ट () के बाद अपवाद पैदा कर रहा था।

माता-पिता को समग्र पीके का हिस्सा बनाने के लिए मुझे यह संकेत देना था कि बच्चे माता-पिता के बिना नहीं रह सकते। मैंने DB-पहले मॉडल का इस्तेमाल किया, PK जोड़ा और EntityKey के रूप में parentId कॉलम को चिह्नित किया (इसलिए, मुझे इसे DB और EF दोनों में अपडेट करना था - निश्चित नहीं कि अगर EF अकेले पर्याप्त होगा)।

मैंने RequestId को PK का हिस्सा बनाया और फिर ईएफ मॉडल को अपडेट किया, और एंटिटी की के हिस्से के रूप में अन्य संपत्ति निर्धारित की

एक बार जब आप इसके बारे में सोचते हैं, तो यह एक बहुत ही सुंदर अंतर है जिसे EF यह तय करने के लिए उपयोग करता है कि क्या बच्चे माता-पिता के बिना "समझदारी" बनाते हैं (इस मामले में स्पष्ट) उन्हें हटा नहीं देगा और जब तक आप कुछ और / विशेष करने के लिए पेरेंट को सेट नहीं कर देते, तब तक अपवाद नहीं फेंकेंगे। ), या - जैसे मूल प्रश्न - हम अपेक्षा करते हैं कि माता-पिता द्वारा हटाए जाने के बाद आइटम हटा दिए जाएंगे।


0

यह समस्या इसलिए उत्पन्न होती है क्योंकि हम पैरेंट टेबल को हटाने का प्रयास करते हैं फिर भी चाइल्ड टेबल डेटा मौजूद है। हम कैस्केड हटाने की मदद से समस्या को हल करते हैं।

मॉडल में dbcontext वर्ग में विधि बनाएँ।

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

उसके बाद, हमारे एपीआई कॉल में

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

कैस्केड डिलीट ऑप्शन इस सिंपल कोड के साथ पेरेंट को पेरेंट के साथ ही चाइल्ड टेबल से संबंधित चाइल्ड टेबल डिलीट कर देता है। इसे इस सरल तरीके से आज़माएं।

निकालें रेंज जो डेटाबेस में रिकॉर्ड की सूची को हटाने के लिए उपयोग किया जाता है धन्यवाद


0

मैं भी साथ मेरी समस्या हल Mosh के जवाब और मैंने सोचा कि PeterB के जवाब के बाद से यह विदेशी कुंजी के रूप में एक enum इस्तेमाल किया एक सा था। याद रखें कि इस कोड को जोड़ने के बाद आपको एक नया माइग्रेशन जोड़ना होगा।

मैं इस ब्लॉग पोस्ट को अन्य समाधानों के लिए भी सुझा सकता हूं:

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

कोड:

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

सलुमा के समाधान का उपयोग करते हुए, मैंने बाल वस्तुओं और बाल वस्तुओं के संग्रह को अद्यतन करने में मदद करने के लिए कुछ सामान्य कार्य बनाए।

मेरी सभी लगातार वस्तुएँ इस इंटरफ़ेस को लागू करती हैं

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

इसके साथ मैंने अपने रिपोजिटरी में इन दोनों कार्यों को लागू किया

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

इसका उपयोग करने के लिए मैं निम्नलिखित कार्य करता हूं:

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

उम्मीद है की यह मदद करेगा


अतिरिक्त: आप एक अलग DbContextExtentions (या अपनी खुद की संदर्भ अनुमान) श्रेणी भी बना सकते हैं:

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

और इसका उपयोग करें:

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

आप इन फ़ंक्शंस के साथ अपने संदर्भ के लिए एक एक्स्टेंशन क्लास भी बना सकते हैं:
Bluemoon74

0

मैं एक ही समस्या का सामना कर रहा था जब मैं अपने रिकॉर्ड को हटाने जा रहा था जब कुछ समस्या हुई थी, इस समस्या का समाधान यह है कि जब आप हेडर / मास्टर रिकॉर्ड को हटाने से पहले किसी चीज़ को याद करने से पहले अपना रिकॉर्ड हटाने जा रहे हों तो आपको कोड के लिए लिखना होगा हेडर / मास्टर से पहले इसके विवरण को हटा दें। मुझे आशा है कि आप जारी करेंगे।


-1

मैं कई घंटों से पहले इस समस्या से मिला हूं और सब कुछ करने की कोशिश कर रहा हूं, लेकिन मेरे मामले में समाधान ऊपर सूचीबद्ध से अलग था।

यदि आप डेटाबेस से पहले से ही प्राप्त इकाई का उपयोग करते हैं और इसे संशोधित करने का प्रयास करते हैं, तो यह त्रुटि उत्पन्न हो जाएगी, लेकिन यदि आपको डेटाबेस से इकाई की नई प्रतिलिपि मिलती है तो कोई समस्या नहीं होनी चाहिए। इसका उपयोग न करें:

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

इसे इस्तेमाल करो:

 public void CheckUsersCount(Guid companyProductId)
 {
      CompanyProduct companyProduct = CompanyProductManager.Get(companyProductId);
      companyProduct.Name = "Test";
 }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.