Tidak perlu mengurai JSON. Semuanya di sini sebenarnya dapat dilakukan secara langsung dengan antarmuka LINQ atau Aggregate Fluent.
Hanya menggunakan beberapa kelas demonstrasi karena pertanyaannya tidak terlalu banyak untuk dilanjutkan.
Penyiapan
Pada dasarnya kami memiliki dua koleksi di sini, menjadi
entitas
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
dan lainnya
{
"_id" : ObjectId("5b08cef10a8a7614c70a5712"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
"name" : "Sub-A"
}
{
"_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
"name" : "Sub-B"
}
Dan beberapa kelas untuk mengikat mereka, seperti contoh yang sangat mendasar:
public class Entity
{
public ObjectId id;
public string name { get; set; }
}
public class Other
{
public ObjectId id;
public ObjectId entity { get; set; }
public string name { get; set; }
}
public class EntityWithOthers
{
public ObjectId id;
public string name { get; set; }
public IEnumerable<Other> others;
}
public class EntityWithOther
{
public ObjectId id;
public string name { get; set; }
public Other others;
}
Permintaan
Antarmuka Lancar
var listNames = new[] { "A", "B" };
var query = entities.Aggregate()
.Match(p => listNames.Contains(p.name))
.Lookup(
foreignCollection: others,
localField: e => e.id,
foreignField: f => f.entity,
@as: (EntityWithOthers eo) => eo.others
)
.Project(p => new { p.id, p.name, other = p.others.First() } )
.Sort(new BsonDocument("other.name",-1))
.ToList();
Permintaan dikirim ke server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "others"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$others", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Mungkin yang paling mudah dipahami karena antarmuka yang lancar pada dasarnya sama dengan struktur BSON umum. $lookup
stage memiliki semua argumen yang sama dan $arrayElemAt
direpresentasikan dengan First()
. Untuk $sort
Anda cukup menyediakan dokumen BSON atau ekspresi valid lainnya.
Alternatifnya adalah bentuk ekspresif yang lebih baru dari $lookup
dengan pernyataan sub-pipa untuk MongoDB 3.6 dan di atasnya.
BsonArray subpipeline = new BsonArray();
subpipeline.Add(
new BsonDocument("$match",new BsonDocument(
"$expr", new BsonDocument(
"$eq", new BsonArray { "$$entity", "$entity" }
)
))
);
var lookup = new BsonDocument("$lookup",
new BsonDocument("from", "others")
.Add("let", new BsonDocument("entity", "$_id"))
.Add("pipeline", subpipeline)
.Add("as","others")
);
var query = entities.Aggregate()
.Match(p => listNames.Contains(p.name))
.AppendStage<EntityWithOthers>(lookup)
.Unwind<EntityWithOthers, EntityWithOther>(p => p.others)
.SortByDescending(p => p.others.name)
.ToList();
Permintaan dikirim ke server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"let" : { "entity" : "$_id" },
"pipeline" : [
{ "$match" : { "$expr" : { "$eq" : [ "$$entity", "$entity" ] } } }
],
"as" : "others"
} },
{ "$unwind" : "$others" },
{ "$sort" : { "others.name" : -1 } }
]
"Builder" yang Lancar belum mendukung sintaks secara langsung, ekspresi LINQ juga tidak mendukung $expr
operator, namun Anda masih dapat membuat menggunakan BsonDocument
dan BsonArray
atau ekspresi valid lainnya. Di sini kita juga "ketik" $unwind
hasil untuk menerapkan $sort
menggunakan ekspresi daripada BsonDocument
seperti yang ditunjukkan sebelumnya.
Selain kegunaan lain, tugas utama "sub-pipeline" adalah mengurangi dokumen yang dikembalikan dalam larik target $lookup
. Juga $unwind
di sini berfungsi untuk benar-benar "digabungkan" ke dalam $lookup
pernyataan pada eksekusi server, jadi ini biasanya lebih efisien daripada hanya mengambil elemen pertama dari larik yang dihasilkan.
Gabung ke Grup yang Dapat Dikueri
var query = entities.AsQueryable()
.Where(p => listNames.Contains(p.name))
.GroupJoin(
others.AsQueryable(),
p => p.id,
o => o.entity,
(p, o) => new { p.id, p.name, other = o.First() }
)
.OrderByDescending(p => p.other.name);
Permintaan dikirim ke server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "o"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$o", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Ini hampir identik tetapi hanya menggunakan antarmuka yang berbeda dan menghasilkan pernyataan BSON yang sedikit berbeda, dan benar-benar hanya karena penamaan yang disederhanakan dalam pernyataan fungsional. Ini memunculkan kemungkinan lain hanya dengan menggunakan $unwind
seperti yang dihasilkan dari SelectMany()
:
var query = entities.AsQueryable()
.Where(p => listNames.Contains(p.name))
.GroupJoin(
others.AsQueryable(),
p => p.id,
o => o.entity,
(p, o) => new { p.id, p.name, other = o }
)
.SelectMany(p => p.other, (p, other) => new { p.id, p.name, other })
.OrderByDescending(p => p.other.name);
Permintaan dikirim ke server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "o"
}},
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : "$o",
"_id" : 0
} },
{ "$unwind" : "$other" },
{ "$project" : {
"id" : "$id",
"name" : "$name",
"other" : "$other",
"_id" : 0
}},
{ "$sort" : { "other.name" : -1 } }
]
Biasanya menempatkan $unwind
langsung mengikuti $lookup
sebenarnya adalah "pola yang dioptimalkan" untuk kerangka kerja agregasi. Namun driver .NET mengacaukannya dalam kombinasi ini dengan memaksa $project
di antara daripada menggunakan penamaan tersirat pada "as"
. Jika bukan karena itu, ini sebenarnya lebih baik daripada $arrayElemAt
ketika Anda tahu Anda memiliki "satu" hasil terkait. Jika Anda ingin $unwind
"coalescence", maka Anda lebih baik menggunakan antarmuka yang lancar, atau bentuk lain seperti yang ditunjukkan nanti.
Alami yang Luar Biasa
var query = from p in entities.AsQueryable()
where listNames.Contains(p.name)
join o in others.AsQueryable() on p.id equals o.entity into joined
select new { p.id, p.name, other = joined.First() }
into p
orderby p.other.name descending
select p;
Permintaan dikirim ke server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "joined"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$joined", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Semua cukup akrab dan benar-benar hanya sampai ke penamaan fungsional. Sama seperti menggunakan $unwind
pilihan:
var query = from p in entities.AsQueryable()
where listNames.Contains(p.name)
join o in others.AsQueryable() on p.id equals o.entity into joined
from sub_o in joined.DefaultIfEmpty()
select new { p.id, p.name, other = sub_o }
into p
orderby p.other.name descending
select p;
Permintaan dikirim ke server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "joined"
} },
{ "$unwind" : {
"path" : "$joined", "preserveNullAndEmptyArrays" : true
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : "$joined",
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Yang sebenarnya menggunakan bentuk "penggabungan yang dioptimalkan". Penerjemah masih bersikeras untuk menambahkan $project
karena kita memerlukan select
intermediate perantara untuk membuat pernyataan tersebut valid.
Ringkasan
Jadi ada beberapa cara untuk sampai pada apa yang pada dasarnya adalah pernyataan kueri yang sama dengan hasil yang persis sama. Sementara Anda "bisa" mengurai JSON ke BsonDocument
bentuk dan masukkan ini ke Aggregate()
. yang lancar perintah, umumnya lebih baik menggunakan pembangun alami atau antarmuka LINQ karena mereka dengan mudah memetakan ke pernyataan yang sama.
Opsi dengan $unwind
sebagian besar ditampilkan karena bahkan dengan kecocokan "tunggal" bentuk "koalesensi" itu sebenarnya jauh lebih optimal daripada menggunakan $arrayElemAt
untuk mengambil elemen larik "pertama". Ini bahkan menjadi lebih penting dengan pertimbangan hal-hal seperti Batas BSON di mana $lookup
array target dapat menyebabkan dokumen induk melebihi 16MB tanpa pemfilteran lebih lanjut. Ada posting lain di sini di Agregat $lookup Ukuran total dokumen dalam pipa yang cocok melebihi ukuran dokumen maksimum di mana saya benar-benar membahas bagaimana menghindari batas itu dipukul dengan menggunakan opsi seperti itu atau Lookup()
lainnya sintaks hanya tersedia untuk antarmuka yang lancar saat ini.