หลีกเลี่ยงการระเบิดคาร์ทีเซียนโดยไม่แยกรวมทั้งหมด

ฉันมีเอนทิตี Post ที่มีความสัมพันธ์แบบหนึ่งต่อกลุ่มกับ Author และ Comment ฉันต้องการโหลด Posts ทั้งหมดและรวมเข้ากับ Author แรกและ Comments ทั้งหมด รหัสที่มี Include จะมีลักษณะดังนี้:

Post[] posts = ctx.Posts.Include(p => p.Authors.Take(1)).Include(p => p.Comments).ToArray();

มีปัญหาการระเบิดแบบคาร์ทีเซียนกับแบบสอบถามนี้ ถ้า Post เป็นเจ้าของ n Comments, Author และ Comment จะถูกทำซ้ำ n ครั้งในชุดผลลัพธ์

โซลูชัน #1

ใน EF Core 5.0 ฉันสามารถใช้ แยกแบบสอบถาม แต่จากนั้นจะสร้าง 3 แบบสอบถามเมื่อฉันต้องการโหลด Post ด้วย Author ก่อน จากนั้นจึงทั้งหมด Comments

โซลูชัน # 2

ขั้นแรก โหลด Post ด้วย Author จากนั้นวนซ้ำในโพสต์ไปที่ โหลดความคิดเห็นของพวกเขาอย่างชัดเจน แต่นั่นจะสร้างข้อความค้นหา n + 1 รายการ

Post[] posts = ctx.Posts.Include(p => p.Authors.Take(1)).ToArray();
foreach (Post post in posts)
  ctx.Entry(post).Collection(p => p.Comments).Load();

โซลูชัน # 3

ขั้นแรก โหลด Post ด้วย Author จากนั้นรวบรวมรหัสโพสต์ทั้งหมดเพื่อสร้างแบบสอบถามเดียวเพื่อโหลดความคิดเห็น

Dictionary<int, Post> postsById = ctx.Posts.Include(p => p.Authors.Take(1)).ToDictionnary(p => p.Id);
Comment[] comments = ctx.Comments.Where(c => postsById.ContainsKey(c.PostId)).ToArray();
foreach (Comment comment in comments)
  postsById[comment.PostId].Comments.Add(comment); // How to avoid re-adding comment?

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


person Greg    schedule 11.12.2020    source แหล่งที่มา
comment
EF รู้วิธีจัดการกับชุดผลลัพธ์ คุณจะได้รับโพสต์ที่ไม่ซ้ำใครเสมอ ดูเหมือนว่าคุณกำลังพยายามแก้ไขปัญหาที่ไม่มีอยู่   -  person Gert Arnold    schedule 11.12.2020
comment
ฉันมีปัญหาที่คล้ายกัน - การรวมข้ามจาก Include จะทำให้เกิดชุดผลลัพธ์จำนวนมหาศาลจากการสืบค้น SQL ในที่สุดเมื่อจำนวนบันทึกที่เกี่ยวข้องเพิ่มขึ้น ในที่สุดฉันก็เปลี่ยนมาใช้ ADO สำหรับการสืบค้นประเภทนี้ - ช่วยให้ฉันแยกการสืบค้นด้วยตนเองและเรียกใช้การสืบค้นทั้งหมดพร้อมกัน (เนื่องจาก EF ไม่ปลอดภัยสำหรับเธรด) ประสิทธิภาพที่เพิ่มขึ้นทั้งกลางวันและกลางคืน   -  person crgolden    schedule 11.12.2020
comment
@GertArnold ปัญหาของฉันที่นี่ไม่ใช่ความถูกต้อง แต่เป็นประสิทธิภาพ ฉันต้องการหนึ่งคำถามสำหรับทั้งโพสต์และผู้แต่ง และคำถามที่สองสำหรับความคิดเห็น ฉันสังเกตเห็นว่าตัวอย่างของฉันเรียบง่ายเกินไป สำหรับความสัมพันธ์แบบหนึ่งต่อหนึ่ง EF core รู้ว่าไม่จำเป็นต้องสร้างแบบสอบถามใหม่ แต่สามารถใช้ JOIN ได้ ฉันแก้ไขตัวอย่างเพื่อสะท้อนถึงปัญหาของฉัน ขณะนี้โพสต์มีความสัมพันธ์แบบหนึ่งต่อกลุ่มกับผู้เขียน และฉันต้องการโพสต์ที่มีผู้เขียนคนแรกและความคิดเห็นทั้งหมดของพวกเขา   -  person Greg    schedule 11.12.2020
comment
@crgolden ฉันต้องการหลีกเลี่ยงการเขียน SQL ใด ๆ เพราะฉันใช้ผู้ให้บริการในหน่วยความจำสำหรับสภาพแวดล้อมการพัฒนา   -  person Greg    schedule 11.12.2020
comment
ใช่ ฉันเข้าใจแล้วตอนนี้ แต่คำอธิบายแรกของคุณไม่ชัดเจนซึ่งดูเหมือนว่าจะมุ่งเน้นไปที่การจำลองข้อมูล (ซึ่งแน่นอนว่ากระทบต่อประสิทธิภาพ โดยเฉพาะกับสตริงยาวๆ ซ้ำๆ)   -  person Gert Arnold    schedule 11.12.2020
comment
การรวมรายการเดียวจากความสัมพันธ์แบบหนึ่งต่อกลุ่มดูเหมือนจะไม่ใช่กรณีทั่วไป (ฉันอยากจะบอกว่ามันควรจะค่อนข้างหายาก) ดังนั้นการปรับให้เหมาะสมสำหรับกรณีนั้นดูเหมือนจะไม่คุ้มค่ากับความพยายาม ฉันคิดว่าการแยกมาตรฐานเป็น 3 แบบสอบถามก็น่าจะใช้ได้   -  person Ivan Stoev    schedule 11.12.2020
comment
@IvanStoev ถูกต้อง แต่สำหรับวัฒนธรรมของฉัน คุณจะรู้วิธีทำให้โซลูชันที่สามทำงานได้อย่างไร   -  person Greg    schedule 11.12.2020


คำตอบ (2)


ฉันจะเพิ่มตัวเลือกอื่น เนื่องจากฉันเป็นผู้เขียน Eager Loading in linq2db ฉันค่อนข้างแน่ใจว่ามันจะเรียกใช้เพียงสองแบบสอบถามเท่านั้น

ดังนั้นเพียงแค่ติดตั้งส่วนขยายนี้ linq2db.EntityFrameworkCore (เวอร์ชัน 3.x สำหรับ EF Core 3.1.x และ เวอร์ชัน 5.x สำหรับ EF Core 5.x)

และลองแบบสอบถามนี้:

Post[] posts = ctx.Posts
   .Include(p => p.Author)
   .Include(p => p.Comments)
   .ToLinqToDB()
   .ToArray();

นอกจากนี้แนวทางนี้ควรใช้ได้กับ ProjectTo ของ AutoMapper และการฉายภาพแบบกำหนดเองทั้งหมด ฉันรู้ว่าการฉายภาพแบบกำหนดเองใช้ไม่ได้กับ AsSplitQuery (ตั้งแต่ฉันลองแล้ว)

person Svyatoslav Danyliv    schedule 11.12.2020
comment
ดูเหมือนว่าคำตอบทั้งหมดของคุณสำหรับปัญหาที่เกี่ยวข้องกับ EFC คือการใช้สะพาน Linq2Db :-) ซึ่งฉันควรยอมรับว่าเป็นสิ่งที่ถูกต้อง - person Ivan Stoev; 11.12.2020
comment
ทำไมจะไม่ล่ะ. ผู้คนย้ายไปที่ Dapper เพราะ EF Core จะไม่สนับสนุนบางสิ่งหรือทำสิ่งนั้นเหมือนมือใหม่ - person Svyatoslav Danyliv; 11.12.2020
comment
นั่นคือสิ่งที่ฉันกำลังพูด ไม่ใช่เรื่องปกติสำหรับฉันที่จะพึ่งพาแพ็คเกจของบุคคลที่สามเพื่อมอบฟังก์ชันพื้นฐาน แต่นั่นคือความเป็นจริงของ EF Core (น่าเสียดาย) แม้จะผ่านการพัฒนามาหลายปีก็ตาม ปัญหาเดียวคือจำเป็นต้องมีการโทร ToLinqToDb() อย่างชัดเจน มันอาจจะง่ายกว่ามากสำหรับเรา (และสำหรับพวกเขาด้วย) หากพวกเขาเปิดเผยวิธีง่ายๆ ในการเปลี่ยนผู้ให้บริการ แต่... - person Ivan Stoev; 11.12.2020
comment
ที่แย่กว่านั้นคือบางครั้งฉันก็ติดตามปัญหาของพวกเขา การแปลแบบสอบถาม LINQ ด้วย 5-6 รวมนั้นแย่มาก 20 วินาที ข่าวดีที่นี่ว่าพวกเขาไม่เป็นอันตรายต่อ Database Server เมื่อทำเช่นนั้น ;) - person Svyatoslav Danyliv; 11.12.2020
comment
แน่นอน :-( เกี่ยวกับปัญหาอื่น ๆ ที่คุณตอบในวันนี้ แทนที่จะเป็น NeinLinq และ LinqKit คุณอาจพบวิธี DelegateDecompiler ที่น่าสนใจ เช่นเดียวกับความพยายามของฉันในการเสียบตัวประมวลผลล่วงหน้าของแบบสอบถาม ที่นี่ และความพยายามที่ล้มเหลวของฉันในการโน้มน้าวทีม EF ที่นี่ อย่างน้อยปัญหายังคงเปิดอยู่ :-) - person Ivan Stoev; 11.12.2020
comment
@IvanStoev ต่อสู้กับพวกเขา;) ฉันไม่เข้าใจจริงๆว่าทำไมมันถึงถูกห้าม มันสนุก..และช่วยชีวิตได้ (สัปดาห์) โดยปกติแล้วผู้ใช้ส่วนขยายของเราจะพูดคำที่ถูกต้อง: ส่วนขยายของคุณคือเครื่องช่วยชีวิต และฉันภูมิใจในสิ่งนั้น ;) - person Svyatoslav Danyliv; 11.12.2020
comment
@GertArnold เข้าใจแล้ว ฉันใหม่ใน SO ความแตกต่างบางอย่างยังไม่เข้าใจง่าย - person Svyatoslav Danyliv; 11.12.2020
comment
หากคุณอยู่ในรูปแบบหรือรูปแบบใดที่เกี่ยวข้องกับปลั๊กอิน/เว็บไซต์/บล็อก/ผลิตภัณฑ์/โครงการ ฯลฯ ที่คุณได้เชื่อมโยงไว้ โปรดระบุความเกี่ยวข้องของคุณอย่างชัดเจนในคำตอบ ความร่วมมือที่ไม่เปิดเผยจะถือเป็นสแปมและถูกลบ โปรดอ่าน วิธีไม่เป็นนักส่งสแปม และ ฉันสามารถสนับสนุนผลิตภัณฑ์ของฉันบนเว็บไซต์นี้ได้หรือไม่ - person Sabito 錆兎; 11.12.2020
comment
เป็นการดีที่จะอ่านลิงก์เหล่านั้น... นอกจากนี้โปรดแก้ไขคำตอบทั้งหมดที่อยู่ภายใต้เกณฑ์นี้.. - person Sabito 錆兎; 11.12.2020
comment
@Svyatoslav สำหรับปัญหานี้โดยเฉพาะ ให้ลองใช้แบบสอบถามดั้งเดิมของ OP (มี .Include(p => p.Authors.Take(1))) ด้วยแนวทางของคุณ ฉันเห็นข้อความค้นหาฐานข้อมูล 3 และได้รับข้อยกเว้นเมื่อสิ้นสุดการดำเนินการค้นหาครั้งล่าสุด (เพิ่ม at LinqToDB.Common.ConvertBuilder.ConvertDefault(Object value, Type conversionType)) - person Ivan Stoev; 11.12.2020
comment
@IvanStoev สำหรับทุกคอลเลกชันจะมีหนึ่งแบบสอบถาม ตามข้อยกเว้นของคุณ คุณช่วยกรุณาสร้างปัญหาใน github ได้ไหม - person Svyatoslav Danyliv; 12.12.2020

ฉันพบวิธีสำหรับโซลูชัน #2 ที่จะทำงานกับเพียงสองแบบสอบถามที่นี่: https://github.com/dotnet/efcore/issues/7350

int postIds = new[] { 3, 4 };
Post[] posts = ctx.Posts
    .Include(p => p.Authors.Take(1))
    .Where(p => postIds.Contains(p.Id))
    .ToArray();

// This line automatically populates posts comments in the same DbContext.
ctx.Comments
    .Where(c => postIds.Contains(c.PostId))
    .Load();
person Greg    schedule 15.12.2020