J'ai la requête MongoDb suivante qui fonctionne:
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
}
}
]
)
Cela récupère une liste d'objets joints à un objet correspondant d'une autre collection.
Est-ce que quelqu'un sait comment je peux l'utiliser en C # en utilisant LINQ ou en utilisant cette chaîne exacte?
J'ai essayé d'utiliser le code suivant, mais il ne semble pas trouver les types pour QueryDocument
et MongoCursor
- Je pense qu'ils ont été dépréciés?
BsonDocument document = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>("{ name : value }");
QueryDocument queryDoc = new QueryDocument(document);
MongoCursor toReturn = _connectionCollection.Find(queryDoc);
Il n'est pas nécessaire d'analyser le JSON. Tout ici peut en fait être fait directement avec LINQ ou les interfaces Aggregate Fluent.
Il suffit d'utiliser des cours de démonstration, car la question ne donne pas vraiment grand-chose.
Fondamentalement, nous avons deux collections ici, étant
entités
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
et autres
{
"_id" : ObjectId("5b08cef10a8a7614c70a5712"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
"name" : "Sub-A"
}
{
"_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
"name" : "Sub-B"
}
Et quelques classes auxquelles les lier, comme des exemples très basiques:
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;
}
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();
Demande envoyée au serveur:
[
{ "$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 } }
]
Probablement la plus facile à comprendre car l'interface fluide est fondamentalement la même que la structure BSON générale. L'étape $lookup
a tous les mêmes arguments et le $arrayElemAt
est représenté par First()
. Pour le $sort
vous pouvez simplement fournir un document BSON ou une autre expression valide.
Une alternative est la nouvelle forme expressive de $lookup
avec une instruction de sous-pipeline pour MongoDB 3.6 et supérieur.
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();
Demande envoyée au serveur:
[
{ "$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 } }
]
Le "Builder" Fluent ne prend pas encore directement en charge la syntaxe, pas plus que les expressions LINQ ne prennent en charge l'opérateur $expr
, mais vous pouvez toujours construire en utilisant BsonDocument
et BsonArray
ou d'autres expressions valides. Ici, nous "tapons" également le résultat $unwind
afin d'appliquer un $sort
en utilisant une expression plutôt qu'un BsonDocument
comme indiqué précédemment.
Mis à part d'autres utilisations, une tâche principale d'un "sous-pipeline" consiste à réduire les documents renvoyés dans le tableau cible de $lookup
. De même, le $unwind
sert ici à [être "fusionné" dans l'instruction $lookup
sur le serveur l'exécution, c'est donc généralement plus efficace que de simplement saisir le premier élément du tableau résultant.
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);
Demande envoyée au serveur:
[
{ "$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 } }
]
C'est presque identique mais en utilisant simplement l'interface différente et produit une instruction BSON légèrement différente, et vraiment uniquement en raison de la dénomination simplifiée dans les instructions fonctionnelles. Cela fait apparaître l'autre possibilité d'utiliser simplement un $unwind
tel que produit à partir d'une 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);
Demande envoyée au serveur:
[
{ "$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 } }
]
Normalement, placer un $unwind
directement après $lookup
est en fait un "modèle optimisé" pour le cadre d'agrégation. Cependant, le pilote .NET gâche cela dans cette combinaison en forçant un $project
Entre plutôt qu'en utilisant la dénomination implicite sur le "as"
. Sinon, c'est en fait mieux que le $arrayElemAt
quand vous savez que vous avez "un" résultat lié. Si vous voulez la $unwind
"coalescence", alors vous feriez mieux d'utiliser l'interface fluide, ou une forme différente comme démontré plus loin.
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;
Demande envoyée au serveur:
[
{ "$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 } }
]
Tout cela est assez familier et vraiment juste pour le nommage fonctionnel. Tout comme avec l'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;
Demande envoyée au serveur:
[
{ "$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 } }
]
Qui utilise en fait la forme "coalescence optimisée" . Le traducteur insiste toujours sur l'ajout d'un $project
car nous avons besoin de l'intermédiaire select
pour rendre la déclaration valide.
Il existe donc plusieurs façons d'arriver à ce qui est essentiellement la même instruction de requête avec exactement les mêmes résultats. Alors que vous "pourriez" analyser le JSON en BsonDocument
et le nourrir avec la commande fluent Aggregate()
, il est généralement préférable d'utiliser les constructeurs naturels ou les interfaces LINQ car ils mappent facilement sur le même déclaration.
Les options avec $unwind
sont largement affichées parce que même avec une correspondance "singulière", cette forme de "coalescence" est en réalité beaucoup plus optimale qu'en utilisant $arrayElemAt
pour prendre le "premier" élément du tableau. Cela devient même plus important avec des considérations telles que la limite BSON où le tableau cible $lookup
pourrait entraîner un dépassement de 16 Mo du document parent sans filtrage supplémentaire. Il y a un autre article ici sur La recherche globale de $ La taille totale des documents dans le pipeline correspondant dépasse la taille maximale du document où j'explique comment éviter que cette limite soit atteinte en utilisant de telles options ou d'autres Lookup()
syntaxe disponible pour l'interface fluide uniquement pour le moment.
voici comment le faire avec MongoDB.Entities . dans les cas où deux entités sont dans une relation un-à-plusieurs ou plusieurs-à-plusieurs, vous pouvez obtenir un accès inversé à la relation sans avoir à effectuer les jointures manuellement comme indiqué ci-dessous. [Avertissement: je suis l'auteur de la bibliothèque]
using System;
using System.Linq;
using MongoDB.Entities;
using MongoDB.Driver.Linq;
namespace StackOverflow
{
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();
}
}
}