我有一组代表一些工作项的文件:
public class WorkItem
{
public string Id {get;set;
public string DocumentId { get; set; }
public string FieldId { get; set; }
public bool IsValidated { get; set; }
}
public class ExtractionUser
{
public string Id {get;set;}
public string Name {get;set;}
public string[] AssignedFields {get;set;}
}
用户可以访问一组FieldIds。我需要根据这组字段查询WorkItems并获得每个文档的状态:
public class UserWorkItems
{
public string DocumentId { get; set; }
public int Validated { get; set; }
public int Total { get; set; }
}
我追求的问题是:
using (var session = RavenDb.OpenSession())
{
string[] userFields = session.Load<User>("users/1").Fields;
session.Query<WorkItem>()
.Where(w => w.FieldId.In(userFields))
.GroupBy(w => w.DocumentId)
.Select(g => new
{
DocumentId = g.Key,
Validated = g.Where(w => w.IsValidated).Count(),
Total = g.Count()
}).Skip(page * perPage).Take(perPage)
.ToArray();
}
我尝试过创建Map / Reduce索引,但主要问题是我需要能够在FieldId上应用一个过滤器,它不包含在Reduce输出中,因为它是计算的属性。
我还尝试在FieldId上为查询部分执行简单的Map索引,并使用TransformResults执行GroupBy - 但由于在TransformResults之前应用了分页,因此页面和总计在分组之前反映了文档并不好。
然后我尝试使用映射用户及其字段集合的Multi Map索引,并映射工作项和字段,然后尝试将结果减少到我想要的。我用索引定义创建了a gist。 reduce部分涉及一组一个字段,然后是多个SelectMany和一个最终的GroupBy和Select。该指数已被乌鸦接受,但我没有返回任何结果。我有点卡在Multi Map索引,因为我不知道我怎么能实际调试它。
我想最终我的问题可能会减少(双关语)如何查询“减少”字段?
我有什么想法可以实现这样的功能?我还可以在Map / MultiMap / Reduce / TransformResults旁边探索其他任何选项吗?
更新:在阅读Ayende's Map Reduce post时,我意识到我正在接近mapreduce错误。仍在寻找解决方案......
更新2 :经过一番研究后,我最终得到了这个看起来像我想做但却没有返回任何数据的索引(索引直接在工作室中定义) :
地图:
from user in docs
where user["@metadata"]["Raven-Entity-Name"] == "ExtractionUsers"
from field in user.AssignedFields
from item in docs
where item["@metadata"]["Raven-Entity-Name"] == "WorkItems" && item.FieldId == field
select new {
UserId = user.Id,
DocumentId = item.DocumentId,
Validated = item.Status=="Validated"? 1: 0,
Count = 1
}
减少
from r in results
group r by new { r.UserId , r.DocumentId } into g
select new {
UserId = g.Key.UserId,
DocumentId = g.Key.DocumentId,
Validated = g.Sum(d => d.Validated),
Count = g.Sum(d => d.Count),
}
这个想法是尝试在索引中映射所有文档,并从Users链接到Fields和WorkItems。
答案 0 :(得分:3)
一周后我成功地解决了这个问题。我采用了一种稍微不同(较少关系)的方法,这种方法很简单,似乎工作得很好。以下是其他人遇到此类问题的详细信息:
我通过DocumentId对WorkItems进行分组,并将Validated和NonValidated字段放入一个集合中。 map reduce的结果如下所示:
public class Result
{
public string DocumentId { get; set; }
public string[] ValidatedFields { get; set; }
public string[] ReadyFields { get; set; }
}
地图功能如下所示:
Map = items => items.Select(i => new
{
DocumentId = i.DocumentId,
ValidatedFields = i.IsValidated ? new string[] { i.FieldId } : new string[0],
ReadyFields = !i.IsValidated ? new string[] { i.FieldId } : new string[0]
});
Reduce :
Reduce = result => result
.GroupBy(i => i.DocumentId)
.Select(g => new
{
DocumentId = g.Key,
ValidatedFields = g.SelectMany(i => i.ValidatedFields),
ReadyFields = g.SelectMany(i => i.ReadyFields)
});
要查询索引,我现在使用以下表达式:
User user = session.Load<User>("users/1");
var result = session.Query<WorkItem, UserWorkItemIndex>()
.As<UserWorkItemIndex.Result>()
.Where(d => d.ValidatedFields.Any(f => f.In(user.AssignedFields)))
.ToArray();
我唯一需要做客户端的事情就是只计算属于用户的字段。
还有gist解决方案。
答案 1 :(得分:0)
首先,免责声明:我之前从未在真实系统中使用过RavenDB,但我阅读了一些文章,观看了一些视频,并且非常喜欢它背后的想法。我认为这个问题是一个有趣的练习。所以,这种做法可能并不理想;欢迎提出意见和改进。
我的想法是必须在WorkItems
集合上创建一个索引以包含这些字段:
DocumentId
(因为这是我们最后将分组的内容)FieldId
(因为那是我们将过滤的字段)ValidatedCount
(IsValidated
= true)TotalCount
创建此索引后,我们可以使用.Where(x => x.FieldId.In(userFields))
过滤器进行查询,并获取具有上述结构的一组结果。
为了获得最终结果,我们需要根据DocumentId
对这些结果进行更多分组。
我提出的代码是:
public class WorkItems_ValidationStatistics :
AbstractIndexCreationTask<WorkItem, WorkItems_ValidationStatistics.ReduceResult>
{
public class ReduceResult
{
public string DocumentId { get; set; }
public string FieldId { get; set; }
public int ValidatedCount { get; set; }
public int TotalCount { get; set; }
}
public WorkItems_ValidationStatistics()
{
Map = workItems =>
from workItem in workItems
select new
{
workItem.DocumentId,
workItem.FieldId,
ValidatedCount = workItem.IsValidated ? 1 : 0,
TotalCount = 1
};
Reduce = results =>
from result in results
group result by new { result.FieldId, result.DocumentId }
into g
select new
{
g.Key.DocumentId,
g.Key.FieldId,
ValidatedCount = g.Sum(x => x.ValidatedCount),
TotalCount = g.Sum(x => x.TotalCount)
};
}
}
public void CreateIndex()
{
using (var store = CreateDocumentStore())
{
IndexCreation.CreateIndexes(
typeof(WorkItems_ValidationStatistics).Assembly, store);
}
}
注意:或者,您可以直接在RavenDB Management Studio中创建索引
public void GetWorkItemStatisticsGroupedByDocumentId()
{
using (var store = CreateDocumentStore())
using (var documentSession = store.OpenSession())
{
var userFields = new[] { "fields/11", "fields/13" };
var items = documentSession
.Query<WorkItems_ValidationStatistics.ReduceResult, WorkItems_ValidationStatistics>()
.Where(x => x.FieldId.In(userFields))
.ToList();
var results = items
.GroupBy(x => x.DocumentId)
.Select(g => new
{
DocumentId = g.Key,
ValidatedCount = g.Sum(x => x.ValidatedCount),
TotalCount = g.Sum(x => x.TotalCount)
});
foreach (var r in results)
{
Console.WriteLine("DocId={0}: validated: {1}/{2}",
r.DocumentId, r.ValidatedCount, r.TotalCount);
}
}
}