Není potřeba analyzovat JSON. Vše zde lze ve skutečnosti provést přímo pomocí rozhraní LINQ nebo Aggregate Fluent.
Stačí použít některé demonstrační třídy, protože otázka ve skutečnosti nedává moc k pokračování.
Nastavení
V podstatě zde máme dvě sbírky, jsou to
subjekty
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
a další
{
"_id" : ObjectId("5b08cef10a8a7614c70a5712"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
"name" : "Sub-A"
}
{
"_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
"name" : "Sub-B"
}
A pár tříd, se kterými je lze svázat, stejně jako velmi základní příklady:
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;
}
Dotazy
Plynulé rozhraní
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();
Požadavek odeslán na 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 } }
]
Pravděpodobně nejsnáze pochopitelné, protože plynulé rozhraní je v podstatě stejné jako obecná struktura BSON. $lookup
stage má všechny stejné argumenty a $arrayElemAt
je reprezentován pomocí First()
. Pro $sort
můžete jednoduše zadat dokument BSON nebo jiný platný výraz.
Alternativou je novější výrazová forma $lookup
s příkazem sub-pipeline pro MongoDB 3.6 a vyšší.
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();
Požadavek odeslán na 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 } }
]
Fluent "Builder" zatím nepodporuje syntaxi přímo, ani výrazy LINQ nepodporují $expr
operátor, ale stále můžete konstruovat pomocí BsonDocument
a BsonArray
nebo jiné platné výrazy. Zde také "napíšeme" $unwind
výsledek, aby bylo možné použít $sort
pomocí výrazu namísto BsonDocument
jak bylo uvedeno dříve.
Kromě jiných použití je primárním úkolem "sub-pipeline" snížit množství dokumentů vrácených v cílovém poli $lookup
. Také $unwind
zde slouží účelu skutečného „sloučení“ do $lookup
při spuštění serveru, takže je to obvykle efektivnější než pouhé zachycení prvního prvku výsledného pole.
Dotazovatelné GroupJoin
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);
Požadavek odeslán na 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 } }
]
To je téměř identické, ale pouze s použitím jiného rozhraní a vytváří mírně odlišný příkaz BSON, a to opravdu jen kvůli zjednodušenému pojmenování ve funkčních příkazech. To přináší další možnost jednoduše použít $unwind
vytvořené z 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);
Požadavek odeslán na 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 } }
]
Normálně umístění $unwind
přímo po $lookup
je ve skutečnosti "optimalizovaný vzor" pro agregační rámec. Ovladač .NET to však v této kombinaci zkazí tím, že vynutí $project
spíše než použití implikovaného pojmenování v "as"
. Pokud ne, je to ve skutečnosti lepší než $arrayElemAt
když víte, že máte „jeden“ související výsledek. Pokud chcete $unwind
„slučování“, pak je lepší použít plynulé rozhraní nebo jinou formu, jak bude ukázáno později.
Querable Natural
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;
Požadavek odeslán na 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 } }
]
Všechno docela známé a opravdu jen funkční pojmenování. Stejně jako při použití $unwind
možnost:
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;
Požadavek odeslán na 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 } }
]
Což ve skutečnosti používá formu „optimalizované koalescence“. Překladatel stále trvá na přidání $project
protože potřebujeme prostřední select
aby bylo prohlášení platné.
Shrnutí
Existuje tedy poměrně málo způsobů, jak v podstatě dospět k tomu, co je v podstatě stejný příkaz dotazu s přesně stejnými výsledky. Zatímco jste „mohli“ analyzovat JSON na BsonDocument
formulář a vložte jej do plynulého Aggregate()
příkazu, je obecně lepší použít přirozené stavitele nebo rozhraní LINQ, protože se snadno mapují na stejný příkaz.
Možnosti pomocí $unwind
jsou z velké části zobrazeny, protože i při „singulární“ shodě je tato „slučovací“ forma ve skutečnosti mnohem optimálnější než použití $arrayElemAt
vzít "první" prvek pole. To se stává ještě důležitějším s ohledem na věci, jako je BSON Limit, kde $lookup
cílové pole by mohlo způsobit, že nadřazený dokument překročí 16 MB bez dalšího filtrování. Je zde další příspěvek na Aggregate $lookup Celková velikost dokumentů v odpovídajícím kanálu překračuje maximální velikost dokumentu, kde ve skutečnosti diskutuji o tom, jak se vyhnout dosažení tohoto limitu použitím takových možností nebo jiných Lookup()
syntaxe je v současné době k dispozici pouze pro plynulé rozhraní.