代码之家  ›  专栏  ›  技术社区  ›  Andrzej Gis

如何从MongoDb中的内部对象聚合数据?

  •  1
  • Andrzej Gis  · 技术社区  · 9 年前

    我有以下型号:三个 incidents 每一个 incident time frames 并且每 time frame objects 出现在这个 时间框架 。可能有 数千 每个事件中的帧数。

    Incident 
    {
     Id: "incident 1"
     IncidetntFrames: [
      {
       Objects: [
        {
          id: 1
        },
        {
          id: 2
        }
       ]
      },
      {
       Objects: [
        {
          id: 1
        },
        {
          id: 3
        }
       ]
      }
     ]
    }
    

    我将事件存储在MongoDb数据库中。我想获取摘要:选择 不同的 物体 参与每起事件的伯爵。在这种情况下 3 (1,2,3)-3重复。

    我尝试这样做(C#LINQ):

    _mongoDatabase.GetCollection<Incident>("Incident").AsQueryable()
        .Select(incident => new IncidentDto {
            Id = incident.Id;
            ObjectsCount = incident
                .IncidentFrames
                .SelectMany(received => received
                    .Objects
                    .Select(dto => dto.Id))
                .Distinct())
                .Count()
        });
    

    然后我得到一个错误:

    表达式树中不支持SelectMany方法

    我提出的解决方案是对数据进行非规范化并存储 物体 计算为中的数字 发生的事情 .

    这给我带来了一个很有趣的问题。假设应用程序已经投入生产,现在我决定需要对象计数。我必须进行某种迁移来计算这个值并将其添加到现有记录中。

    如何在数据库端执行此操作?我还没有NoSQL的经验,所以我的想法可能会被类似SQL的apprach所蒙蔽。也许这种架构是完全错误的?

    1 回复  |  直到 9 年前
        1
  •  1
  •   ocuenca    9 年前

    好吧,您可以在这里做的一件事是,首先投影查询以仅加载所需的数据,然后使用Linq to Objects处理该数据:

    var query = _mongoDatabase.GetCollection<Incident>("Incident")
                              .Aggregate()
                              .Project(i=>new{Id= i.Id,
                                              ObjectIds= i.IncidentFrames.Select(f=>f.Objects.Select(o=>o.Id))}).ToList();
    
    var result = query.Select(e => new IncidentDto { Id=e.Id, ObjectsCount = e.ObjectIds.SelectMany(l => l).Distinct().Count() });
    

    也许有更好的方法可以使用 aggregations 但这是我能找到的最佳解决方案。

    使现代化

    我找到了另一个解决方案,您可以从数据库端执行同样的操作:

     var Grouping = new BsonDocument { { "_id", "$Id" }, { "ObjectIds", new BsonDocument("$addToSet", "$ObjectIds") } };
     var query = collection.Aggregate()
                           .Project(i => new { i.Id, ObjectIds = i.IncidentFrames.Select(f => f.Objects.Select(o => o.Id)) })
                           .Unwind(a => a.ObjectIds)
                           .Unwind(e => e["ObjectIds"])
                           .Group<IncidentDTO>(Grouping)
                           .ToList();
    

    唯一的是,您需要将DTO稍微更改为以下内容:

    public class IncidentDTO
    {
        public int Id { get; set; }
    
        public int[] ObjectIds { get; set; }
    }
    

    如果您不想获取对象ID,而只希望对象计数(作为原始DTO),那么您可以获取 ObjectIds 数组使用 $size 聚合运算符。如果您的DTO是这样的:

    public class IncidentDTO
    {
        public int Id { get; set; }
    
        public int ObjectsCount{ get; set; }
    }
    

    您可以执行以下操作:

     var Grouping = new BsonDocument { { "_id", "$Id" }, { "ObjectIds", new BsonDocument("$addToSet", "$ObjectIds") } };
     var projection = new BsonDocument { { "_id", "$_id" }, { "ObjectsCount", new BsonDocument("$size", "$ObjectIds") } };
    
     var query = collection.Aggregate()
                           .Project(i => new { i.Id, ObjectIds = i.IncidentFrames.Select(f => f.Objects.Select(o => o.Id)) })
                           .Unwind(a => a.ObjectIds)
                           .Unwind(e => e["ObjectIds"])
                           .Group(Grouping)
                           .Project<IncidentDTO>(projection).ToList();