c# mongodb aggregation-framework mongodb-.net-driver

Búsqueda agregada de $ con C#



mongodb aggregation-framework (2)

No hay necesidad de analizar el JSON. Todo lo que aquí se puede hacer directamente con LINQ o las interfaces de Aggregate Fluent.

Solo uso algunas clases de demostración porque la pregunta realmente no da mucho para continuar.

Preparar

Básicamente tenemos dos colecciones aquí, siendo

entidades

{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" } { "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }

y otros

{ "_id" : ObjectId("5b08cef10a8a7614c70a5712"), "entity" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "Sub-A" } { "_id" : ObjectId("5b08cefd0a8a7614c70a5713"), "entity" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "Sub-B" }

Y un par de clases para unirlas, como ejemplos muy básicos:

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

Consultas

Interfaz fluida

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();

Solicitud enviada al servidor:

[ { "$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 } } ]

Probablemente el más fácil de entender ya que la interfaz fluida es básicamente la misma que la estructura general de BSON. La etapa $lookup tiene todos los mismos argumentos y $arrayElemAt se representa con First() . Para el $sort puede simplemente proporcionar un documento BSON u otra expresión válida.

Una alternativa es la forma expresiva más nueva de $lookup con una declaración de sub-pipeline para MongoDB 3.6 y superior.

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();

Solicitud enviada al servidor:

[ { "$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" aún no admite la sintaxis directamente, ni LINQ Expressions es compatible con el operador $expr , sin embargo, aún puede construir utilizando BsonDocument y BsonArray u otras expresiones válidas. Aquí también "tipeamos" el resultado de $sort BsonDocument para aplicar un $sort usando una expresión en lugar de un BsonDocument como se mostró anteriormente.

Aparte de otros usos, una tarea principal de una "sub-tubería" es reducir los documentos devueltos en la matriz de destino de $lookup . Además, $unwind aquí tiene el propósito de ser realmente "fusionado" en la declaración de $lookup en la ejecución del servidor, por lo que esto suele ser más eficiente que simplemente capturar el primer elemento de la matriz resultante.

Grupo Queryable

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);

Solicitud enviada al servidor:

[ { "$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 } } ]

Esto es casi idéntico, pero solo utiliza la interfaz diferente y produce una declaración BSON ligeramente diferente, y en realidad solo por el nombre simplificado en las declaraciones funcionales. Esto hace surgir la otra posibilidad de simplemente usar un $unwind como se produce desde un 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);

Solicitud enviada al servidor:

[ { "$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 } } ]

Normalmente, colocar $unwind directamente después de $lookup es en realidad un "patrón optimizado" para el marco de agregación. Sin embargo, el controlador .NET desordena esto en esta combinación forzando un $project en el medio en lugar de usar el nombre implícito en "as" . Si no es por eso, esto es realmente mejor que el $arrayElemAt cuando sabes que tienes un resultado relacionado "uno". Si desea la "coalescencia" de $unwind , entonces es mejor que utilice la interfaz fluida o una forma diferente como se muestra más adelante.

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;

Solicitud enviada al servidor:

[ { "$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 } } ]

Todo muy familiar y realmente solo para nombrar funcionalmente. Al igual que con el uso de la opción $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;

Solicitud enviada al servidor:

[ { "$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 } } ]

Que en realidad está utilizando la forma de "coalescencia optimizada" . El traductor todavía insiste en agregar un $project ya que necesitamos la select intermedia para que la declaración sea válida.

Resumen

Así que hay bastantes formas de llegar a lo que es básicamente la misma declaración de consulta con exactamente los mismos resultados. Mientras que "podría" analizar el BsonDocument JSON a BsonDocument y BsonDocument al BsonDocument Aggregate() , generalmente es mejor usar los constructores naturales o las interfaces LINQ, ya que se asignan fácilmente a la misma declaración.

Las opciones con $unwind se muestran en gran parte porque incluso con una coincidencia "singular" que la forma de "coalescencia" es en realidad mucho más óptima que usar $arrayElemAt para tomar el "primer" elemento de matriz. Esto incluso se vuelve más importante con consideraciones de cosas como el límite BSON donde la matriz de destino $lookup podría hacer que el documento principal supere los 16 MB sin más filtros. Hay otra publicación aquí en Aggregate $ lookup. El tamaño total de los documentos en el canal de concordancia excede el tamaño máximo del documento, donde realmente discuto cómo evitar que se alcance ese límite al usar tales opciones u otra sintaxis de Lookup() disponible para la interfaz fluida solo en este momento .

Tengo la siguiente consulta de MongoDb trabajando:

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

Esto recupera una lista de objetos unidos con un objeto coincidente de otra colección.

¿Alguien sabe cómo puedo usar esto en C # usando LINQ o usando esta cadena exacta?

Intenté usar el siguiente código, pero parece que no puedo encontrar los tipos para QueryDocument y MongoCursor . ¿Creo que están en desuso?

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


MongoDB.Entities aquí cómo hacerlo con MongoDB.Entities . En los casos en que dos entidades se encuentran en una relación de uno a muchos o de muchos a muchos, puede obtener acceso a relaciones inversas sin tener que hacer uniones manualmente como se muestra a continuación. [descargo de responsabilidad: soy el autor de la biblioteca]

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(); } } }