JSON muss nicht analysiert werden. Alles hier kann tatsächlich direkt mit LINQ oder den Aggregate Fluent-Schnittstellen erledigt werden.
Verwenden Sie nur einige Demonstrationsklassen, da die Frage nicht wirklich viel zu bieten hat.
Einrichtung
Grundsätzlich haben wir hier zwei Sammlungen, nämlich
Entitäten
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
und andere
{
"_id" : ObjectId("5b08cef10a8a7614c70a5712"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
"name" : "Sub-A"
}
{
"_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
"name" : "Sub-B"
}
Und ein paar Klassen, an die sie gebunden werden können, nur als sehr grundlegende Beispiele:
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;
}
Abfragen
Fließende Benutzeroberfläche
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();
Anfrage an Server gesendet:
[
{ "$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 } }
]
Wahrscheinlich am einfachsten zu verstehen, da die fließende Schnittstelle im Grunde die gleiche ist wie die allgemeine BSON-Struktur. Die $lookup
stage hat dieselben Argumente und das $arrayElemAt
wird mit First()
dargestellt . Für $sort
Sie können einfach ein BSON-Dokument oder einen anderen gültigen Ausdruck angeben.
Eine Alternative ist die neuere Ausdrucksform von $lookup
mit einer Sub-Pipeline-Anweisung für MongoDB 3.6 und höher.
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();
Anfrage an Server gesendet:
[
{ "$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 } }
]
Der Fluent "Builder" unterstützt die Syntax noch nicht direkt, noch unterstützen LINQ-Ausdrücke den $expr
-Operator, Sie können jedoch immer noch mit BsonDocument
konstruieren und BsonArray
oder andere gültige Ausdrücke. Hier "tippen" wir auch den $unwind
result, um einen $sort
anzuwenden Verwendung eines Ausdrucks statt eines BsonDocument
wie zuvor gezeigt.
Abgesehen von anderen Verwendungen besteht eine Hauptaufgabe einer "Sub-Pipeline" darin, die im Zielarray von $lookup
zurückgegebenen Dokumente zu reduzieren . Auch das $unwind
dient hier dem Zweck, tatsächlich in $lookup
"zusammengeführt" zu werden -Anweisung bei der Serverausführung, daher ist dies in der Regel effizienter, als nur das erste Element des resultierenden Arrays abzurufen.
Abfragebarer Gruppenbeitritt
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);
Anfrage an Server gesendet:
[
{ "$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 } }
]
Dies ist fast identisch, verwendet aber nur die andere Schnittstelle und erzeugt eine etwas andere BSON-Anweisung, und zwar wirklich nur wegen der vereinfachten Benennung in den funktionalen Anweisungen. Dies bringt die andere Möglichkeit hervor, einfach ein $unwind
zu verwenden wie von einem SelectMany()
erzeugt :
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);
Anfrage an Server gesendet:
[
{ "$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 } }
]
Normalerweise wird ein $unwind
platziert direkt nach $lookup
ist eigentlich ein "optimiertes Muster" für das Aggregationsframework. Der .NET-Treiber bringt dies jedoch in dieser Kombination durcheinander, indem er ein $project
erzwingt dazwischen, anstatt die implizite Benennung auf dem "as"
zu verwenden . Wenn nicht, ist dies tatsächlich besser als $arrayElemAt
wenn Sie wissen, dass Sie "ein" zugehöriges Ergebnis haben. Wenn Sie das $unwind
wollen "Koaleszenz", dann sind Sie besser dran, die fließende Schnittstelle zu verwenden, oder eine andere Form, wie später gezeigt wird.
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;
Anfrage an Server gesendet:
[
{ "$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 } }
]
Alles ziemlich vertraut und wirklich nur auf die funktionale Benennung zurückzuführen. Genau wie bei der Verwendung von $unwind
Möglichkeit:
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;
Anfrage an Server gesendet:
[
{ "$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 } }
]
Was tatsächlich das Formular "optimierte Koaleszenz" verwendet. Der Übersetzer besteht immer noch darauf, ein $project
hinzuzufügen da wir das dazwischenliegende select
benötigen um die Aussage gültig zu machen.
Zusammenfassung
Es gibt also eine ganze Reihe von Möglichkeiten, im Wesentlichen zu derselben Abfrageanweisung mit genau denselben Ergebnissen zu gelangen. Während Sie den JSON in BsonDocument
"parsen" könnten bilden und dies dem Fluent Aggregate()
zuführen Befehl, ist es im Allgemeinen besser, die natürlichen Builder oder die LINQ-Schnittstellen zu verwenden, da sie sich leicht auf dieselbe Anweisung abbilden lassen.
Die Optionen mit $unwind
werden weitgehend gezeigt, da selbst bei einer "singulären" Übereinstimmung diese "Koaleszenz"-Form tatsächlich weitaus optimaler ist als die Verwendung von $arrayElemAt
um das "erste" Array-Element zu nehmen. Dies wird sogar noch wichtiger bei Überlegungen zu Dingen wie dem BSON-Limit, bei dem $lookup
Zielarray könnte dazu führen, dass das übergeordnete Dokument ohne weitere Filterung 16 MB überschreitet. Hier gibt es einen weiteren Beitrag zu Aggregate $lookup. Die Gesamtgröße der Dokumente in der übereinstimmenden Pipeline überschreitet die maximale Dokumentgröße, in dem ich tatsächlich erläutere, wie Sie vermeiden können, dass diese Grenze erreicht wird, indem Sie solche Optionen oder andere Lookup()
verwenden Syntax, die derzeit nur für die Fluent-Schnittstelle verfügbar ist.