Mapreduce RavenDB:为什么我在这个多重映射/减少索引中得到字段的空值?

Mapreduce RavenDB:为什么我在这个多重映射/减少索引中得到字段的空值?,mapreduce,ravendb,Mapreduce,Ravendb,受Ayende文章的启发,我有以下索引,如下所示: public类Posts\u与ViewCountByUser:AbstractMultiMapIndexCreationTask { 公共帖子_WithViewCountByUser() { AddMap(posts=>来自posts中的p 选择新的 { ViewedByUserId=(字符串)null, ViewCount=0, Id=p.Id, PostTitle=p.PostTitle, }); AddMap(postView=>来自po

受Ayende文章的启发,我有以下索引,如下所示:

public类Posts\u与ViewCountByUser:AbstractMultiMapIndexCreationTask
{
公共帖子_WithViewCountByUser()
{
AddMap(posts=>来自posts中的p
选择新的
{
ViewedByUserId=(字符串)null,
ViewCount=0,
Id=p.Id,
PostTitle=p.PostTitle,
});
AddMap(postView=>来自postView中的postView
选择新的
{
ViewedByUserId=postView.ViewedByUserId,
ViewCount=1,
Id=(字符串)postView.PostId,
PostTitle=(字符串)null,
});
Reduce=results=>from result in results
分组结果由新
{
结果.Id,,
result.ViewedByUserId
}
进入g
选择新结果
{
ViewCount=g.Sum(x=>x.ViewCount),
Id=g.Key.Id,
ViewedByUserId=g.Key.ViewedByUserId,
PostTitle=g.Select(x=>x.PostTitle)。其中(x=>x!=null)。FirstOrDefault(),
};
存储(x=>x.PostTitle,FieldStorage.Yes);
}
公开课成绩
{
公共字符串Id{get;set;}
公共字符串ViewedByUserId{get;set;}
public int ViewCount{get;set;}
公共字符串PostTitle{get;set;}
}
}
我想这样查询这个索引:

返回所有帖子,包括给定用户查看帖子的次数的整数。“视图”存储在单独的文档类型中,
PostView
。请注意,我真正的文档类型在这里被重命名,以匹配本文中的示例(我当然不会以这种方式实现“查看次数最多”)

我得到的查询结果是正确的-即,我总是为用户获得具有正确视图计数的所有
Post
文档。但我的问题是,结果集中的PostTitle字段总是空的(所有
Post
文档在数据集中都有非空值)

我将userId和(post)Id的组合作为我的“唯一性”进行分组。我的理解是(如果我错了,请纠正我),在reduce的这一点上,我有一堆伪文档,它们具有相同的userId/postId组合,其中一些来自
Post
map,另一些来自
PostView
map。现在我只需找到其中任何一个伪文档,它们实际上有一个PostTitle值,即一个源于
Post
map的文档。这些显然都应该有相同的价值,因为它是相同的帖子,只是“外部连接”。
.Select(..).Where(..).FirstOrDefault()链取自我用作基础的示例。然后,我为最终文档设置这个ViewCount值,并将其投影到结果中


我的问题是:如何在结果中获取PostTitle字段的非空值?

问题在于:

       ViewedByUserId = (string) null,
以及:

换句话说,您实际上是按null分组的,我假设这不是您的意图

只在
PostView
上有一个map/reduce索引,并通过include或transformer获取
PostTitle
会简单得多

您对正在发生的事情的理解是正确的,因为您正在创建带有
userId/postId
的索引结果

Buit您实际上正在使用
userId/postId
PostView
null/postId
Post
创建结果


这就是为什么您没有想要的匹配项。

索引中的分组不正确。使用以下示例数据:

new Post { Id = "Post-1", PostTitle = "Post Title", AuthorId = "Author-1" }
new PostView { ViewedByUserId = "User-1", PostId = "Post-1" }
new PostView { ViewedByUserId = "User-1", PostId = "Post-1" }
new PostView { ViewedByUserId = "User-2", PostId = "Post-1" }
索引结果如下所示:

ViewCount | Id     | ViewedByUserId | PostTitle
--------- | ------ | -------------- | ----------
 0        | Post-1 | null           | Post Title
 2        | Post-1 | User-1         | null
 1        | Post-1 | User-2         | null
索引中的映射操作只是为所有源文档创建一个公共文档。因此,
Post-1
文档产生一行,
Post-1
User-1
的两个文档产生两行(随后减少为
ViewCount
==2的单行),而
Post-1
User-2
的文档产生最后一行

reduce操作将所有映射行分组,并在索引中生成结果文档。在这种情况下,
Post
源文档与
PostView
源文档分开存储,因为
viewByUserId
中的
null
值未与
PostView
集合中的任何文档分组

如果可以更改存储数据的方式,则可以通过直接在PostView中存储视图的数量来解决此问题。它将大大减少数据库中的重复数据,同时在更新视图计数时具有几乎相同的成本

完成测试(需要xunit和RavenDB.Tests.Helpers nugets):

使用Raven.Abstractions.index;
使用Raven.Client;
使用Raven.Client.index;
使用Raven.Tests.Helpers;
使用System.Linq;
使用Xunit;
名称空间SO41559770Answer
{
公共类SO41559770:RavenTestBase
{
[事实]
公共无效SO41559770Test()
{
使用(var server=GetNewServer())
使用(var store=NewRemoteDocumentStore(ravenDbServer:server))
{
新建PostViewsIndex().Execute(存储);
使用(IDocumentSession=store.OpenSession())
{
存储(新帖子{Id=“Post-1”,PostTitle=“Post-Title”,authord=“Author-1”});
存储(新的PostView{Id=“Views-1-1”,viewByUserId=“User-1”,PostId=“Post-1”,ViewCount=2});
session.Store(新的PostView{Id=“Views-1-2”,视图
ViewCount | Id     | ViewedByUserId | PostTitle
--------- | ------ | -------------- | ----------
 0        | Post-1 | null           | Post Title
 2        | Post-1 | User-1         | null
 1        | Post-1 | User-2         | null
using Raven.Abstractions.Indexing;
using Raven.Client;
using Raven.Client.Indexes;
using Raven.Tests.Helpers;
using System.Linq;
using Xunit;

namespace SO41559770Answer
{
    public class SO41559770 : RavenTestBase
    {
        [Fact]
        public void SO41559770Test()
        {
            using (var server = GetNewServer())
            using (var store = NewRemoteDocumentStore(ravenDbServer: server))
            {
                new PostViewsIndex().Execute(store);

                using (IDocumentSession session = store.OpenSession())
                {
                    session.Store(new Post { Id = "Post-1", PostTitle = "Post Title", AuthorId = "Author-1" });
                    session.Store(new PostView { Id = "Views-1-1", ViewedByUserId = "User-1", PostId = "Post-1", ViewCount = 2 });
                    session.Store(new PostView { Id = "Views-1-2", ViewedByUserId = "User-2", PostId = "Post-1", ViewCount = 1 });
                    session.SaveChanges();
                }

                WaitForAllRequestsToComplete(server);
                WaitForIndexing(store);

                using (IDocumentSession session = store.OpenSession())
                {
                    var resultsForId1 = session
                        .Query<PostViewsIndex.Result, PostViewsIndex>()
                        .ProjectFromIndexFieldsInto<PostViewsIndex.Result>()
                        .Where(x => x.PostId == "Post-1" && x.UserId == "User-1");
                    Assert.Equal(2, resultsForId1.First().ViewCount);
                    Assert.Equal("Post Title", resultsForId1.First().PostTitle);
                    var resultsForId2 = session
                        .Query<PostViewsIndex.Result, PostViewsIndex>()
                        .ProjectFromIndexFieldsInto<PostViewsIndex.Result>()
                        .Where(x => x.PostId == "Post-1" && x.UserId == "User-2");
                    Assert.Equal(1, resultsForId2.First().ViewCount);
                    Assert.Equal("Post Title", resultsForId2.First().PostTitle);
                }
            }
        }
    }

    public class PostViewsIndex : AbstractIndexCreationTask<PostView, PostViewsIndex.Result>
    {
        public PostViewsIndex()
        {
            Map = postViews => from postView in postViews
                               let post = LoadDocument<Post>(postView.PostId)
                               select new
                               {
                                   Id = postView.Id,
                                   PostId = post.Id,
                                   PostTitle = post.PostTitle,
                                   UserId = postView.ViewedByUserId,
                                   ViewCount = postView.ViewCount,
                               };
            StoreAllFields(FieldStorage.Yes);
        }


        public class Result
        {
            public string Id { get; set; }
            public string PostId { get; set; }
            public string PostTitle { get; set; }
            public string UserId { get; set; }
            public int ViewCount { get; set; }
        }
    }

    public class Post
    {
        public string Id { get; set; }
        public string PostTitle { get; set; }
        public string AuthorId { get; set; }
    }

    public class PostView
    {
        public string Id { get; set; }
        public string ViewedByUserId { get; set; }
        public string PostId { get; set; }
        public int ViewCount { get; set; }
    }
}