You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MongoDB .NET Driver:为商品集合添加评分数量与平均值统计

解决方案

你的核心问题是误用了Group阶段,并且没有正确处理Lookup后的强类型映射。Lookup后每个商品文档已经关联了对应的评分数组,不需要对整个集合分组,而是要针对每个商品的评分数组单独计算统计值。下面是修正后的实现方案:

1. 定义结果DTO

首先创建一个DTO来承载商品信息+评分统计,避免修改原ShopItem类:

public class ShopItemWithRatingStats
{
    public Guid Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public decimal Price { get; set; }
    public string Description { get; set; } = string.Empty;
    public SeasonEnum Season { get; set; }
    public DateTimeOffset CreatedDate { get; set; }
    public int AmountOfRatings { get; set; }
    public double? AverageRating { get; set; } // 用double适配MongoDB的$avg返回值,无评分时返回null
}

2. 修正聚合管道

方式一:强类型聚合(推荐)

通过泛型Lookup保持强类型,直接在投影阶段计算统计值:

// 假设你有评分集合的引用
var ratingsCollection = database.GetCollection<RatingModel>("Rate");

FilterDefinition<ShopItem>? filter = filterBuilder.Empty;

if (filterOptions.NameToMatch != null)
{ 
    var nameFilter = filterBuilder.Where(item => item.Name.Contains(filterOptions.NameToMatch));
    filter &= nameFilter;
}

if (filterOptions.SeasonToMatch != null)
{ 
    var seasonFilter = filterBuilder.Where(item => item.Season == filterOptions.SeasonToMatch);
    filter &= seasonFilter;
}

var items = await itemsCollection
    .Aggregate()
    .Match(filter)
    // 强类型关联评分集合,将结果映射到临时的带Ratings属性的类
    .Lookup<ShopItem, RatingModel, ShopItemWithRatings>(
        ratingsCollection,
        localField: item => item.Id,
        foreignField: rating => rating.ShopItemId,
        @as: itemWithRatings => itemWithRatings.Ratings
    )
    // 投影构造最终结果,计算评分统计
    .Projection(itemWithRatings => new ShopItemWithRatingStats
    {
        Id = itemWithRatings.Id,
        Name = itemWithRatings.Name,
        Price = itemWithRatings.Price,
        Description = itemWithRatings.Description,
        Season = itemWithRatings.Season,
        CreatedDate = itemWithRatings.CreatedDate,
        AmountOfRatings = itemWithRatings.Ratings.Count,
        // 无评分时返回null,避免空数组计算平均值出错
        AverageRating = itemWithRatings.Ratings.Any() ? (double?)itemWithRatings.Ratings.Average(r => r.Rate) : null
    })
    .ToListAsync();

需要临时定义一个继承自ShopItem的中间类来承载关联的评分:

public class ShopItemWithRatings : ShopItem
{
    public List<RatingModel> Ratings { get; set; } = new List<RatingModel>();
}

方式二:BsonDocument聚合

如果不想新增中间类,可以直接用BsonDocument构造聚合阶段,最后映射到DTO:

var items = await itemsCollection
    .Aggregate()
    .Match(filter)
    // 关联评分集合,生成BsonDocument
    .Lookup("Rate", "Id", "ShopItemId", "Ratings")
    // 添加评分统计字段
    .AddFields(new BsonDocument
    {
        { "AmountOfRatings", new BsonDocument("$size", "$Ratings") },
        { "AverageRating", new BsonDocument("$avg", "$Ratings.Rate") }
    })
    // 移除临时的Ratings字段
    .Project(Builders<BsonDocument>.Projection.Exclude("Ratings"))
    // 映射到目标DTO
    .As<ShopItemWithRatingStats>()
    .ToListAsync();

关键说明

  • 不需要Group阶段:Group是用来对多个文档做分组统计(比如按季节分组统计所有商品的评分),而你需要的是单个商品的评分统计,Lookup后每个文档已经对应一个商品+它的所有评分,直接对数组计算即可。
  • 空评分处理:当商品没有评分时,$avg会返回null,强类型方式中需要用Any()判断避免空数组调用Average()报错。
  • 类型映射:通过泛型Lookup或者As<T>方法可以将BsonDocument转换回强类型对象,解决Lookup后类型变为BsonDocument的问题。

内容的提问来源于stack exchange,提问作者algorithms

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 09:54:33