Mapreduce RavenDB:为什么我在这个多重映射/减少索引中得到字段的空值?
受Ayende文章的启发,我有以下索引,如下所示: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
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; }
}
}