Объединить поиск $ с C # - PullRequest
0 голосов
/ 25 мая 2018

У меня работает следующий запрос MongoDb:

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

Извлекает список объектов, соединенных с подходящим объектом из другой коллекции.

Кто-нибудь знает, как я могу использовать это вВ C # с использованием LINQ или с использованием этой точной строки?

Я пытался использовать следующий код, но кажется, что не удается найти типы для QueryDocument и MongoCursor - я думаю, что они устарели?

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

Ответы [ 2 ]

0 голосов
/ 04 июня 2019

вот как это сделать с MongoDB.Entities .в тех случаях, когда два объекта находятся в отношении «один ко многим» или «многие ко многим», вы можете получить обратный доступ к отношениям без необходимости выполнять соединения вручную, как показано ниже.[Отказ от ответственности: я автор библиотеки]

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();
        }
    }
}
0 голосов
/ 26 мая 2018

Нет необходимости анализировать JSON.Здесь все можно сделать напрямую с помощью интерфейсов LINQ или Aggregate Fluent.

Просто с помощью некоторых демонстрационных классов, потому что вопрос на самом деле мало что дает для продолжения.

Настройка

В основном у нас есть две коллекции:

сущностей

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

и другие

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

Ипара классов, к которым они могут быть привязаны, как простые примеры:

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

Запрос отправлен на сервер:

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

Вероятно, самый простой для понимания, поскольку свободный интерфейс в основном такой же, как и общая структура BSON.Стадия $lookup имеет все те же аргументы, а $arrayElemAt обозначается First().Для $sort вы можете просто предоставить документ BSON или другое допустимое выражение.

Альтернативой является более новая выразительная форма $lookup с подпунктомоператор конвейера для MongoDB 3.6 и выше.

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

Запрос отправлен на сервер:

[ 
  { "$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" пока не поддерживает синтаксис напрямую, а выражения LINQ не поддерживаютОператор $expr, однако вы все еще можете создавать с использованием BsonDocument и BsonArray или других допустимых выражений.Здесь мы также "набираем" результат $unwind, чтобы применить $sort, используя выражение, а не BsonDocument, как показано ранее.

Помимо других применений, основная задача «субпроводника» - уменьшить количество документов, возвращаемых в целевом массиве, до $lookup.Также $unwind здесь служит для "* объединения" *1072* в оператор $lookup при выполнении на сервере, так что это обычно более эффективно, чемпросто захватывает первый элемент полученного массива.

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

Запрос отправлен на сервер:

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

Это почти идентично, но только с использованием другогоинтерфейс и производит немного другой оператор BSON, и на самом деле только из-за упрощенного именования в функциональных операторах.Это вызывает другую возможность простого использования $unwind, полученного из 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);

Запрос, отправленный на сервер:

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

Обычно размещение $unwind непосредственно после $lookup на самом деле "оптимизированный шаблон" для структуры агрегации.Тем не менее, драйвер .NET все портит в этой комбинации, заставляя $project между, а не используя подразумеваемое именование "as".Если бы не это, это на самом деле лучше, чем $arrayElemAt, когда вы знаете, что у вас есть «один» связанный результат.Если вы хотите $unwind «коалесценция», то вам лучше использовать плавный интерфейс или другую форму, как показано ниже.

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;

Запрос отправлен на сервер:

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

Все довольно знакомо и в действительности относится только к функциональным именам.Как и при использовании опции $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;

Запрос отправлен на сервер:

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

, который на самом деле использует оптимизированную коалесценцию "" форма.Переводчик по-прежнему настаивает на добавлении $project, поскольку нам нужен промежуточный select, чтобы сделать утверждение действительным.

Резюме

Так чтонесколько способов по существу получить то же, что и в основном, тот же оператор запроса с точно такими же результатами.Хотя вы «можете» проанализировать JSON-форму BsonDocument и передать ее в свободную команду Aggregate(), обычно лучше использовать естественные компоновщики или интерфейсы LINQ, поскольку они легко отображаются на один и тот же оператор.

Варианты с $unwind в значительной степени показаны, потому что даже при «единственном» совпадении эта форма «коалесценции» на самом деле гораздо более оптимальна, чем использование $arrayElemAt для получения«первый» элемент массива.Это даже становится более важным с учетом таких вещей, как предел BSON, где целевой массив $lookup может привести к тому, что родительский документ превысит 16 МБ без дальнейшей фильтрации.Здесь есть еще одно сообщение о Aggregate $ lookup. Общий размер документов в соответствующем конвейере превышает максимальный размер документа , где я на самом деле обсуждаю, как избежать этого ограничения, используя такие параметры или другой синтаксис Lookup(), доступный длясвободный интерфейс только в это время.

...