MongoDB / C # Как запросить только глубоко вложенный массив?

#c# #json #mongodb

#c# #json #mongodb

Вопрос:

Я устал от того, что не могу понять (ни в одиночку, ни через SO), как напрямую извлекать глубоко вложенные данные внутри BsonDocument или пользовательские классы. Я знаю, что я должен обязательно использовать фильтр и проекцию, чтобы получить массив / список Guid s, вложенных в другой массив. Ниже приведена структура (упрощенная):

 //Thread
{
  Id: "B2",
  Answers: [
    {
      Id: "A1",
      Likes: [ "GUID1", "GUID2", "ETC" ] //<- this array, and only this.
    }
  ]
}
  

У меня есть Thread.Id как Answer.Id данные фильтрации, так и данные фильтрации, но затем я попытался с:

 var f = Builders<BsonDocument>.Filter;
var filter = f.And(f.Eq("Id", ids.ThreadId), f.Eq("Answers.$[].Id", ids.AnswerId));
var projection = Builders<BsonDocument>.Projection.Include("Answers.Likes.$");
var likes = await dbClient.GetCollection<BsonDocument>(nameof(Thread))
    .Find(filter)
    .Project(projection)
    .FirstOrDefaultAsync();
  

Но этот запрос всегда возвращает null, что я делаю неправильно из этого POV?

Комментарии:

1. Я бы изменил это на строго типизированные объекты, и таким образом вы можете построить свой запрос, используя только linq, чтобы вам не приходилось использовать такие вещи, как Lies.$ или $ [], а затем попробовать.

2. Со строго типизированными объектами я не могу запрашивать / проектировать прямолинейно array[].array[].array[]

3. @Eugene не могли бы вы попробовать: var f = Builders<BsonDocument>.Filter; var filter = f.And(f.Eq("Id", ids.ThreadId), f.Eq("Answers.Id", ids.AnswerId)); var projection = Builders<BsonDocument>.Projection.Include("Answers.Likes"); var likes = await dbClient.GetCollection<BsonDocument>(nameof(Thread)) .Find(filter) .Project(projection) .FirstOrDefaultAsync(); я не думаю, что $/ $ [] требуется для драйвера C #.

4. @DipenShah Put like это также всегда возвращает null между тем я вижу, что находится в БД

5. @Eugene являются ли имена полей в БД такими же, как указано здесь? Я имею в виду, они в одном и том же случае?

Ответ №1:

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

В лучшем случае вы можете спроецировать соответствующий элемент, используя обычные запросы, а затем сопоставить подобные.

Что-то вроде

 var f = Builders<BsonDocument>.Filter;
var filter = f.And(f.Eq("Id", ids.ThreadId), f.Eq("Answers.Id", ids.AnswerId));
var projection = Builders<BsonDocument>.Projection.Include("Answers.$");
var answer = await dbClient.GetCollection<BsonDocument>(nameof(Thread))
    .Find(filter)
    .Project(projection)
    .FirstOrDefaultAsync();
  

В качестве альтернативы вы можете использовать фильтры с картой, используя агрегацию, чтобы сопоставить элемент ответа по идентификатору, за которым следует проекция, чтобы сопоставить подобное поле.

Что-то вроде

 var f = Builders<BsonDocument>.Filter;
var match = f.And(f.Eq("Id", ids.ThreadId), f.Eq("Answers.Id", ids.AnswerId));

var project = new BsonDocument("newRoot", 
            new BsonDocument("$arrayElemAt", new BsonArray {
                new BsonDocument("$map", 
                    new BsonDocument
                        { 
                            { "input", 
                                new BsonDocument("$filter", new BsonDocument
                                    {
                                        { "input", "$Answers"},
                                        {"cond", new BsonDocument("$eq", new BsonArray { "$$this.Id", ids.AnswerId})}
                                    })
                            },
                            { "in", new BsonDocument("Likes", "$$this.UserLikes") }
                        }), 
                0}));    

var pipeline = collection.Aggregate()
.Match(match)
.AppendStage<BsonDocument, BsonDocument, BsonDocument>(new BsonDocument("$replaceRoot", project));

var list = pipeline.ToList();
  

Рабочий пример здесь — https://mongoplayground.net/p/wM1z6q92_mV

Комментарии:

1. Это сопоставление не сработало, bson пуст. Стоит ли прилагать усилия в mongo DB для создания сложных документов, но затем быть убитым сложными запросами?

2. Исправлено — добавлен пример использования запроса, сгенерированного драйвером, для проверки ожидаемых результатов.

Ответ №2:

Я не смог получить лайки с помощью одиночной фильтрации и проекции. Однако я смог добиться этого с помощью конвейера агрегации.

 private async Task<BsonArray> GetLikes(string docId, string answerId)
{
    var client = new MongoClient();
    var idFilter = Builders<BsonDocument>.Filter.Eq("ID", docId);
    var answerIdFilter = Builders<BsonDocument>.Filter.Eq("Answers.ID", answerId);
    var projection = Builders<BsonDocument>.Projection.Exclude("_id").Include("Answers.Likes");
    var likes = await client.GetDatabase("test").GetCollection<BsonDocument>("items")
        .Aggregate(new AggregateOptions())
        .Match(idFilter)
        .Unwind("Answers")
        .Match(answerIdFilter)
        .Project(projection)
        .FirstOrDefaultAsync();

    return likes == null ? null
        : (likes.GetElement("Answers").Value as BsonDocument).GetElement("Likes").Value as BsonArray;
}
  

По какой-то причине результат включал документ в исходную структуру, а не включал только документ со Likes свойством, поэтому мне пришлось выполнить некоторую постобработку после этого.

Комментарии:

1. По-прежнему возвращается null :/ Я добавлю больше деталей в чат, если вам все еще интересно.