MongoDB c# retrieve all the matching elements in an array within a document using Definition builder

人走茶凉 提交于 2019-12-11 12:44:27

问题


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!