首页 > 解决方案 > 如何使用 C#、LINQ 在 mongodb 中获取每个组的 n 条记录?

问题描述

每组需要得到10个项目。按 ID 降序和按类别分组

public class Item
{
  public string Id { get; set ; }
  public string Name { get ; set ; }
  public string Category { get ; set ; }
}

我尝试过这样的事情。

    var data = Collection.AsQueryable()
              .OrderByDescending(o=> o.Id)
              .GroupBy(x => x.Category)
              .Select(g => new { GroupName = g.Key, Items = 
              g.Take(10).ToList() });

但是遇到了这样的异常

System.NotSupportedException:不支持指定的方法。在 MongoDB.Driver.Linq.Processors.AccumulatorBinder.GetAccumulatorArgument(表达式节点)

标签: c#mongodbentity-frameworklinq

解决方案


请改用聚合框架。MongoDB LINQ 提供程序建立在它之上。

您可以Id使用以下查询按降序排列每个组的 10 个项目:

db.items.aggregate([
   { "$sort": { "Id": -1 } },
   { "$group": { "_id": "$Category", "Items": { "$push": "$$ROOT" } } },
   { "$project": { "_id": 0, "GroupName": "$_id", "Items": { "$slice": ["$Items", 10] } } }
])

C#代码如下:

// models
public class Item
{
    public string Id { get; set; }
    public string Name { get; set; }
    public string Category { get; set; }
}

public class ItemsGroup
{
    public string GroupName { get; set; }
    public Item[] Items { get; set; }
}

// query
var collection = db.GetCollection<Item>("Items");

IAggregateFluent<ItemsGroup> result = collection.Aggregate()
    .SortByDescending(o => o.Id)
    .Group(BsonDocument.Parse("{ _id: '$Category', Items: { '$push': '$$ROOT'}}"))
    .Project<ItemsGroup>(BsonDocument.Parse("{ _id: 0, GroupName: '$_id',  Items: { $slice: ['$Items', 10]}}"));

List<ItemsGroup> groups = result.ToList();

但是,此查询可能有问题。如果每个组有数千个项目,我猜小组赛阶段会将它们全部保存在Items临时数组中,而我们只需要 10 个。

如果被分组的键的数量不是很大,那么最好为每个组执行 $lookup 而不是将所有内容推入一个数组然后对其进行切片。这可以通过以下查询来实现:

aggregate([
   { "$group": { "_id": "$Category" } },
   { "$lookup": { 
       "from": "Items",
       "let": { "c": "$_id" },
       "as": "Items"
       "pipeline": [
            { "$match": { "$expr": { "$eq": ["$Category", "$$c"] } } },
            { "$sort": { "Id": -1 } },
            { "$limit": 10 }
         ],        
      }
   },
   { "$project": { "_id": 0, "GroupName": "$_id", "Items": 1 } }
])

在 C# 代码中,它会像:

BsonArray pipeline = new BsonArray
{
    BsonDocument.Parse("{ $match: { $expr: { $eq: ['$Category', '$$c']} } }"),
    BsonDocument.Parse("{ $sort: { Id: -1 } }"),
    BsonDocument.Parse("{ $limit: 10 }")
};

BsonDocument lookup = new BsonDocument("$lookup",
    new BsonDocument("from", "Items")
        .Add("let", new BsonDocument("c", "$_id"))
        .Add("pipeline", pipeline)
        .Add("as", "Items")
);

IAggregateFluent<ItemsGroup> result = collection.Aggregate()
    .Group(BsonDocument.Parse("{ _id: '$Category' }"))
    .AppendStage<object>(lookup)
    .Project<ItemsGroup>("{ _id: 0, GroupName: '$_id', Items: 1 }");

List<ItemsGroup> groups = result.ToList();

推荐阅读