Reputation: 68127
I'm wondering how one can query a MongoDB collection for an embedded document (in an array) via the official C# driver version 1.7? By querying for an embedded document I mean I'd like to retrieve only the embedded document and not the one containing it, AKA projection.
Example of the kind of data model, with embedded documents, I am querying:
// Library, contained in db.Libraries
{
_id: 1,
Categories: [{_id: 2, Name: "Classics", Books: [{_id: 3, Name: The Count of Monte Cristo}]}]
}
The problem here would be how to query the Library collection for an object with _id
1 and one of its books with _id
3, and return only the Book. Is it doable at all? As far as I could tell, it would be possible to do this through a projection in the MongoDB shell.
My example query would be to query db.Libraries for a Book with _id
3 contained in a Library with _id
1. The Book returned would be this sub-document:
{_id: 3, Name: The Count of Monte Cristo}]}
I've looked at the question How to retrieve an embedded document using the official C# driver for MongoDB?, but I can't make the accepted answer work for me.
EDIT:
I can see now that the accepted answer of How to retrieve an embedded document using the official C# driver for MongoDB? works, kind of. I failed to see that it iterates over each found document, equivalent to this:
var libraryCollection = new MongoCollection<Library>();
var refBook = new Book { Id = ObjectId.GenerateNewId().ToString(), Name = "The Count of Monte Cristo" };
libraryCollection.Insert(new Library { Id = ObjectId.GenerateNewId().ToString(), Categories = new[] { new Category { Books = new[] { refBook } } } });
MongoCursor<Library> libraries = libraryCollection.Find(new QueryDocument(
new Dictionary<string, object>
{
{"_id", new ObjectId()},
}
));
Book book;
foreach (var library in libraries)
{
book = library.Categories.SelectMany(c => c.Books).FirstOrDefault(b => b.Id == refBook.Id);
}
However, this solution is moot since it retrieves whole Library documents rather than just the embedded Book document. I'm really after having to deserialize only the embedded Book, AKA projection.
Upvotes: 2
Views: 5697
Reputation: 312169
To perform a projection where the resulting document is not just filtered, but reshaped, you need to use Aggregate
instead of one of the Find
methods like this:
var result = collection.Aggregate(
// Only include the document where _id = 1
new BsonDocument {{"$match", new BsonDocument {{"_id", 1}}}},
// 'Unwind' the Categories array by duplicating the docs, one per element.
new BsonDocument {{"$unwind", "$Categories"}},
// Now do the same for the Books array inside each Categories element.
new BsonDocument {{"$unwind", "$Categories.Books"}},
// Only include the resulting docs with a Book _id of 3
new BsonDocument {{"$match", new BsonDocument {{"Categories.Books._id", 3}}}},
// Reshape the document to bring the book attributes out to the top level
new BsonDocument {{"$project", new BsonDocument {
{"_id", "$Categories.Books._id"},
{"Name", "$Categories.Books.Name"}
}}}
);
result.Response.toJson()
:
{"result": [{ "_id": 3.0, "Name": "The Count of Monte Cristo" }], "ok": 1.0 }
Upvotes: 4