我如何获得 RavenDb 中相关项目的计数?

How can I get counts of related items in RavenDb?

在 RavenDb 中,我正在尝试获取包含计数的项目列表。

使用 Raven 示例数据库作为简化示例,我想获得一个类别列表以及每个类别中的产品数量。

相关类:

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

public class Product
{
    public string Id { get; set; }
    public string Category { get; set; }
    // ...others removed...
}

部分有效的查询(但遗漏 0 个计数)

以下查询似乎有效(我正在为 Linqpad 使用 .Dump();):

using (var session = docStore.OpenSession()) {
    var products = session
        .Query<Product>()
        .Customize(x => x.Include<Product>(p => p.Category))
        .ToArray() 
        .GroupBy(x => x.Category)
        .Select(x => new {
            category = session.Load<Category>(x.Key), 
            numProducts = x.Count()
        })
        .Dump();

    session.Advanced.NumberOfRequests.Dump("NumberOfRequests");
}

问题在于,如果某个类别没有产品,则它不会包含在结果集中。

(此外,这是执行此查询的正确方法吗?对服务器的一个请求提示我至少我没有可怕偏离轨道)

使用非常糟糕的查询更正结果

暴力破解,我可以用代码:

using (var session = docStore.OpenSession()) {
    var categories = session
        .Query<Category>();

    var categoryCounts = new Dictionary<Category,int>();
    foreach (var category in categories) 
    {
        if (!categoryCounts.ContainsKey(category)) categoryCounts.Add(category,0);
        categoryCounts[category] += session
            .Query<Product>()
            .Where(p => p.Category == category.Id)
            .Count();
    }
    categoryCounts.Dump();

    session.Advanced.NumberOfRequests.Dump("NumberOfRequests");
}

但这显然是一种糟糕的方式,导致 1+n 次请求(其中 n == 类别数)。


如何获取类别+产品数量,包括没有产品的类别,并且不会导致1+n请求?

对于具有数百个类别和数十万(甚至数百万)产品(单独一个类别可能有数十万)的数据库,是否有不同的考虑因素?

执行此类操作的惯用方法是使用索引(即 map - reduce 索引)。虽然我还没有彻底考虑过这一点,所以可能有比使用多地图更简单的方法来实现这一点,但我认为你可以执行以下操作:

public class CategoryUsageCount
{
    public string CategoryId { get; set; }
    public Category Category { get; set; }
    public int UsageCount { get; set; }
}

public class UsageCountByCategory : AbstractMultiMapIndexCreationTask<CategoryUsageCount>
{
    public UsageCountByCategory()
    {
        AddMap<Category>(categories => 
            from category in categories 
            select new {
                CategoryId = category.Id,
                Category = category,
                UsageCount = 0
            });

        AddMap<Procuct>(products =>
            from product in products
            select new {
                CategoryId = product.Category,
                Category = (Category)null,
                UsageCount = 1
            });

        Reduce = results => 
            from result in results
            group result by result.CategoryId into g
            select new {
                CategoryId = g.Key,
                Category = g.First(x => x != null).Category,
                UsageCount = g.Sum(x => x.UsageCount)
            };

        Index(x => x.CategoryId, FieldIndexing.Analyzed);
    }
}

并像这样使用它:

using (var session = docStore.OpenSession()) {
    var categoryUsageCounts = session
        .Query<CategoryUsageCount, UsageCountByCategory>()
        .ToList();
}