Hindari ledakan kartesius tanpa membelah semua bagian

Saya memiliki entitas Post yang memiliki hubungan satu-ke-banyak dengan Author dan Comment. Saya ingin memuat semua Posts dan menggabungkannya dengan Author pertama dan semua Comments. Kode dengan Include akan terlihat seperti ini:

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

Ada masalah ledakan kartesius pada kueri ini. Jika Post memiliki n Comments, Author dan Comment akan diulang n kali dalam kumpulan hasil.

Solusi #1

Di EF Core 5.0, saya bisa menggunakan Pisahkan Kueri tetapi itu akan menghasilkan 3 kueri ketika saya ingin memuat Post dengan Author terlebih dahulu kemudian semua Comments.

Solusi #2

Pertama, muat Post dengan Author lalu ulangi postingan ke muat secara eksplisit komentar mereka tetapi itu akan menghasilkan n + 1 pertanyaan.

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

Solusi #3

Pertama, muat Post dengan Author lalu kumpulkan semua id postingan untuk menghasilkan satu kueri guna memuat komentar.

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?

Solusi ini hanya akan menghasilkan 2 kueri tanpa data duplikat, tetapi bagaimana saya bisa menghindari komentar ditambahkan lagi ke postingan? Apakah ada cara yang lebih baik daripada 3 solusi yang diusulkan?


person Greg    schedule 11.12.2020    source sumber
comment
EF tahu bagaimana menangani hasil yang ditetapkan. Anda selalu mendapatkan postingan unik. Sepertinya Anda mencoba memecahkan masalah yang tidak ada.   -  person Gert Arnold    schedule 11.12.2020
comment
Saya memiliki masalah serupa - gabungan silang dari Include pada akhirnya akan menyebabkan kumpulan hasil yang sangat besar dari kueri SQL seiring dengan bertambahnya jumlah catatan terkait. Saya akhirnya beralih ke ADO untuk jenis kueri ini - ini memungkinkan saya membagi kueri secara manual dan juga menjalankan semuanya secara bersamaan (karena EF tidak aman untuk thread). Peningkatan kinerja terjadi siang dan malam.   -  person crgolden    schedule 11.12.2020
comment
@GertArnold masalah saya di sini bukanlah kebenaran tetapi kinerja. Saya ingin satu permintaan untuk posting dan penulis dan permintaan kedua untuk komentar. Saya perhatikan bahwa contoh saya terlalu disederhanakan. Untuk hubungan satu-ke-satu, inti EF mengetahui bahwa ia tidak harus membuat kueri baru tetapi cukup menggunakan GABUNG. Saya mengedit contoh saya untuk mencerminkan masalah saya. Postingan sekarang memiliki hubungan satu-ke-banyak dengan Penulis dan saya ingin postingan dengan penulis pertama dan semua komentar mereka.   -  person Greg    schedule 11.12.2020
comment
@crgolden Saya ingin menghindari penulisan SQL apa pun karena saya menggunakan penyedia dalam memori untuk lingkungan pengembangan.   -  person Greg    schedule 11.12.2020
comment
Ya, saya mengerti sekarang, tetapi itu tidak terlihat dari deskripsi pertama Anda yang tampaknya berfokus pada replikasi data (yang tentu saja mempengaruhi kinerja, khususnya dengan string panjang yang berulang).   -  person Gert Arnold    schedule 11.12.2020
comment
Menyertakan satu item dari hubungan satu-ke-banyak sepertinya bukan kasus yang umum (saya lebih suka mengatakan ini sangat jarang terjadi), jadi mengoptimalkan kasus tersebut tampaknya tidak sepadan dengan usaha. Saya pikir pembagian standar menjadi 3 kueri seharusnya baik-baik saja.   -  person Ivan Stoev    schedule 11.12.2020
comment
@IvanStoev Benar, tetapi untuk budaya saya, tahukah Anda cara membuat solusi ketiga berhasil?   -  person Greg    schedule 11.12.2020


Jawaban (2)


Saya akan menambahkan opsi lain. Karena saya penulis Eager Loading di linq2db. Saya cukup yakin itu hanya akan menjalankan dua pertanyaan.

Jadi instal saja ekstensi ini linq2db.EntityFrameworkCore (versi 3.x untuk EF Core 3.1.x dan versi 5.x untuk EF Core 5.x)

Dan coba pertanyaan ini:

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

Pendekatan ini juga harus bekerja dengan ProjectTo AutoMapper dan proyeksi khusus sepenuhnya. Saya tahu bahwa proyeksi khusus tidak berfungsi dengan AsSplitQuery (sejak saya mencobanya)

person Svyatoslav Danyliv    schedule 11.12.2020
comment
Sepertinya semua jawaban Anda untuk masalah terkait EFC adalah menggunakan jembatan Linq2Db :-) Yang harus saya akui adalah hal yang benar. - person Ivan Stoev; 11.12.2020
comment
Mengapa tidak. Orang-orang pindah ke Dapper karena EF Core tidak mau mendukung sesuatu atau melakukan itu seperti seorang pemula. - person Svyatoslav Danyliv; 11.12.2020
comment
Itulah yang saya katakan. Tidak normal bagi saya untuk mengandalkan paket pihak ketiga untuk menyediakan fungsionalitas dasar, namun itulah kenyataannya dengan EF Core (sayangnya) bahkan setelah beberapa tahun pengembangan. Satu-satunya masalah adalah kebutuhan panggilan ToLinqToDb() yang eksplisit, akan lebih mudah bagi kami (dan bagi mereka juga) jika mereka menemukan cara mudah untuk mengganti penyedia, tapi... - person Ivan Stoev; 11.12.2020
comment
Lebih buruk lagi, kadang-kadang saya memantau masalah mereka. Ini adalah 20 detik yang buruk untuk menerjemahkan kueri LINQ dengan 5-6 penyertaan. Kabar baik di sini adalah mereka tidak membahayakan Server Database saat melakukan itu;) - person Svyatoslav Danyliv; 11.12.2020
comment
Memang :-( Mengenai salah satu masalah lain yang telah Anda jawab hari ini, alih-alih NeinLinq dan LinqKit Anda mungkin menemukan pendekatan DelegateDecompiler yang menarik, serta upaya saya dengan memasukkan praprosesor kueri di sini dan upaya saya yang gagal untuk meyakinkan tim EF di sini. Setidaknya masalahnya masih terbuka :-) - person Ivan Stoev; 11.12.2020
comment
@IvanStoev, bertarunglah dengan mereka ;) Saya benar-benar tidak mengerti mengapa hal itu dilarang. Ini sangat mudah dan menyelamatkan nyawa (berminggu-minggu). Biasanya pengguna ekstensi kami mengucapkan kata-kata yang tepat: ekstensi Anda adalah penyelamat dan saya bangga akan hal itu ;) - person Svyatoslav Danyliv; 11.12.2020
comment
@GertArnold, mengerti. Saya baru di SO, beberapa nuansa masih belum intuitif. - person Svyatoslav Danyliv; 11.12.2020
comment
Jika Anda berafiliasi dengan cara atau bentuk apa pun dengan plugin/situs web/blog/produk/proyek dll yang telah Anda tautkan, harap sebutkan afiliasi Anda dengan jelas dalam jawaban itu sendiri. Afiliasi yang dirahasiakan akan dianggap spam dan dihapus. Silakan baca Cara untuk tidak menjadi spammer dan Dapatkah saya mendukung produk saya di situs ini?. - person Sabito 錆兎; 11.12.2020
comment
Ada baiknya untuk membaca tautan-tautan itu... Harap juga edit semua jawaban yang termasuk dalam kriteria ini.. - person Sabito 錆兎; 11.12.2020
comment
@Svyatoslav Untuk masalah khusus ini, coba kueri asli OP (memiliki .Include(p => p.Authors.Take(1))) dengan pendekatan Anda. Saya melihat kueri database 3 dan mendapatkan pengecualian di akhir eksekusi kueri terakhir (diangkat at LinqToDB.Common.ConvertBuilder.ConvertDefault(Object value, Type conversionType)) - person Ivan Stoev; 11.12.2020
comment
@IvanStoev, untuk setiap koleksi akan ada satu query. Menurut pengecualian Anda, bisakah Anda membuat masalah di github? - person Svyatoslav Danyliv; 12.12.2020

Saya telah menemukan cara agar solusi #2 berfungsi hanya dengan dua pertanyaan di sini: 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