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

时间:2016-02-04 14:06:21

标签: c# mongodb linq

我有以下模型:Thre是incidents。每个incident都有time frames,而time frame每个objects都有time frame。每次事件都可以有数千的帧。

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

我将事件存储在MongoDb数据库中。我想获得摘要:选择参与每个事件的不同 objects计数。在这种情况下,它将是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方法

我提出的解决方案是对数据进行非规范化并将objects计数存储为incident中的数字。

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

如何做这个数据库端?我还没有NoSQL的经验,所以我的想法可能会被类似SQL的apprach蒙上阴影。也许这种架构是完全错误的?

1 个答案:

答案 0 :(得分:1)

嗯,你可以做的一件事就是首先调查你的查询,只加载你需要的数据,然后使用Linq对象处理这些数据:

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),那么您可以使用$size聚合运算符获得ObjectIds数组的长度。如果您的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();