c# - source - working with mongodb in.net part 1




$ Lookup mit C#aggregieren (2)

Ich habe die folgende MongoDb-Abfrage ausgeführt:

db.Entity.aggregate(
    [
        {
            "$match":{"Id": "12345"}
        },
        {
            "$lookup": {
                "from": "OtherCollection",
                "localField": "otherCollectionId",
                "foreignField": "Id",
                "as": "ent"
            }
        },
        { 
            "$project": { 
                "Name": 1,
                "Date": 1,
                "OtherObject": { "$arrayElemAt": [ "$ent", 0 ] } 
            }
        },
        { 
            "$sort": { 
                "OtherObject.Profile.Name": 1
            } 
        }
    ]
)

Dadurch wird eine Liste von Objekten abgerufen, die mit einem übereinstimmenden Objekt aus einer anderen Sammlung verbunden sind.

Weiß jemand, wie ich dies in C # mit LINQ oder mit genau dieser Zeichenfolge verwenden kann?

Ich habe versucht, den folgenden Code zu verwenden, aber er scheint die Typen für QueryDocument und MongoCursor nicht zu finden. Ich denke, sie sind veraltet.

BsonDocument document = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>("{ name : value }");
QueryDocument queryDoc = new QueryDocument(document);
MongoCursor toReturn = _connectionCollection.Find(queryDoc);

Es ist nicht erforderlich, den JSON-Code zu analysieren. Alles kann hier direkt mit LINQ oder den Aggregate Fluent-Schnittstellen durchgeführt werden.

Ich benutze nur einige Demonstrationsklassen, weil die Frage nicht wirklich viel zu sagen hat.

Konfiguration

Grundsätzlich haben wir hier zwei Kollektionen

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 man sie binden kann, nur als sehr einfache 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ßendes Interface

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 flüssige Oberfläche im Grunde der allgemeinen BSON-Struktur entspricht. Die $lookup Stufe hat dieselben Argumente und das $arrayElemAt wird mit First() . Für die $sort können Sie 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 und LINQ Expressions unterstützen den $expr Operator nicht. Sie können jedoch weiterhin mit BsonDocument und BsonArray oder anderen gültigen Ausdrücken konstruieren. Hier "tippen" wir auch das Ergebnis " $unwind ein, um eine $sort BsonDocument unter Verwendung eines Ausdrucks anstelle eines BsonDocument wie oben gezeigt.

Neben anderen Verwendungen besteht eine Hauptaufgabe einer "Sub-Pipeline" darin, die im Ziel-Array von $lookup Dokumente zu reduzieren. Auch das $unwind hier dient dem Zweck, bei der Serverausführung tatsächlich mit der $lookup Anweisung "zusammengeführt" zu werden, was in der Regel effizienter ist, als nur das erste Element des resultierenden Arrays zu erfassen.

Abfragbarer 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 jedoch nur die andere Schnittstelle und erzeugt eine etwas andere BSON-Anweisung, und zwar nur aufgrund der vereinfachten Benennung in den funktionalen Anweisungen. Dies eröffnet die andere Möglichkeit, einfach ein $unwind wie es von einem 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);

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 ist das Platzieren eines $unwind direkt nach $lookup ein "optimiertes Muster" für das Aggregationsframework. Der .NET-Treiber bringt dies jedoch in dieser Kombination durcheinander, indem er ein $project zwischendurch erzwingt, anstatt die implizite Benennung auf dem "as" . Wenn dies nicht der $arrayElemAt ist, ist dies tatsächlich besser als $arrayElemAt wenn Sie wissen, dass Sie "ein" zugehöriges Ergebnis haben. Wenn Sie das Zusammenwachsen von $unwind wünschen, ist es besser, die flüssige Benutzeroberfläche oder eine andere Form zu verwenden, 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. So wie bei Verwendung der Option $unwind :

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 } }
]

Welches tatsächlich die "optimierte Koaleszenz" -Form verwendet. Der Übersetzer besteht weiterhin darauf, ein $project hinzuzufügen $project da wir die Zwischenauswahl benötigen, um die Anweisung gültig zu machen.

Zusammenfassung

Es gibt also eine Reihe von Möglichkeiten, im Wesentlichen zu der gleichen Abfrageanweisung mit genau den gleichen Ergebnissen zu gelangen. Während Sie das JSON-zu- BsonDocument Formular "analysieren" und es dem fließenden Aggregate() -Befehl zuführen können, ist es im Allgemeinen besser, die natürlichen Builder oder die LINQ-Schnittstellen zu verwenden, da sie auf einfache Weise derselben Anweisung zugeordnet werden können.

Die Optionen mit $unwind werden größtenteils angezeigt, da selbst bei einer "singulären" Übereinstimmung die "Koaleszenz" -Form weitaus optimaler ist, $arrayElemAt mit $arrayElemAt das "erste" Array-Element genommen wird. Dies wird noch wichtiger bei Überlegungen wie dem BSON-Limit, bei dem das $lookup Zielarray dazu führen kann, dass das übergeordnete Dokument 16 MB ohne weitere Filterung überschreitet. Hier gibt es einen weiteren Beitrag zum Thema " Aggregate $ lookup". Die Gesamtgröße der Dokumente in der übereinstimmenden Pipeline überschreitet die maximale Dokumentgröße. In diesem Artikel wird besprochen, wie diese Beschränkung vermieden werden kann, indem solche Optionen oder andere Lookup() Syntax verwendet werden, die der fließenden Benutzeroberfläche derzeit zur Verfügung stehen .


So geht's mit MongoDB.Entities . In Fällen, in denen sich zwei Entitäten in einer Eins-zu-Viele- oder einer Viele-zu-Viele-Beziehung befinden, können Sie den umgekehrten Beziehungszugriff erhalten, ohne die Verknüpfungen manuell ausführen zu müssen (siehe unten). [Haftungsausschluss: Ich bin der Autor der Bibliothek]

using System;
using System.Linq;
using MongoDB.Entities;
using MongoDB.Driver.Linq;

namespace 
{
    public class Program
    {
        public class Author : Entity
        {
            public string Name { get; set; }
            public Many<Book> Books { get; set; }

            public Author() => this.InitOneToMany(() => Books);
        }

        public class Book : Entity
        {
            public string Title { get; set; }
        }

        static void Main(string[] args)
        {
            new DB("test");

            var book = new Book { Title = "The Power Of Now" };
            book.Save();

            var author = new Author { Name = "Eckhart Tolle" };
            author.Save();

            author.Books.Add(book);

            //build a query for finding all books that has Power in the title.
            var bookQuery = DB.Queryable<Book>()
                              .Where(b => b.Title.Contains("Power"));

            //find all the authors of books that has a title with Power in them
            var authors = author.Books
                                .ParentsQueryable<Author>(bookQuery); //also can pass in an ID or array of IDs

            //get the result
            var result = authors.ToArray();

            //output the aggregation pipeline
            Console.WriteLine(authors.ToString());


            Console.ReadKey();
        }
    }
}




mongodb-.net-driver