#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
:/ Я добавлю больше деталей в чат, если вам все еще интересно.