ฉันสามารถอัปเดตวัตถุที่แนบโดยใช้วัตถุที่แยกออกมา แต่เท่ากันได้หรือไม่


10

ฉันดึงข้อมูลภาพยนตร์จาก API ภายนอก ในระยะแรกฉันจะขูดภาพยนตร์แต่ละเรื่องแล้วใส่ลงในฐานข้อมูลของฉันเอง ในระยะที่สองฉันจะอัปเดตฐานข้อมูลเป็นระยะโดยใช้ API "การเปลี่ยนแปลง" ของ API ซึ่งฉันสามารถสืบค้นเพื่อดูว่าภาพยนตร์เรื่องใดที่ข้อมูลของพวกเขาเปลี่ยนไป

เลเยอร์ ORM ของฉันคือ Entity-Framework คลาสภาพยนตร์มีลักษณะดังนี้:

class Movie
{
    public virtual ICollection<Language> SpokenLanguages { get; set; }
    public virtual ICollection<Genre> Genres { get; set; }
    public virtual ICollection<Keyword> Keywords { get; set; }
}

ปัญหาเกิดขึ้นเมื่อผมมีหนังที่ต้องมีการปรับปรุงฐานข้อมูลของฉันจะคิดว่าของวัตถุที่มีการติดตามและคนใหม่ที่ฉันได้รับจากสายการปรับปรุง API .Equals()เป็นวัตถุที่แตกต่างกันโดยไม่คำนึงถึง

สิ่งนี้ทำให้เกิดปัญหาเพราะเมื่อฉันพยายามอัปเดตฐานข้อมูลด้วยภาพยนตร์ที่อัปเดตแล้วมันจะแทรกแทนการอัปเดตภาพยนตร์ที่มีอยู่

ฉันมีปัญหานี้มาก่อนด้วยภาษาและวิธีการแก้ปัญหาของฉันคือการค้นหาวัตถุภาษาที่แนบมาแยกพวกเขาออกจากบริบทย้าย PK ของพวกเขาไปยังวัตถุที่อัปเดตและแนบไปกับบริบท เมื่อSaveChanges()ตอนนี้ถูกประหารชีวิตมันจะเข้ามาแทนที่

นี่เป็นวิธีการส่งกลิ่นเพราะถ้าฉันใช้วิธีนี้ต่อไปยังMovieวัตถุของฉันหมายความว่าฉันจะต้องแยกภาพยนตร์ภาษาประเภทและคำหลักออกค้นหาแต่ละรายการในฐานข้อมูลถ่ายโอน ID และใส่ วัตถุใหม่

มีวิธีการทำสิ่งนี้อย่างสง่างามมากขึ้น? เป็นการดีที่ฉันเพียงแค่ต้องการส่งผ่านภาพยนตร์ที่อัปเดตไปยังบริบทและมีการเลือกภาพยนตร์ที่ถูกต้องเพื่ออัปเดตตามEquals()วิธีการอัปเดตฟิลด์ทั้งหมดและสำหรับวัตถุที่ซับซ้อนแต่ละรายการ: ใช้ระเบียนที่มีอยู่อีกครั้งตามEquals()วิธีการของตนเองมันยังไม่มีอยู่

ฉันสามารถข้ามการแยก / การติดโดยการให้.Update()วิธีการกับวัตถุที่ซับซ้อนซึ่งฉันสามารถใช้ในการรวมกันของการดึงวัตถุที่แนบมาทั้งหมด แต่สิ่งนี้จะยังต้องให้ฉันดึงวัตถุที่มีอยู่ทุกเดียวเพื่อปรับปรุง


เหตุใดคุณจึงไม่สามารถอัปเดตเอนทิตีที่ติดตามจากข้อมูล API ของคุณและบันทึกการเปลี่ยนแปลงโดยไม่ต้องแทนที่ / แยก / จับคู่ / แนบเอนทิตี้
Si-N

@ Si-N: คุณสามารถขยายว่าจะไปได้อย่างไร?
Jeroen Vannevel

ตกลงตอนนี้คุณได้เพิ่มที่ย่อหน้าสุดท้ายมันสมเหตุสมผลมากขึ้นคุณกำลังพยายามหลีกเลี่ยงการดึงเอนทิตีก่อนที่จะอัพเดต? ไม่มีอะไรที่เป็น PK ของคุณในชั้นเรียนภาพยนตร์ของคุณ? คุณจับคู่ภาพยนตร์จาก API ภายนอกกับเอนทิตีของคุณอย่างไร คุณสามารถดึงเอนทิตีทั้งหมดที่คุณต้องการอัปเดตในการเรียก db หนึ่งครั้งได้หรือไม่นี่ไม่ใช่โซลูชันที่ง่ายกว่าซึ่งไม่น่าจะส่งผลให้เกิดการทำงานครั้งใหญ่ (เว้นแต่คุณกำลังพูดถึงภาพยนตร์จำนวนมากเพื่ออัปเดต)
Si-N

ระดับภาพยนตร์ของฉันมีเภสัชจลนศาสตร์idและภาพยนตร์จาก API tmdbidภายนอกจะถูกจับคู่กับคนในท้องถิ่นโดยใช้สนาม ฉันไม่สามารถเรียกคืนเอนทิตีทั้งหมดที่ต้องได้รับการอัปเดตในการโทรครั้งเดียวเนื่องจากเกี่ยวกับภาพยนตร์ประเภทภาษาคำหลัก ฯลฯ แต่ละรายการมี PK และอาจมีอยู่แล้วในฐานข้อมูล
Jeroen Vannevel

คำตอบ:


8

ฉันไม่พบสิ่งที่ฉันหวัง แต่ก็พบว่ามีการปรับปรุงลำดับ Select-detach-update-Attach ที่มีอยู่

วิธีการขยายAddOrUpdate(this DbSet)ช่วยให้คุณทำสิ่งที่ฉันต้องการ: แทรกถ้ามันไม่ได้มีและอัปเดตหากพบว่ามีค่าที่มีอยู่ ฉันไม่ได้ตระหนักถึงการใช้สิ่งนี้เร็วกว่านี้เพราะฉันเห็นเพียงว่าใช้กับseed()วิธีการรวมกับการย้ายข้อมูล หากมีเหตุผลใดที่ฉันไม่ควรใช้ให้ฉันรู้

สิ่งที่มีประโยชน์ที่ควรทราบ: มีโอเวอร์โหลดที่ให้คุณเลือกได้ว่าควรกำหนดความเท่าเทียมกันเป็นพิเศษ นี่ฉันจะได้ใช้ของฉันTMDbIdแต่ฉันแทนที่จะเลือกที่จะเพียงแค่ไม่สนใจ ID ของตัวเองโดยสิ้นเชิงและแทนที่จะใช้ PK บน TMDbId DatabaseGeneratedOption.Noneรวมกับ ฉันใช้วิธีการนี้ในการรวบรวมย่อยแต่ละรายการเช่นกันตามความเหมาะสม

ส่วนที่น่าสนใจของแหล่งที่มา :

internalSet.InternalContext.Owner.Entry(existing).CurrentValues.SetValues(entity);

ซึ่งเป็นวิธีการปรับปรุงข้อมูลจริงภายใต้ประทุน

สิ่งที่เหลือคือการโทรหาAddOrUpdateแต่ละวัตถุที่ฉันต้องการได้รับผลกระทบจากสิ่งนี้:

public void InsertOrUpdate(Movie movie)
{
    _context.Movies.AddOrUpdate(movie);
    _context.Languages.AddOrUpdate(movie.SpokenLanguages.ToArray());
    // Other objects/collections
    _context.SaveChanges();
}

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

การอ่านที่เกี่ยวข้อง: /programming/15336248/entity-framework-5-updating-a-record


ปรับปรุง:

ปรากฎว่าการทดสอบของฉันไม่เข้มงวดเพียงพอ หลังจากใช้เทคนิคนี้ฉันสังเกตว่าในขณะที่เพิ่มภาษาใหม่มันไม่ได้เชื่อมต่อกับภาพยนตร์ ในตารางหลายต่อหลายคน นี่เป็นปัญหาที่ทราบกัน แต่มีลำดับความสำคัญต่ำและยังไม่ได้รับการแก้ไขเท่าที่ฉันทราบ

ในตอนท้ายฉันตัดสินใจที่จะใช้วิธีการที่ฉันมีUpdate(T)วิธีในแต่ละประเภทและทำตามลำดับเหตุการณ์:

  • วนซ้ำคอลเล็กชันในวัตถุใหม่
  • สำหรับแต่ละรายการในแต่ละคอลเล็กชันให้ค้นหาในฐานข้อมูล
  • หากมีอยู่ให้ใช้Update()วิธีการอัปเดตด้วยค่าใหม่
  • หากไม่มีอยู่ให้เพิ่มลงใน DbSet ที่เหมาะสม
  • ส่งคืนวัตถุที่แนบมาและแทนที่คอลเลกชันในวัตถุรากด้วยคอลเลกชันของวัตถุที่แนบมา
  • ค้นหาและอัพเดตวัตถุรูท

มันเป็นงานที่ทำด้วยมือจำนวนมากและมันน่าเกลียดดังนั้นมันจะผ่านการปรับโครงสร้างอีกสองสามครั้ง แต่ตอนนี้การทดสอบของฉันระบุว่ามันควรจะทำงานได้ในสถานการณ์ที่เข้มงวดมากขึ้น


หลังจากทำความสะอาดเพิ่มเติมฉันใช้วิธีนี้:

private IEnumerable<T> InsertOrUpdate<T, TKey>(IEnumerable<T> entities, Func<T, TKey> idExpression) where T : class
{
    foreach (var entity in entities)
    {
        var existingEntity = _context.Set<T>().Find(idExpression(entity));
        if (existingEntity != null)
        {
            _context.Entry(existingEntity).CurrentValues.SetValues(entity);
            yield return existingEntity;
        }
        else
        {
            _context.Set<T>().Add(entity);
            yield return entity;
        }
    }
    _context.SaveChanges();
}

สิ่งนี้ทำให้ฉันสามารถเรียกมันได้เช่นนี้และใส่ / อัพเดทคอลเลคชั่นพื้นฐาน:

movie.Genres = new List<Genre>(InsertOrUpdate(movie.Genres, x => x.TmdbId));

โปรดสังเกตว่าฉันกำหนดค่าที่ดึงมาให้กับวัตถุรากเดิมได้อย่างไร: ตอนนี้มันเชื่อมต่อกับแต่ละวัตถุที่แนบมา การอัพเดตวัตถุรูท (ภาพยนตร์) ทำได้ในลักษณะเดียวกัน:

var localMovie = _context.Movies.SingleOrDefault(x => x.TmdbId == movie.TmdbId);
if (localMovie == null)
{
    _context.Movies.Add(movie);
} 
else
{
    _context.Entry(localMovie).CurrentValues.SetValues(movie);
}

คุณจัดการกับการลบในความสัมพันธ์ 1-M อย่างไร ตัวอย่างเช่น 1-Movie สามารถมีได้หลายภาษา หากภาษาใดภาษาหนึ่งถูกลบออกรหัสของคุณจะถูกลบหรือไม่ ดูเหมือนว่าโซลูชันของคุณจะแทรกและหรืออัปเดตเท่านั้น (แต่ไม่ลบออกหรือไม่)
joedotnot

0

เนื่องจากคุณกำลังติดต่อกับสาขาที่แตกต่างกันidและtmbidฉันขอแนะนำให้อัปเดต API เพื่อสร้างดัชนีเดี่ยวและแยกต่างหากของข้อมูลทั้งหมดเช่นประเภทภาษาคำหลัก ฯลฯ ... จากนั้นทำการโทรไปยังดัชนีและตรวจสอบข้อมูลแทนที่จะรวบรวม ข้อมูลทั้งหมดเกี่ยวกับวัตถุเฉพาะในคลาสภาพยนตร์ของคุณ


1
ฉันไม่ได้ติดตามความคิดที่นี่ คุณสามารถขยายหรือไม่ โปรดทราบว่า API ภายนอกนั้นไม่สามารถควบคุมได้ทั้งหมด
Jeroen Vannevel
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.