I have a domain class like this.
public class Thing
{
public Guid Id { get; set; }
public string Name { get; set; }
public Dictionary<string, string> Stuff { get; set; }
}
I'm retrieving it from my DB using the following.
return await _db.Things
.Find(x => x.Name == name)
.SingleOrDefaultAsync(token);
As I noticed, there might be huge chunks of unnecessary data so I used a projection like this.
ProjectionDefinition<Thing> projection = Builders<Thing>
.Projection
.Include(a => a.Id)
.Include(a => a.Name);
BsonDocument projected = await _dbContext.Things
.Find(x => x.Name == name)
.Project(projection)
.SingleOrDefaultAsync(token);
This works but, naturally, cuts of all the dictionary contents. I'd like to alter the definition of projection to include the field but perform a filtration of the constituting elements. Suppose I'd only want to bring in the keys of said dictionary that start with duck. An attempt might be something like this.
ProjectionDefinition<Thing> projection = Builders<Thing>
.Projection
.Include(a => a.Id)
.Include(a => a.Name)
.Include(a => a.Stuff.Where(b => b.Key.StartsWith("duck")));
That resulted in exception as follows.
System.NotSupportedException: The expression tree is not supported: {document}{configuration}
Given my ignorance with MongoDb, I have no idea if I should add something, remove something or forget the idea all together. I also tried to work with the original type to be able to filter the stuff that way but the only solution I've got was post-fetch, basically trumming the retrieved data. I'd like to lower the payload from the DB to my service.
Thing projected = await _dbContext.Things
.Find(x => x.Name == name)
.Project<Thing>(projection)
.SingleOrDefaultAsync(token);
Is it doable at all and if so, how (or at least what to google for)?
Proof of effort: code examples, general operations, tutorials, wrong answers etc. It might be out there somewhere but I failed to find it (or recognize if found).
Finally, I landed into the following - God forgive me for I know not what I'm doing. Is this at all in the right direction or is a gang of crazy donkeys going to bite me in the lower back for this?!
ProjectionDefinition<Thing, Thing> wtf = Builders<Thing>.Projection
.Expression(a => new Thing
{
Id = a.Id,
Name = a.Name,
Stuff = a.Stuff
.Where(b => b.Key == scope)
.ToDictionary(b => scope, b => b.Value)
});
This is a mongo
shell query with MongoDB v4.2.8.
Consider this input document:
{
"_id" : 1,
"name" : "john",
"stuff" : {
"mycolor" : "red",
"fruit" : "apple",
"mybook" : "programming gems",
"movie" : "star wars"
}
}
The goal is to project the name
and stuff
fields, but stuff
with only field names starting with "my"
.
The aggregation query:
db.test.aggregate([
{
$project: {
_id: 0,
name: 1,
stuff: {
$filter: {
input: { $objectToArray: "$stuff" },
as: "stf",
cond: { $regexMatch: { input: "$$stf.k" , regex: "^my" } }
}
}
}
},
{
$addFields: { stuff: { $arrayToObject: "$stuff" } }
}
])
And, the projected output:
{
"name" : "john",
"stuff" : {
"mycolor" : "red",
"mybook" : "programming gems"
}
}