问题
I have a document that looks like this in structure with nested sub document
{
"_id":ObjectId("50419077c2e6a1e18a489a0f"),
"user":"Jone Doe",
"fooArray":[
{
"plot":"circle",
"color":"yellow",
},
{
"plot":"circle",
"color":"red",
},
{
"plot":"square",
"color":"green",
}
]
}
And I want to retrieve all the matching elements in fooArray in this document that has circular plot.
This is what I tried
var filter = FilterBuilder.filter.Eq(doc => doc.User, User);
var projection = ProjectionBuilder
.Exclude(doc => doc.Id)
.Exclude(doc => doc.User)
.Include(doc => doc.FooArray)
.ElemMatch(x => x.FooArray, y => y.Plot == "circle");
var definition = new OperationDefinitions<ShapeDocument> { Filter = filter };
return await Performer.Perform(definition, async (def, collection) =>
{
var findResult = collection.Find(def.Filter).Project(projection);
var result = await findResult.SingleOrDefaultAsync();
});
This is what I get
{
"fooArray":[
{
"plot":"circle",
"color":"yellow",
}
]
}
But it only gives me the first matching element instead of all the elements that have the plot equals to circle
{
"fooArray":[
{
"plot":"circle",
"color":"yellow",
},
{
"plot":"circle",
"color":"red",
}
]
}
I did read the mongodb documentation which mentions
" The $elemMatch operator limits the contents of an field from the query results to contain only the first element matching the $elemMatch condition."
Not quite sure how to achieve this!
回答1:
The question doesn't fully describe the use case so I've come up with a few potential options for you to explore based on a few assumptions, in particular they depend on LINQ being available and to be targetting a single document at a time (and that you probably don't want more code than you really need):
1) A variation on what you have. Use a standard find
with a projection and LINQ expression.
var projection = Builders<ShapeDocument>.Projection
.Expression(x => x.fooArray.Where(y => y.plot == "circle"));
var items1 = collection
.Find(x => x.user == "Jone Doe")
.Project(projection)
.ToList();
2) Use the aggregation pipeline (you could use the same projection as above)
var pipeline = collection
.Aggregate()
.Match(x => x.user == "Jone Doe")
.Project(i => new
{
x = i.fooArray.Where(x => x.plot == "circle")
});
var items2 = pipeline.SingleOrDefault();
3) Pull the document back with all array elements then filter locally using LINQ. On the plus side this is a small amount of readable code, however, it does bring the entire document back before filtering. Depending on you're exact use this may well be acceptable.
var items3 = collection.AsQueryable()
.SingleOrDefault(x => x.user == "Jone Doe")
.fooArray.Where(x => x.plot == "circle");
If LINQ really isn't an option then there's an example here that shows how you might convert the projection to not us LINQ. Totally untested but would be something along the lines of:
var filter = new BsonDocument {
{"input", "$items"},
{"as", "item" },
{"cond", new BsonDocument {
// Fill in the condition values
{ "", new BsonArray { "", xxx } } }
}
};
var project = new BsonDocument {
{ "items", new BsonDocument { { "$filter", filter} } }
};
var pipeline = collection.Aggregate().Project(project);
回答2:
here's a sweet and simple solution using MongoDB.Entities which is just a wrapper library for the c# driver.
using MongoDB.Driver.Linq;
using MongoDB.Entities;
using System.Linq;
namespace StackOverflow
{
public class User : Entity
{
public string Name { get; set; }
public Foo[] Foos { get; set; }
}
public class Foo
{
public string Plot { get; set; }
public string Color { get; set; }
}
class Program
{
static void Main(string[] args)
{
new DB("test");
var user = new User
{
Name = "Jone Doe",
Foos = new[]
{
new Foo{ Plot = "circle", Color="yellow"},
new Foo{ Plot = "circle", Color="red"},
new Foo{ Plot = "square", Color="green"},
}
};
user.Save();
var circularFoos = DB.Collection<User>()
.Where(u => u.Name == "Jone Doe")
.SelectMany(u => u.Foos)
.Where(f=>f.Plot=="circle").ToArray();
}
}
}
this is the aggregate query it generates:
{
"$match": {
"Name": "Jone Doe"
}
},
{
"$unwind": "$Foos"
},
{
"$project": {
"Foos": "$Foos",
"_id": 0
}
},
{
"$match": {
"Foos.Plot": "circle"
}
}
回答3:
I figured a neat way to do it
var filter = FilterBuilder.filter.Eq(doc => doc.User, User);
var definition = new OperationDefinitions<ShapeDocument> { Filter = filter };
return await Performer.Perform(definition, async (def, collection) =>
{
var findResult = collection.Find(def.Filter).Project(doc => doc.fooArray.Where(x => x.Plot == "Circle"));
var result = await findResult.SingleOrDefaultAsync();
}
来源:https://stackoverflow.com/questions/56225026/mongodb-c-sharp-retrieve-all-the-matching-elements-in-an-array-within-a-document