EF में मूल संस्था को अद्यतन करते समय बाल संस्थाओं को कैसे जोड़ें / अपडेट करें


151

दो संस्थाएं एक-से-कई संबंध हैं (कोड पहले धाराप्रवाह द्वारा निर्मित)।

public class Parent
{
    public Parent()
    {
        this.Children = new List<Child>();
    }

    public int Id { get; set; }

    public virtual ICollection<Child> Children { get; set; }
}

public class Child
{
    public int Id { get; set; }

    public int ParentId { get; set; }

    public string Data { get; set; }
}

मेरे WebApi नियंत्रक में मेरे पास पैरेंट एंटिटी (जो ठीक काम कर रही है) बनाने के लिए एक्शन है और पैरेंट एंटिटी (जिसमें कुछ समस्या है) को अपडेट करता है। अद्यतन कार्रवाई इस प्रकार है:

public void Update(UpdateParentModel model)
{
    //what should be done here?
}

वर्तमान में मेरे पास दो विचार हैं:

  1. नाम existingसे ट्रैक की गई मूल इकाई प्राप्त करें model.Id, और modelएक से एक इकाई में मान असाइन करें । यह बेवकूफ लगता है। और model.Childrenमुझे नहीं पता कि कौन सा बच्चा नया है, कौन सा बच्चा संशोधित है (या हटा दिया गया है)।

  2. के माध्यम से एक नई मूल इकाई बनाएँ model, और इसे DbContext में संलग्न करें और इसे सहेजें। लेकिन DbContext बच्चों की स्थिति (नया ऐड / डिलीट / संशोधित) कैसे जान सकता है?

इस सुविधा को लागू करने का सही तरीका क्या है?


एक डुप्लिकेट प्रश्न stackoverflow.com/questions/29351401/…
माइकल फ्रीजिम

जवाबों:


219

क्योंकि जो मॉडल वेबएपीआई नियंत्रक को पोस्ट किया जाता है, उसे किसी भी इकाई-ढांचे (EF) के संदर्भ से अलग किया जाता है, एकमात्र विकल्प डेटाबेस से ऑब्जेक्ट ग्राफ (उसके बच्चों सहित माता-पिता) को लोड करना और तुलना करना है कि कौन से बच्चे जोड़े गए हैं, हटाए गए हैं या अपडेट किया गया। (जब तक आप अलग किए गए राज्य (ब्राउज़र में या जहाँ भी हो) के दौरान अपने स्वयं के ट्रैकिंग तंत्र के साथ परिवर्तनों को ट्रैक करेंगे जो मेरी राय में निम्नलिखित की तुलना में अधिक जटिल हैं।) यह इस तरह दिख सकता है:

public void Update(UpdateParentModel model)
{
    var existingParent = _dbContext.Parents
        .Where(p => p.Id == model.Id)
        .Include(p => p.Children)
        .SingleOrDefault();

    if (existingParent != null)
    {
        // Update parent
        _dbContext.Entry(existingParent).CurrentValues.SetValues(model);

        // Delete children
        foreach (var existingChild in existingParent.Children.ToList())
        {
            if (!model.Children.Any(c => c.Id == existingChild.Id))
                _dbContext.Children.Remove(existingChild);
        }

        // Update and Insert children
        foreach (var childModel in model.Children)
        {
            var existingChild = existingParent.Children
                .Where(c => c.Id == childModel.Id)
                .SingleOrDefault();

            if (existingChild != null)
                // Update child
                _dbContext.Entry(existingChild).CurrentValues.SetValues(childModel);
            else
            {
                // Insert child
                var newChild = new Child
                {
                    Data = childModel.Data,
                    //...
                };
                existingParent.Children.Add(newChild);
            }
        }

        _dbContext.SaveChanges();
    }
}

...CurrentValues.SetValuesकिसी भी वस्तु को ले सकते हैं और संपत्ति के नाम के आधार पर संपत्ति के मान को संलग्न कर सकते हैं। यदि आपके मॉडल में मौजूद प्रॉपर्टी के नाम उस इकाई के नामों से अलग हैं, तो आप इस पद्धति का उपयोग नहीं कर सकते हैं और एक-एक करके मानों को असाइन करना होगा।


35
लेकिन ef के पास अधिक "शानदार" तरीका क्यों नहीं है? मुझे लगता है कि यदि बच्चा संशोधित / हटाए गए / जोड़े गए है तो पता लगा सकता है कि आईएमओ आपके कोड से ऊपर ईएफ ढांचे का हिस्सा हो सकता है और अधिक सामान्य समाधान बन सकता है।
चेंग चेन

7
@ डैनीचेन: यह वास्तव में एक लंबा अनुरोध है कि डिस्कनेक्ट की गई संस्थाओं को अपडेट करने में ईएफ़ द्वारा अधिक आरामदायक तरीके से समर्थन किया जाना चाहिए । वर्तमान में आप केवल उस कोडप्लेक्स वर्किटेम में उल्लिखित तीसरे पक्ष के लिबास "ग्राफडिफ़" को आजमा सकते हैं या ऊपर दिए गए मेरे उत्तर की तरह मैनुअल कोड लिख सकते हैं।
सुलामा

7
जोड़ने के लिए एक चीज: अद्यतन के लिए और बच्चों को सम्मिलित करने के लिए, आप ऐसा नहीं कर सकते, existingParent.Children.Add(newChild)क्योंकि तब मौजूदा लिंक्ड खोज हाल ही में जोड़ी गई इकाई को वापस कर देगी, और इसलिए उस इकाई को अपडेट किया जाएगा। आपको बस एक अस्थायी सूची में सम्मिलित करना होगा और फिर जोड़ना होगा।
इररे एफे

3
@ RandolfRincónFadul मैं अभी इस मुद्दे पर आता हूं। मेरा फिक्स जो थोड़ा कम प्रयास है, जहां existingChildLINQ क्वेरी में क्लॉज को बदलना है :.Where(c => c.ID == childModel.ID && c.ID != default(int))
गेविन वार्ड

2
@RalphWillgoss 2.2 में फिक्स क्या आप के बारे में बात कर रहे थे?
जन पाओलो गो

11

मैं इस तरह से कुछ के साथ खिलवाड़ कर रहा हूँ ...

protected void UpdateChildCollection<Tparent, Tid , Tchild>(Tparent dbItem, Tparent newItem, Func<Tparent, IEnumerable<Tchild>> selector, Func<Tchild, Tid> idSelector) where Tchild : class
    {
        var dbItems = selector(dbItem).ToList();
        var newItems = selector(newItem).ToList();

        if (dbItems == null && newItems == null)
            return;

        var original = dbItems?.ToDictionary(idSelector) ?? new Dictionary<Tid, Tchild>();
        var updated = newItems?.ToDictionary(idSelector) ?? new Dictionary<Tid, Tchild>();

        var toRemove = original.Where(i => !updated.ContainsKey(i.Key)).ToArray();
        var removed = toRemove.Select(i => DbContext.Entry(i.Value).State = EntityState.Deleted).ToArray();

        var toUpdate = original.Where(i => updated.ContainsKey(i.Key)).ToList();
        toUpdate.ForEach(i => DbContext.Entry(i.Value).CurrentValues.SetValues(updated[i.Key]));

        var toAdd = updated.Where(i => !original.ContainsKey(i.Key)).ToList();
        toAdd.ForEach(i => DbContext.Set<Tchild>().Add(i.Value));
    }

जिसे आप कुछ इस तरह से कॉल कर सकते हैं:

UpdateChildCollection(dbCopy, detached, p => p.MyCollectionProp, collectionItem => collectionItem.Id)

दुर्भाग्य से, यह थोथा खत्म हो जाता है अगर बच्चे के प्रकार पर संग्रह गुण हैं जिन्हें अद्यतन करने की भी आवश्यकता है। एक IRepository (मूल CRUD विधियों के साथ) पास करके इसे हल करने की कोशिश को ध्यान में रखते हुए, जो कि UpdateChildCollection को अपने आप में कॉल करने के लिए जिम्मेदार होगा। DbContext.Entry पर सीधे कॉल के बजाय रेपो को कॉल करेगा।

पता नहीं कैसे यह सभी बड़े पैमाने पर प्रदर्शन करेंगे, लेकिन यह सुनिश्चित नहीं करेंगे कि इस समस्या का क्या करना है।


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

1
toAdd.ForEach(i => (selector(dbItem) as ICollection<Tchild>).Add(i.Value));n -> n समस्या को हल करना चाहिए।
रेनस्टेन

10

ठीक है दोस्तों। मेरे पास इसका उत्तर एक बार था लेकिन मैंने इसे खो दिया। पूर्ण यातना जब आप जानते हैं कि एक बेहतर तरीका है, लेकिन इसे याद नहीं कर सकते हैं या इसे नहीं पा सकते हैं! यह बहुत सरल है। मैंने इसे कई तरीकों से जांचा।

var parent = _dbContext.Parents
  .Where(p => p.Id == model.Id)
  .Include(p => p.Children)
  .FirstOrDefault();

parent.Children = _dbContext.Children.Where(c => <Query for New List Here>);
_dbContext.Entry(parent).State = EntityState.Modified;

_dbContext.SaveChanges();

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


बस मुझे क्या चाहिए, क्योंकि मेरे मॉडल में बच्चों की संख्या आम तौर पर काफी कम है, इसलिए यह मानते हुए कि लिनक शुरू में सभी मूल बच्चों को हटा देगा और फिर सभी नए लोगों को जोड़ देगा प्रदर्शन प्रभाव एक मुद्दा नहीं है।
विलियम टी। मल्लार्ड

@Charles McIntosh। मैं नहीं जानता कि आप बच्चों को फिर से सेट क्यों करते हैं जब आप इसे प्रारंभिक क्वेरी में शामिल करते हैं?
पैंटोनिस

1
@ पैंटोनिस I में बाल संग्रह शामिल है ताकि इसे संपादन के लिए लोड किया जा सके। अगर मैं यह पता लगाने के लिए आलसी लोडिंग पर भरोसा करता हूं तो यह काम नहीं करता है। मैंने बच्चों को (एक बार) सेट किया क्योंकि मैन्युअल रूप से हटाने और संग्रह में आइटम जोड़ने के बजाय मैं बस सूची को बदल सकता हूं और Unitframework मेरे लिए आइटम जोड़ और हटा देगा। कुंजी इकाई की स्थिति को संशोधित करने और भारी भार उठाने के लिए Unitframework की अनुमति देने के लिए सेट कर रही है।
चार्ल्स मैकिन्टोश

@CharlesMcIntosh मुझे अभी भी समझ में नहीं आ रहा है कि आप वहाँ के बच्चों के साथ क्या करने की कोशिश कर रहे हैं। आप पहली बार अनुरोध (शामिल (पी => p.Children) में शामिल क्यों आप इसे फिर से अनुरोध करता है।?
pantonis

@pantonis, मुझे .include () का उपयोग करके पुरानी सूची को खींचना था ताकि यह डेटाबेस से संग्रह के रूप में लोड और संलग्न हो जाए। यह आलसी लोडिंग को कैसे लागू किया जाता है। इसके बिना, जब मैं Unitstate.modified का उपयोग करता हूं तो सूची में कोई भी परिवर्तन ट्रैक नहीं किया जाएगा। पुनरावृत्ति करने के लिए, मैं जो कर रहा हूं वह वर्तमान बाल संग्रह को एक अलग बाल संग्रह में सेट कर रहा है। जैसे कि एक प्रबंधक को नए कर्मचारियों का एक समूह मिला या कुछ खो दिया। मैं उन नए कर्मचारियों को शामिल करने या बाहर करने के लिए एक क्वेरी का उपयोग करूंगा और बस पुरानी सूची को एक नई सूची से बदल दूंगा और फिर EF को डेटाबेस की ओर से आवश्यकतानुसार जोड़ या हटा दूंगा।
चार्ल्स मैकिन्टोश

9

यदि आप EntityFrameworkCore का उपयोग कर रहे हैं, तो आप अपने कंट्रोलर पोस्ट एक्शन में निम्नलिखित कर सकते हैं ( संग्रह सहित पुनरावर्ती नेविगेशन गुणों को संलग्न करता है ):

_context.Attach(modelPostedToController);

IEnumerable<EntityEntry> unchangedEntities = _context.ChangeTracker.Entries().Where(x => x.State == EntityState.Unchanged);

foreach(EntityEntry ee in unchangedEntities){
     ee.State = EntityState.Modified;
}

await _context.SaveChangesAsync();

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

आपको यह भी सुनिश्चित करने की आवश्यकता है कि आप इस ऑपरेशन के लिए एक नया / समर्पित इकाई ढांचा डेटाबेस संदर्भ का उपयोग कर रहे हैं।


5
public async Task<IHttpActionResult> PutParent(int id, Parent parent)
        {
            if (!ModelState.IsValid)
            {
                return BadRequest(ModelState);
            }

            if (id != parent.Id)
            {
                return BadRequest();
            }

            db.Entry(parent).State = EntityState.Modified;

            foreach (Child child in parent.Children)
            {
                db.Entry(child).State = child.Id == 0 ? EntityState.Added : EntityState.Modified;
            }

            try
            {
                await db.SaveChangesAsync();
            }
            catch (DbUpdateConcurrencyException)
            {
                if (!ParentExists(id))
                {
                    return NotFound();
                }
                else
                {
                    throw;
                }
            }

            return Ok(db.Parents.Find(id));
        }

इस तरह मैंने इस समस्या को हल किया। इस तरह, ईएफ को पता है कि कौन सा अद्यतन करना है।


एक जादू की तरह काम किया! धन्यवाद।
इंटककिलर

2

वहाँ कुछ परियोजनाएँ हैं जो क्लाइंट और सर्वर के बीच बातचीत को आसान बनाती हैं जहाँ तक यह एक पूरे ऑब्जेक्ट ग्राफ को बचाने की चिंता करता है।

यहाँ दो हैं जिन्हें आप देखना चाहते हैं:

ऊपर दिए गए दोनों प्रोजेक्ट डिस्कनेक्टेड एंटिटीज को पहचानते हैं जब यह सर्वर पर वापस आता है, परिवर्तनों का पता लगाता है और सहेजता है, और क्लाइंट प्रभावित डेटा पर वापस लौटता है।


1

अवधारणा का सिर्फ प्रमाणControler.UpdateModel सही ढंग से काम नहीं करेगा।

पूरी कक्षा यहाँ :

const string PK = "Id";
protected Models.Entities con;
protected System.Data.Entity.DbSet<T> model;

private void TestUpdate(object item)
{
    var props = item.GetType().GetProperties();
    foreach (var prop in props)
    {
        object value = prop.GetValue(item);
        if (prop.PropertyType.IsInterface && value != null)
        {
            foreach (var iItem in (System.Collections.IEnumerable)value)
            {
                TestUpdate(iItem);
            }
        }
    }

    int id = (int)item.GetType().GetProperty(PK).GetValue(item);
    if (id == 0)
    {
        con.Entry(item).State = System.Data.Entity.EntityState.Added;
    }
    else
    {
        con.Entry(item).State = System.Data.Entity.EntityState.Modified;
    }

}

0

@Charles McIntosh ने वास्तव में मुझे अपनी स्थिति के लिए जवाब दिया कि मॉडल में पारित किया गया था। मेरे लिए जो अंततः काम कर रहा था वह पहले मॉडल में पारित होने से बचा रहा था ... फिर बच्चों को जोड़ना जारी रखा जैसा कि मैं पहले से ही था:

public async Task<IHttpActionResult> GetUPSFreight(PartsExpressOrder order)
{
    db.Entry(order).State = EntityState.Modified;
    db.SaveChanges();
  ...
}

0

VB.NET डेवलपर्स के लिए, बच्चे के राज्य को चिह्नित करने के लिए इस सामान्य उप का उपयोग करें, उपयोग करने के लिए आसान है

टिप्पणियाँ:

  • PromatCon: इकाई वस्तु
  • amList: वह बाल सूची है जिसे आप जोड़ना या संशोधित करना चाहते हैं
  • rList: वह बाल सूची है जिसे आप निकालना चाहते हैं
updatechild(objCas.ECC_Decision, PromatCon.ECC_Decision.Where(Function(c) c.rid = objCas.rid And Not objCas.ECC_Decision.Select(Function(x) x.dcid).Contains(c.dcid)).toList)
Sub updatechild(Of Ety)(amList As ICollection(Of Ety), rList As ICollection(Of Ety))
        If amList IsNot Nothing Then
            For Each obj In amList
                Dim x = PromatCon.Entry(obj).GetDatabaseValues()
                If x Is Nothing Then
                    PromatCon.Entry(obj).State = EntityState.Added
                Else
                    PromatCon.Entry(obj).State = EntityState.Modified
                End If
            Next
        End If

        If rList IsNot Nothing Then
            For Each obj In rList.ToList
                PromatCon.Entry(obj).State = EntityState.Deleted
            Next
        End If
End Sub
PromatCon.SaveChanges()


0

यहाँ मेरा कोड है जो ठीक काम करता है।

public async Task<bool> UpdateDeviceShutdownAsync(Guid id, DateTime shutdownAtTime, int areaID, decimal mileage,
        decimal motohours, int driverID, List<int> commission,
        string shutdownPlaceDescr, int deviceShutdownTypeID, string deviceShutdownDesc,
        bool isTransportation, string violationConditions, DateTime shutdownStartTime,
        DateTime shutdownEndTime, string notes, List<Guid> faultIDs )
        {
            try
            {
                using (var db = new GJobEntities())
                {
                    var isExisting = await db.DeviceShutdowns.FirstOrDefaultAsync(x => x.ID == id);

                    if (isExisting != null)
                    {
                        isExisting.AreaID = areaID;
                        isExisting.DriverID = driverID;
                        isExisting.IsTransportation = isTransportation;
                        isExisting.Mileage = mileage;
                        isExisting.Motohours = motohours;
                        isExisting.Notes = notes;                    
                        isExisting.DeviceShutdownDesc = deviceShutdownDesc;
                        isExisting.DeviceShutdownTypeID = deviceShutdownTypeID;
                        isExisting.ShutdownAtTime = shutdownAtTime;
                        isExisting.ShutdownEndTime = shutdownEndTime;
                        isExisting.ShutdownStartTime = shutdownStartTime;
                        isExisting.ShutdownPlaceDescr = shutdownPlaceDescr;
                        isExisting.ViolationConditions = violationConditions;

                        // Delete children
                        foreach (var existingChild in isExisting.DeviceShutdownFaults.ToList())
                        {
                            db.DeviceShutdownFaults.Remove(existingChild);
                        }

                        if (faultIDs != null && faultIDs.Any())
                        {
                            foreach (var faultItem in faultIDs)
                            {
                                var newChild = new DeviceShutdownFault
                                {
                                    ID = Guid.NewGuid(),
                                    DDFaultID = faultItem,
                                    DeviceShutdownID = isExisting.ID,
                                };

                                isExisting.DeviceShutdownFaults.Add(newChild);
                            }
                        }

                        // Delete all children
                        foreach (var existingChild in isExisting.DeviceShutdownComissions.ToList())
                        {
                            db.DeviceShutdownComissions.Remove(existingChild);
                        }

                        // Add all new children
                        if (commission != null && commission.Any())
                        {
                            foreach (var cItem in commission)
                            {
                                var newChild = new DeviceShutdownComission
                                {
                                    ID = Guid.NewGuid(),
                                    PersonalID = cItem,
                                    DeviceShutdownID = isExisting.ID,
                                };

                                isExisting.DeviceShutdownComissions.Add(newChild);
                            }
                        }

                        await db.SaveChangesAsync();

                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                logger.Error(ex);
            }

            return false;
        }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.