DbSet.Attach (एंटिटी) बनाम DbContext.Entry (एंटिटी) .State = EntityState.Modified


115

जब मैं एक अलग परिदृश्य में होता हूं और क्लाइंट से एक dto प्राप्त करता हूं जिसे मैं इसे बचाने के लिए एक इकाई में मैप करता हूं:

context.Entry(entity).State = EntityState.Modified;
context.SaveChanges();

फिर किस लिए DbSet.Attach(entity)

या जब EntityState.Modified इकाई संलग्न करता है, तो मुझे .Aachach पद्धति का उपयोग क्यों करना चाहिए?


बेहतर कुछ संस्करण जानकारी जोड़ें, यह पहले पूछा गया है। यदि यह एक नया प्रश्न है तो मैं स्पष्ट नहीं हूँ।
हेनक होल्टरमैन

जवाबों:


278

जब आप करते हैं context.Entry(entity).State = EntityState.Modified;, तो आप न केवल इकाई को संलग्न DbContextकर रहे हैं, आप पूरी इकाई को भी गंदे के रूप में चिह्नित कर रहे हैं। इसका मतलब यह है कि जब आप करते हैं context.SaveChanges(), तो ईएफ एक अपडेट स्टेटमेंट उत्पन्न करेगा जो कि इकाई के सभी क्षेत्रों को अपडेट करेगा ।

यह हमेशा वांछित नहीं है।

दूसरी ओर, DbSet.Attach(entity)इकाई को गंदे अंकन के बिना संदर्भ में संलग्न करता है। यह करने के बराबर हैcontext.Entry(entity).State = EntityState.Unchanged;

इस तरह से संलग्न करते समय, जब तक आप इकाई पर एक संपत्ति को अपडेट करने के लिए आगे नहीं बढ़ते हैं, अगली बार जब आप कॉल करते हैं context.SaveChanges(), तो ईएफ इस इकाई के लिए डेटाबेस अपडेट उत्पन्न नहीं करेगा।

भले ही आप किसी इकाई को अपडेट करने की योजना बना रहे हों, यदि इकाई में बहुत सारे गुण (डीबी कॉलम) हैं, लेकिन आप केवल कुछ अपडेट करना चाहते हैं, तो आपको यह करने के लिए फायदेमंद हो सकता है DbSet.Attach(entity), और उसके बाद ही कुछ गुणों को अपडेट करें। जिसे अपडेट करने की आवश्यकता है। इस तरह से करने से EF से अधिक कुशल अपडेट स्टेटमेंट तैयार होगा। ईएफ केवल आपके द्वारा संशोधित गुणों को अद्यतन करेगा (इसके विपरीत context.Entry(entity).State = EntityState.Modified;सभी गुण / कॉलम अपडेट किए जाएंगे)

प्रासंगिक प्रलेखन: जोड़ें / संलग्न करें और एंटिटी स्टेट्स

कोड उदाहरण

मान लें कि आपके पास निम्नलिखित इकाई है:

public class Person
{
    public int Id { get; set; } // primary key
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

यदि आपका कोड इस तरह दिखता है:

context.Entry(personEntity).State = EntityState.Modified;
context.SaveChanges();

उत्पन्न SQL कुछ इस तरह दिखाई देगा:

UPDATE person
SET FirstName = 'whatever first name is',
    LastName = 'whatever last name is'
WHERE Id = 123; -- whatever Id is.

ध्यान दें कि उपरोक्त अपडेट स्टेटमेंट सभी कॉलमों को कैसे अपडेट करेगा, भले ही आपने वास्तव में मान बदले हैं या नहीं।

इसके विपरीत, यदि आपका कोड इस तरह "सामान्य" संलग्न का उपयोग करता है:

context.People.Attach(personEntity); // State = Unchanged
personEntity.FirstName = "John"; // State = Modified, and only the FirstName property is dirty.
context.SaveChanges();

तब उत्पन्न अपडेट स्टेटमेंट अलग है:

UPDATE person
SET FirstName = 'John'
WHERE Id = 123; -- whatever Id is.

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

अब, आपके लिए कौन सा विकल्प बेहतर है यह पूरी तरह से इस बात पर निर्भर करता है कि आप क्या करने की कोशिश कर रहे हैं।


1
EF इस तरह से WHERE क्लॉज जेनरेट नहीं करता है। यदि आपने नई (नई इकाई) () के साथ बनाई गई इकाई को संलग्न किया है और इसे संशोधित करने के लिए सेट किया है, तो आपको आशावादी लॉक के कारण सभी मूल फ़ील्ड सेट करने होंगे। यदि UPDATE क्वेरी में उत्पन्न क्लॉज़ में आम तौर पर सभी मूल फ़ील्ड शामिल हैं (केवल Id नहीं) तो यदि आप ऐसा नहीं करते हैं तो EF एक संगामिति अपवाद को फेंक देगा।
बुबी

3
@ बड्डी: आपकी प्रतिक्रिया के लिए धन्यवाद। मैंने सुनिश्चित होने के लिए फिर से परीक्षण किया, और एक मूल इकाई के लिए, यह जैसा मैंने वर्णित किया, वैसा ही व्यवहार करता है, जिसमें WHEREखंड केवल प्राथमिक कुंजी होता है, और किसी भी संगामिति जांच के बिना। समसामयिक जाँच करने के लिए, मुझे एक संगोष्ठी टोकन या रोवरवर्जन के रूप में एक कॉलम को स्पष्ट रूप से कॉन्फ़िगर करने की आवश्यकता है। उस स्थिति में, WHEREक्लॉज़ में केवल प्राथमिक कुंजी होगी और सभी फ़ील्ड के लिए संगणना टोकन कॉलम होगा। यदि आपके परीक्षण अन्यथा प्रदर्शित होते हैं, तो मुझे इसके बारे में सुनना अच्छा लगेगा।
13

मैं डायनेमिक रूप से डायन प्रॉपर्टी को कैसे संशोधित कर सकता हूं?
नवदी_पद 11

2
@ Navid_pdp11 DbContext.Entry(person).CurrentValuesऔर DbContext.Entry(person).OriginalValues
शिम्मी वेइटहैंडलर

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

3

जब आप DbSet.Updateविधि का उपयोग करते हैं , तो Entity Framework आपकी इकाई के सभी गुणों को चिह्नित करता है EntityState.Modified, इसलिए उन्हें ट्रैक करता है। यदि आप केवल अपने कुछ गुणों को बदलना चाहते हैं, तो उनमें से सभी का उपयोग न करें DbSet.Attach। यह विधि आपके सभी गुणों को बनाती है EntityState.Unchanged, इसलिए आपको अपने गुण बनाने चाहिए जिन्हें आप अपडेट करना चाहते हैं EntityState.Modified। इस प्रकार जब ऐप हिट होता है DbContext.SaveChanges, तो यह केवल संशोधित गुणों को संचालित करेगा।


0

बस इसके अलावा (चिह्नित उत्तर में) और (ईएफ कोर में) के बीच एक महत्वपूर्ण अंतर है :context.Entry(entity).State = EntityState.Unchangedcontext.Attach(entity)

मैंने इसे स्वयं समझने के लिए कुछ परीक्षण किए (इसलिए इसमें कुछ सामान्य संदर्भ परीक्षण भी शामिल हैं), इसलिए यह मेरा परीक्षण-परिदृश्य है:

  • मैंने EF Core 3.1.3 का उपयोग किया
  • मैंनें इस्तेमाल किया QueryTrackingBehavior.NoTracking
  • मैंने मैपिंग के लिए केवल विशेषताओं का उपयोग किया है (नीचे देखें)
  • मैंने ऑर्डर प्राप्त करने और ऑर्डर को अपडेट करने के लिए विभिन्न संदर्भों का उपयोग किया
  • मैंने हर टेस्ट के लिए पूरी डीबी मिटा दी

ये मॉडल हैं:

public class Order
{
    public int Id { get; set; }
    public string Comment { get; set; }
    public string ShippingAddress { get; set; }
    public DateTime? OrderDate { get; set; }
    public List<OrderPos> OrderPositions { get; set; }
    [ForeignKey("OrderedByUserId")]
    public User OrderedByUser { get; set; }
    public int? OrderedByUserId { get; set; }
}

public class OrderPos
{
    public int Id { get; set; }
    public string ArticleNo { get; set; }
    public int Quantity { get; set; }
    [ForeignKey("OrderId")]
    public Order Order { get; set; }
    public int? OrderId { get; set; }
}

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

यह डेटाबेस में (मूल) परीक्षण डेटा है: यहां छवि विवरण दर्ज करें

आदेश प्राप्त करने के लिए:

order = db.Orders.Include(o => o.OrderPositions).Include(o => o.OrderedByUser).FirstOrDefault();

अब परीक्षण:

EntityState के साथ सरल अद्यतन :

db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1

अटैच के साथ सरल अद्यतन :

db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1

EntityState के साथ चाइल्ड- आईडीएस बदलने के साथ अपडेट करें :

db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUser.Id = 3; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [ShippingAddress] = 'Germany' WHERE [Id] = 1

अटैच के साथ बदलते चाइल्ड-आईड्स के साथ अपडेट :

db.Attach(order);
order.ShippingAddress = "Germany"; // would be UPDATED
order.OrderedByUser.Id = 3; // will throw EXCEPTION
order.OrderedByUser.FirstName = "William (CHANGED)"; // would be UPDATED
order.OrderPositions[0].Id = 3; // will throw EXCEPTION
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // would be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // would be INSERTED
db.SaveChanges();
// Throws Exception: The property 'Id' on entity type 'User' is part of a key and so cannot be modified or marked as modified. To change the principal of an existing entity with an identifying foreign key first delete the dependent and invoke 'SaveChanges' then associate the dependent with the new principal.)

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

नए के रूप में बदलते चाइल्ड-आईडीएस के साथ अपडेट करें (एंटिटीस्टेट और अटैच के बीच कोई अंतर नहीं):

db.Attach(order); // or db.Entry(order).State = EntityState.Unchanged;
order.OrderedByUser = new User();
order.OrderedByUser.Id = 3; // // Reference will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on User 3)
db.SaveChanges();
// Will generate SQL in 2 Calls:
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 3

नोट: नई (ऊपर) के बिना EntityState के साथ अद्यतन करने के लिए अंतर देखें। नए उपयोगकर्ता उदाहरण के कारण, इस बार नाम अपडेट किया जाएगा।

EntityState के साथ संदर्भ-आईडी बदलने के साथ अद्यतन करें :

db.Entry(order).State = EntityState.Unchanged;
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.Id = 2; // will be IGNORED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be IGNORED
order.OrderPositions[0].Id = 3; // will be IGNORED
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be IGNORED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 2 Calls:
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1

अटैच के साथ संदर्भ-आईडी बदलने के साथ अपडेट करें :

db.Attach(order);
order.ShippingAddress = "Germany"; // will be UPDATED
order.OrderedByUserId = 3; // will be UPDATED
order.OrderedByUser.FirstName = "William (CHANGED)"; // will be UPDATED (on FIRST User!)
order.OrderPositions[0].ArticleNo = "K-1234 (CHANGED)"; // will be UPDATED
order.OrderPositions.Add(new OrderPos { ArticleNo = "T-5555 (NEW)", Quantity = 5 }); // will be INSERTED
db.SaveChanges();
// Will generate SQL in 1 Call:
// UPDATE [OrderPositions] SET [ArticleNo] = 'K-1234' WHERE [Id] = 1
// INSERT INTO [OrderPositions] ([ArticleNo], [OrderId], [Quantity]) VALUES ('T-5555 (NEW)', 1, 5)
// UPDATE [Orders] SET [OrderedByUserId] = 3, [ShippingAddress] = 'Germany' WHERE [Id] = 1
// UPDATE [Users] SET [FirstName] = 'William (CHANGED)' WHERE [Id] = 1

नोट: संदर्भ उपयोगकर्ता 3 करने के लिए बदल दिया जाएगा, लेकिन यह भी उपयोगकर्ता 1 अद्यतन किया जाएगा, मुझे लगता है कि इस वजह से order.OrderedByUser.Idअपरिवर्तित है (यह अभी भी 1 है)।

निष्कर्ष EntityState के साथ आपके पास अधिक नियंत्रण है, लेकिन आपको अपने आप से उप-संपत्तियों (दूसरे-स्तर) को अपडेट करना होगा। अटैच के साथ आप सब कुछ अपडेट कर सकते हैं (मुझे लगता है कि सभी स्तरों के गुणों के साथ), लेकिन आपको संदर्भों पर नजर रखनी होगी। उदाहरण के लिए: यदि उपयोगकर्ता (आर्डरबायर्स) एक ड्रॉपडाउन होगा, तो ड्रॉपडाउन के माध्यम से मूल्य बदलना पूरे उपयोगकर्ता-ऑब्जेक्ट को अधिलेखित कर सकता है। इस मामले में मूल ड्रॉपडाउन-वैल्यू को संदर्भ के बजाय अधिलेखित कर दिया जाएगा।

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

आशा है कि यह मदद करता है, मुझे पता है कि यह बहुत पाठ है: डी

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