อัปเดตความสัมพันธ์เมื่อบันทึกการเปลี่ยนแปลงของออบเจ็กต์ EF4 POCO


107

Entity Framework 4, วัตถุ POCO และ ASP.Net MVC2 ฉันมีความสัมพันธ์แบบหลายต่อหลายคนสมมติว่าระหว่างบล็อกโพสต์และเอนทิตีแท็ก ซึ่งหมายความว่าใน T4 ของฉันสร้างคลาส POCO BlogPost ฉันมี:

public virtual ICollection<Tag> Tags {
    // getter and setter with the magic FixupCollection
}
private ICollection<Tag> _tags;

ฉันขอ BlogPost และแท็กที่เกี่ยวข้องจากอินสแตนซ์ของ ObjectContext และส่งไปยังเลเยอร์อื่น (ดูในแอปพลิเคชัน MVC) ต่อมาฉันจะกลับ BlogPost ที่อัปเดตพร้อมคุณสมบัติที่เปลี่ยนแปลงและความสัมพันธ์ที่เปลี่ยนไป ตัวอย่างเช่นมีแท็ก "A" "B" และ "C" และแท็กใหม่คือ "C" และ "D" ในตัวอย่างเฉพาะของฉันไม่มีแท็กใหม่และคุณสมบัติของแท็กไม่เคยเปลี่ยนแปลงดังนั้นสิ่งเดียวที่ควรบันทึกคือความสัมพันธ์ที่เปลี่ยนแปลง ตอนนี้ฉันต้องการบันทึกสิ่งนี้ใน ObjectContext อื่น (อัปเดต: ตอนนี้ฉันพยายามทำในอินสแตนซ์บริบทเดียวกันและก็ล้มเหลวด้วย)

ปัญหา: ฉันไม่สามารถบันทึกความสัมพันธ์ได้อย่างเหมาะสม ฉันลองทุกอย่างที่พบ:

  • Controller.UpdateModel และ Controller.TryUpdateModel ไม่ทำงาน
  • การรับ BlogPost เก่าจากบริบทแล้วการแก้ไขคอลเลกชันไม่ทำงาน (ด้วยวิธีการที่แตกต่างจากจุดถัดไป)
  • สิ่งนี้อาจใช้ได้ แต่ฉันหวังว่านี่เป็นเพียงวิธีแก้ปัญหาไม่ใช่วิธีแก้ปัญหา :(
  • พยายามแนบ / เพิ่ม / ChangeObjectState สำหรับ BlogPost และ / หรือแท็กในทุกชุดที่เป็นไปได้ ล้มเหลว
  • นี้ดูเหมือนว่าสิ่งที่ฉันต้องการ แต่มันไม่ทำงาน (ผมพยายามที่จะแก้ไขได้ แต่ไม่สามารถแก้ไขปัญหาของฉัน)
  • พยายาม ChangeState / เพิ่ม / แนบ / ... วัตถุความสัมพันธ์ของบริบท ล้มเหลว

"ไม่ทำงาน" หมายความว่าในกรณีส่วนใหญ่ฉันทำงานกับ "โซลูชัน" ที่กำหนดจนกว่าจะไม่มีข้อผิดพลาดและอย่างน้อยก็บันทึกคุณสมบัติของ BlogPost สิ่งที่เกิดขึ้นกับความสัมพันธ์จะแตกต่างกันไป: โดยปกติแท็กจะถูกเพิ่มอีกครั้งในตารางแท็กด้วย PK ใหม่และ BlogPost ที่บันทึกไว้จะอ้างอิงถึงสิ่งเหล่านั้นไม่ใช่ของดั้งเดิม แน่นอนว่าแท็กที่ส่งคืนมี PK และก่อนวิธีบันทึก / อัปเดตฉันจะตรวจสอบ PK และพวกเขาก็เท่ากับแท็กในฐานข้อมูลดังนั้น EF อาจคิดว่าเป็นวัตถุใหม่และ PK เหล่านั้นเป็น temp

ปัญหาที่ฉันทราบและอาจทำให้ไม่สามารถหาวิธีแก้ปัญหาอัตโนมัติแบบง่ายๆได้: เมื่อคอลเลกชันของวัตถุ POCO มีการเปลี่ยนแปลงสิ่งนั้นควรเกิดขึ้นจากคุณสมบัติคอลเลกชันเสมือนที่กล่าวถึงข้างต้นเพราะเคล็ดลับ FixupCollection จะอัปเดตการอ้างอิงย้อนกลับในอีกด้านหนึ่ง ของความสัมพันธ์แบบกลุ่มต่อกลุ่ม อย่างไรก็ตามเมื่อ View "ส่งคืน" อ็อบเจ็กต์ BlogPost ที่อัปเดตสิ่งนั้นจะไม่เกิดขึ้น ซึ่งหมายความว่าอาจจะไม่มีวิธีแก้ปัญหาง่ายๆ แต่นั่นอาจทำให้ฉันเสียใจมากและฉันก็เกลียดชัยชนะของ EF4-POCO-MVC :( นอกจากนี้ยังหมายความว่า EF ไม่สามารถทำสิ่งนี้ในสภาพแวดล้อม MVC ได้ ใช้ประเภทออบเจ็กต์ EF4 :( ฉันคิดว่าการติดตามการเปลี่ยนแปลงตามสแน็ปช็อตควรพบว่า BlogPost ที่เปลี่ยนแปลงมีความสัมพันธ์กับแท็กกับ PK ที่มีอยู่

Btw: ฉันคิดว่าปัญหาเดียวกันนี้เกิดขึ้นกับความสัมพันธ์แบบหนึ่งต่อหลายคน (Google และเพื่อนร่วมงานของฉันพูดอย่างนั้น) ฉันจะลองใช้ที่บ้าน แต่แม้ว่าจะได้ผล แต่ก็ไม่ได้ช่วยฉันในหกความสัมพันธ์แบบกลุ่มต่อกลุ่มในแอปของฉัน :(


กรุณาโพสต์รหัสของคุณ นี่เป็นสถานการณ์ทั่วไป
John Farrell

1
ฉันมีวิธีแก้ปัญหานี้โดยอัตโนมัติมันซ่อนอยู่ในคำตอบด้านล่างหลายคนคงพลาด แต่โปรดดูเพราะมันจะช่วยให้คุณประหยัดงานได้ดูโพสต์ที่นี่
brentmckendrick

@brentmckendrick ฉันคิดว่าแนวทางอื่นดีกว่า แทนที่จะส่งกราฟออบเจ็กต์ที่แก้ไขทั้งหมดผ่านเส้นลวดทำไมไม่ส่งเดลต้าแทนล่ะ คุณไม่จำเป็นต้องสร้างคลาส DTO ด้วยซ้ำในกรณีนั้น หากคุณมีความเห็นเกี่ยวกับเรื่องนี้อย่างใดโปรดขอหารือที่stackoverflow.com/questions/1344066/calculate-object-delta
HappyNomad

คำตอบ:


145

ลองทำตามวิธีนี้:

  • แนบ BlogPost กับบริบท หลังจากแนบวัตถุเข้ากับบริบทสถานะของวัตถุวัตถุที่เกี่ยวข้องทั้งหมดและความสัมพันธ์ทั้งหมดจะถูกตั้งค่าเป็นไม่เปลี่ยนแปลง
  • ใช้ context.ObjectStateManager.ChangeObjectState เพื่อตั้งค่า BlogPost ของคุณเป็น Modified
  • วนซ้ำผ่านการรวบรวมแท็ก
  • ใช้ context.ObjectStateManager.ChangeRelationshipState เพื่อตั้งค่าสถานะสำหรับความสัมพันธ์ระหว่างแท็กปัจจุบันและ BlogPost
  • บันทึกการเปลี่ยนแปลง

แก้ไข:

ฉันเดาว่าหนึ่งในความคิดเห็นของฉันทำให้คุณมีความหวังที่ผิด ๆ ว่า EF จะทำการผสานให้คุณ ฉันเล่นกับปัญหานี้มามากและข้อสรุปของฉันบอกว่า EF จะไม่ทำสิ่งนี้ให้คุณ ฉันคิดว่าคุณพบคำถามของฉันในMSDNเช่นกัน ในความเป็นจริงมีคำถามมากมายบนอินเทอร์เน็ต ปัญหาคือไม่ได้ระบุไว้อย่างชัดเจนว่าจะจัดการกับสถานการณ์นี้อย่างไร มาดูปัญหากัน:

พื้นหลังของปัญหา

EF จำเป็นต้องติดตามการเปลี่ยนแปลงในเอนทิตีเพื่อให้ผู้คงอยู่รู้ว่าต้องอัปเดตแทรกหรือลบบันทึกใด ปัญหาคือเป็นความรับผิดชอบของ ObjectContext ในการติดตามการเปลี่ยนแปลง ObjectContext สามารถติดตามการเปลี่ยนแปลงสำหรับเอนทิตีที่แนบมาเท่านั้น เอนทิตีที่สร้างนอก ObjectContext จะไม่ถูกติดตามเลย

คำอธิบายปัญหา

จากคำอธิบายข้างต้นเราสามารถระบุได้อย่างชัดเจนว่า EF เหมาะสมกว่าสำหรับสถานการณ์ที่เชื่อมต่อซึ่งเอนทิตีมักจะแนบกับบริบท - โดยทั่วไปสำหรับแอปพลิเคชัน WinForm เว็บแอ็พพลิเคชันต้องการสถานการณ์ที่ไม่เชื่อมต่อซึ่งบริบทถูกปิดหลังจากการประมวลผลคำขอและเนื้อหาเอนทิตีถูกส่งผ่านเป็นการตอบสนอง HTTP ไปยังไคลเอ็นต์ คำขอ HTTP ถัดไปจัดเตรียมเนื้อหาที่แก้ไขของเอนทิตีซึ่งต้องสร้างขึ้นใหม่แนบกับบริบทใหม่และคงอยู่ การพักผ่อนหย่อนใจมักเกิดขึ้นนอกขอบเขตบริบท (สถาปัตยกรรมชั้นที่มีการเพิกเฉยต่อเนื่อง)

สารละลาย

แล้วจะจัดการกับสถานการณ์ที่ขาดการเชื่อมต่อดังกล่าวได้อย่างไร? เมื่อใช้คลาส POCO เรามี 3 วิธีในการจัดการกับการติดตามการเปลี่ยนแปลง:

  • Snapshot - ต้องการบริบทเดียวกัน = ไม่มีประโยชน์สำหรับสถานการณ์ที่ถูกตัดการเชื่อมต่อ
  • พร็อกซีการติดตามแบบไดนามิก - ต้องการบริบทเดียวกัน = ไม่มีประโยชน์สำหรับสถานการณ์ที่ถูกตัดการเชื่อมต่อ
  • การซิงโครไนซ์ด้วยตนเอง

การซิงโครไนซ์ด้วยตนเองในเอนทิตีเดียวเป็นเรื่องง่าย คุณเพียงแค่ต้องแนบเอนทิตีและเรียก AddObject เพื่อแทรก DeleteObject เพื่อลบหรือตั้งค่าสถานะใน ObjectStateManager เป็น Modified สำหรับการอัปเดต ความเจ็บปวดที่แท้จริงเกิดขึ้นเมื่อคุณต้องจัดการกับกราฟวัตถุแทนที่จะเป็นเอนทิตีเดียว ความเจ็บปวดนี้จะเลวร้ายยิ่งขึ้นเมื่อคุณต้องจัดการกับสมาคมอิสระ (ผู้ที่ไม่ใช้ทรัพย์สิน Foreign Key) และอีกหลาย ๆ ความสัมพันธ์ ในกรณีนี้คุณต้องซิงโครไนซ์แต่ละเอนทิตีในกราฟออบเจ็กต์ด้วยตนเอง แต่ยังรวมถึงแต่ละความสัมพันธ์ในกราฟออบเจ็กต์ด้วย

การซิงโครไนซ์ด้วยตนเองถูกเสนอให้เป็นโซลูชันโดยเอกสาร MSDN: การแนบและการถอดอ็อบเจ็กต์กล่าวว่า:

อ็อบเจ็กต์ถูกแนบกับบริบทอ็อบเจ็กต์ในสถานะไม่เปลี่ยนแปลง หากคุณต้องการเปลี่ยนสถานะของวัตถุหรือความสัมพันธ์เนื่องจากคุณทราบว่าวัตถุของคุณถูกแก้ไขในสถานะแยกออกให้ใช้วิธีใดวิธีหนึ่งต่อไปนี้

เมธอดที่กล่าวถึง ได้แก่ ChangeObjectState และ ChangeRelationshipState ของ ObjectStateManager = การติดตามการเปลี่ยนแปลงด้วยตนเอง ข้อเสนอที่คล้ายกันอยู่ในบทความเอกสาร MSDN อื่น ๆ : การกำหนดและจัดการความสัมพันธ์พูดว่า:

หากคุณกำลังทำงานกับวัตถุที่ไม่ได้เชื่อมต่อคุณต้องจัดการการซิงโครไนซ์ด้วยตนเอง

นอกจากนี้ยังมีบล็อกโพสต์ที่เกี่ยวข้องกับ EF v1 ซึ่งวิพากษ์วิจารณ์พฤติกรรมนี้ของ EF

เหตุผลในการแก้ปัญหา

EF มีการดำเนินการและการตั้งค่าที่ "เป็นประโยชน์" มากมายเช่นRefresh , Load , ApplyCurrentValues , ApplyOriginalValues , MergeOptionเป็นต้น แต่จากการตรวจสอบของฉันคุณลักษณะทั้งหมดเหล่านี้ใช้งานได้กับเอนทิตีเดียวเท่านั้นและมีผลต่อคุณสมบัติของสเกลาร์เท่านั้น (= ไม่ใช่คุณสมบัติการนำทางและความสัมพันธ์) ฉันไม่อยากทดสอบวิธีนี้กับประเภทที่ซับซ้อนที่ซ้อนอยู่ในเอนทิตี

โซลูชันที่เสนออื่น ๆ

แทนที่จะใช้ฟังก์ชันการผสานรวมจริงทีม EF จัดเตรียมสิ่งที่เรียกว่าSelf Tracking Entities (STE) ซึ่งไม่สามารถแก้ปัญหาได้ ก่อนอื่น STE จะทำงานได้ก็ต่อเมื่อใช้อินสแตนซ์เดียวกันสำหรับการประมวลผลทั้งหมด ในเว็บแอปพลิเคชันจะไม่เป็นเช่นนั้นเว้นแต่คุณจะเก็บอินสแตนซ์ไว้ในสถานะการดูหรือเซสชัน เนื่องจากฉันไม่มีความสุขมากจากการใช้ EF และฉันจะตรวจสอบคุณสมบัติของ NHibernate สังเกตแรกบอกว่าอาจจะมี NHibernate เช่นฟังก์ชั่น

สรุป

ฉันจะจบสมมติฐานนี้ด้วยลิงก์เดียวไปยังคำถามอื่นที่เกี่ยวข้องในฟอรัม MSDN ตรวจสอบคำตอบของ Zeeshan Hirani เขาเป็นผู้เขียนEntity Framework 4.0 สูตร ถ้าเขาบอกว่าไม่รองรับการผสานกราฟออบเจ็กต์โดยอัตโนมัติฉันเชื่อเขา

แต่ก็ยังมีความเป็นไปได้ที่ฉันจะผิดทั้งหมดและมีฟังก์ชันการผสานอัตโนมัติบางอย่างอยู่ใน EF

แก้ไข 2:

อย่างที่คุณเห็นสิ่งนี้ได้ถูกเพิ่มลงในMS Connect แล้วตามคำแนะนำในปี 2007 MS ได้ปิดมันเป็นสิ่งที่ต้องทำในเวอร์ชันถัดไป แต่จริงๆแล้วไม่มีอะไรทำเพื่อปรับปรุงช่องว่างนี้ยกเว้น STE


7
นี่เป็นหนึ่งในคำตอบที่ดีที่สุดที่ฉันเคยอ่านเกี่ยวกับ SO คุณได้ระบุไว้อย่างชัดเจนว่าบทความ MSDN เอกสารและบล็อกโพสต์ในหัวข้อนี้ล้มเหลวในเรื่องใดบ้าง EF4 ไม่สนับสนุนการอัปเดตความสัมพันธ์จากเอนทิตี "แยก" โดยเนื้อแท้ มีเพียงเครื่องมือให้คุณใช้งานได้ด้วยตนเอง ขอบคุณ!
tyriker

1
หลังจากไม่กี่เดือนที่ผ่านมา NHibernate เกี่ยวข้องกับปัญหานี้อย่างไรเมื่อเทียบกับ EF4
CallMeLaNN

1
สิ่งนี้ได้รับการสนับสนุนเป็นอย่างดีใน NHibernate :-) ไม่จำเป็นต้องรวมด้วยตนเองในตัวอย่างของฉันมันเป็นกราฟวัตถุเชิงลึก 3 ระดับคำถามมีคำตอบแต่ละคำตอบมีความคิดเห็นและคำถามก็มีความคิดเห็นเช่นกัน NHibernate สามารถคงอยู่ / ผสานกราฟวัตถุของคุณได้ไม่ว่ามันจะซับซ้อนแค่ไหนก็ตามienablemuch.com/2011/01/nhibernate-saves-your-whole-object.htmlผู้ใช้ NHibernate ที่พึงพอใจรายอื่น: codinginstinct.com/2009/11/…
Michael Buen

2
หนึ่งในคำอธิบายที่ดีที่สุดที่ฉันเคยอ่าน !! ขอบคุณมาก
marvelTracker

2
ทีม EF วางแผนที่จะแก้ไขปัญหาหลัง EF6 นี้ คุณอาจต้องการลงคะแนนให้entityframework.codeplex.com/workitem/864
Eric J.

19

ฉันมีวิธีแก้ปัญหาที่ Ladislav อธิบายไว้ข้างต้น ฉันได้สร้างวิธีการขยายสำหรับ DbContext ซึ่งจะดำเนินการเพิ่ม / อัปเดต / ลบโดยอัตโนมัติตามความแตกต่างของกราฟที่ให้มาและกราฟต่อเนื่อง

ในปัจจุบันโดยใช้ Entity Framework คุณจะต้องดำเนินการอัปเดตผู้ติดต่อด้วยตนเองตรวจสอบว่าผู้ติดต่อแต่ละรายเป็นผู้ติดต่อใหม่หรือไม่และเพิ่มตรวจสอบว่าอัปเดตและแก้ไขตรวจสอบว่าลบออกหรือไม่จากนั้นลบออกจากฐานข้อมูล เมื่อคุณต้องทำสิ่งนี้สำหรับการรวมที่แตกต่างกันสองสามอย่างในระบบขนาดใหญ่คุณจะเริ่มรู้ว่าต้องมีวิธีที่ดีกว่าและทั่วไปกว่า

โปรดดูและดูว่าสามารถช่วยhttp://refactorthis.wordpress.com/2012/12/11/introducing-graphdiff-for-entity-framework-code-first-allowing-automated-updates-of-a- กราฟของเอนทิตีเดี่ยว /

คุณสามารถไปที่รหัสได้โดยตรงที่นี่https://github.com/refactorthis/GraphDiff


ฉันแน่ใจว่าคุณสามารถแก้คำถามนี้ได้อย่างง่ายดายฉันมีช่วงเวลาที่ไม่ดีกับมัน
Shimmy Weitzhandler

1
สวัสดี Shimmy ขอโทษในที่สุดก็มีเวลาดู ฉันจะตรวจสอบคืนนี้
brentmckendrick

ห้องสมุดนี้ดีมากและช่วยฉันประหยัดเวลาได้มาก! ขอบคุณ!
lordjeb

9

ฉันรู้ว่ามันสายไปแล้วสำหรับ OP แต่เนื่องจากนี่เป็นปัญหาที่พบบ่อยมากฉันจึงโพสต์สิ่งนี้ในกรณีที่ให้บริการแก่ผู้อื่น ฉันเคยเล่นกับปัญหานี้มาตลอดและฉันคิดว่าฉันมีวิธีแก้ปัญหาที่ค่อนข้างง่ายสิ่งที่ฉันทำคือ:

  1. บันทึกวัตถุหลัก (ตัวอย่างเช่นบล็อก) โดยตั้งค่าสถานะเป็น Modified
  2. ค้นหาฐานข้อมูลสำหรับวัตถุที่อัปเดตรวมถึงคอลเลกชันที่ฉันต้องอัปเดต
  3. สอบถามและแปลง. ToList () เอนทิตีที่ฉันต้องการให้คอลเลกชันของฉันรวม
  4. อัปเดตคอลเล็กชันของวัตถุหลักไปยังรายการที่ฉันได้รับจากขั้นตอนที่ 3
  5. บันทึกการเปลี่ยนแปลง();

ในตัวอย่างต่อไปนี้ "dataobj" และ "_categories" คือพารามิเตอร์ที่คอนโทรลเลอร์ของฉันได้รับ "dataobj" เป็นอ็อบเจ็กต์หลักของฉันและ "_categories" คือ IEnumerable ที่มี ID ของหมวดหมู่ที่ผู้ใช้เลือกในมุมมอง

    db.Entry(dataobj).State = EntityState.Modified;
    db.SaveChanges();
    dataobj = db.ServiceTypes.Include(x => x.Categories).Single(x => x.Id == dataobj.Id);
    var it = _categories != null ? db.Categories.Where(x => _categories.Contains(x.Id)).ToList() : null;
    dataobj.Categories = it;
    db.SaveChanges();

มันยังใช้ได้กับความสัมพันธ์หลาย ๆ


7

ทีม Entity Framework ทราบดีว่านี่เป็นปัญหาด้านความสามารถในการใช้งานและวางแผนที่จะแก้ไขปัญหาหลัง EF6

จากทีม Entity Framework:

นี่เป็นปัญหาด้านความสามารถในการใช้งานที่เราตระหนักดีและเป็นสิ่งที่เราคิดและวางแผนที่จะทำงานเพิ่มเติมเกี่ยวกับหลัง EF6 ฉันได้สร้างรายการงานนี้เพื่อติดตามปัญหา: http://entityframework.codeplex.com/workitem/864รายการงานยังมีลิงก์ไปยังรายการเสียงของผู้ใช้สำหรับสิ่งนี้ฉันขอแนะนำให้คุณลงคะแนนให้หากคุณมี ยังไม่เสร็จ

หากสิ่งนี้ส่งผลกระทบต่อคุณให้ลงคะแนนสำหรับคุณลักษณะนี้ที่

http://entityframework.codeplex.com/workitem/864


หลัง EF6? แล้วจะเป็นปีไหนในแง่ดี?
quetzalcoatl

@quetzalcoatl: อย่างน้อยมันก็อยู่ในเรดาร์ของพวกเขา :-) EF มาไกลตั้งแต่ EF 1 แต่ก็ยังมีหนทางที่จะไป
Eric J.

1

คำตอบทั้งหมดอธิบายปัญหาได้ดี แต่ไม่มีคำตอบใดที่ช่วยแก้ปัญหาให้ฉันได้อย่างแท้จริง

ฉันพบว่าถ้าฉันไม่ได้ใช้ความสัมพันธ์ในเอนทิตีแม่ แต่เพิ่งเพิ่มและลบเอนทิตีลูกทุกอย่างก็ใช้ได้ดี

ขออภัยสำหรับ VB แต่นั่นคือสิ่งที่โครงการที่ฉันกำลังทำงานเขียนอยู่

เอนทิตีแม่ "รายงาน" มีความสัมพันธ์แบบหนึ่งต่อหลายกับ "ReportRole" และมีคุณสมบัติ "ReportRoles" บทบาทใหม่จะถูกส่งผ่านโดยสตริงที่คั่นด้วยเครื่องหมายจุลภาคจากการเรียก Ajax

บรรทัดแรกจะลบเอนทิตีลูกทั้งหมดออกและถ้าฉันใช้ "รายงาน ReportRoles.Remove (f)" แทน "db รายงานRoles.Remove (f)" ฉันจะได้รับข้อผิดพลาด

report.ReportRoles.ToList.ForEach(Function(f) db.ReportRoles.Remove(f))
Dim newRoles = If(String.IsNullOrEmpty(model.RolesString), New String() {}, model.RolesString.Split(","))
newRoles.ToList.ForEach(Function(f) db.ReportRoles.Add(New ReportRole With {.ReportId = report.Id, .AspNetRoleId = f}))
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.