【问题标题】:MongoDB c# retrieve all the matching elements in an array within a document using Definition builderMongoDB c#使用定义生成器检索文档内数组中的所有匹配元素
【发布时间】:2019-05-20 16:40:38
【问题描述】:

我有一个结构看起来像这样的文档,其中包含嵌套的子文档

{  
   "_id":ObjectId("50419077c2e6a1e18a489a0f"),
   "user":"Jone Doe",
   "fooArray":[  
      {  
         "plot":"circle",
         "color":"yellow",
      },
      {  
         "plot":"circle",
         "color":"red",
      },
      {  
         "plot":"square",
         "color":"green",
      }
   ]
}

并且我想检索该文档中 fooArray 中具有圆形图的所有匹配元素。

这是我试过的

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();
            });

这就是我得到的

{  
   "fooArray":[  
      {  
         "plot":"circle",
         "color":"yellow",
      }
   ]
}

但它只给了我第一个匹配的元素,而不是所有绘图等于圆形的元素

{  
   "fooArray":[  
      {  
         "plot":"circle",
         "color":"yellow",
      },
      {  
         "plot":"circle",
         "color":"red",
      }
   ]
}

我确实阅读了提到的 mongodb 文档

" $elemMatch 运算符将查询结果中的字段内容限制为仅包含与 $elemMatch 条件匹配的第一个元素。"

不太清楚如何做到这一点!

【问题讨论】:

    标签: c# mongodb asp.net-core mongodb-.net-driver


    【解决方案1】:

    这个问题没有完全描述用例,所以我提出了一些潜在的选项供您根据一些假设进行探索,特别是它们取决于 LINQ 是否可用并且以单个文档为目标一段时间(你可能不想要比你真正需要的更多的代码):

    1) 你所拥有的东西的变化。使用带有投影和 LINQ 表达式的标准 find

    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) 使用聚合管道(您可以使用与上述相同的投影)

    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) 将包含所有数组元素的文档拉回,然后使用 LINQ 在本地进行过滤。从好的方面来说,这是少量可读的代码,但是,它确实会在过滤之前将整个文档带回来。根据您的具体用途,这可能是可以接受的。

    var items3 = collection.AsQueryable()
        .SingleOrDefault(x => x.user == "Jone Doe")
        .fooArray.Where(x => x.plot == "circle");
    

    如果 LINQ 真的 不是一个选项,那么有一个示例 here 显示了如何将投影转换为不是我们的 LINQ。完全未经测试,但大致如下:

    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】:

      这是一个使用MongoDB.Entities 的简单易用的解决方案,它只是 c# 驱动程序的包装库。

      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();
              }
          }
      }
      

      这是它生成的聚合查询:

        {
          "$match": {
            "Name": "Jone Doe"
          }
        },
        {
          "$unwind": "$Foos"
        },
        {
          "$project": {
            "Foos": "$Foos",
            "_id": 0
          }
        },
        {
          "$match": {
            "Foos.Plot": "circle"
          }
        }
      

      【讨论】:

      • 您也可以像 Greg Stanley 所说的那样,使用 IQueryable var circularFoos = collection.AsQueryable().Where(u =&gt; u.user == "Jone Doe").SelectMany(u =&gt; u.fooArray).Where(f =&gt; f.plot == "circle").ToArray(); 来实现不使用该库的操作。
      • 是的,但是一旦我习惯了这个库提供的便利,就没有回头路了。现在我不会用 10 英尺长的杆子碰官方司机 ;-)
      【解决方案3】:

      我想出了一个巧妙的方法

      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();
      }
      

      【讨论】:

        猜你喜欢
        • 2013-03-03
        • 2018-10-23
        • 2015-07-01
        • 1970-01-01
        • 1970-01-01
        • 2016-06-29
        • 1970-01-01
        • 2015-02-11
        • 1970-01-01
        相关资源
        最近更新 更多