3

我对 RavenDB 很陌生,正在努力寻找以下解决方案:

我有一个名为 ServiceCalls 的集合,如下所示:

public class ServiceCall
    {
        public int ID { get; set; }
        public string IncidentNumber { get; set; }
        public string Category { get; set; }
        public string SubCategory { get; set; }
        public DateTime ReportedDateTime { get; set; }
        public string Block { get; set; }
        public decimal Latitude { get; set; }
        public decimal Longitude { get; set; }
    }

我有一个名为 ServiceCalls/CallsByCategory 的索引,如下所示:

        Map = docs => from doc in docs
                      select new
                      {
                          Category = doc.Category,
                          CategoryCount = 1,
                          ServiceCalls = doc,
                      };
        Reduce = results => from result in results
                            group result by result.Category into g
                            select new
                            {
                                Category = g.Key,
                                CategoryCount = g.Count(),
                                ServiceCalls = g.Select(i => i.ServiceCalls)
                            };

所以输出是:

public class ServiceCallsByCategory
{
    public string Category { get; set; }
    public int CategoryCount { get; set; }
    public IEnumerable<ServiceCall> ServiceCalls { get; set; }
}

使用此查询一切正常

var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory") select i

我完全迷失的地方是编写一个允许我按 ReportedDateTime 查询的索引。可以让我这样做的东西:

    var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory")
            where i.ServiceCalls.Any(x=>x.ReportedDateTime >= new DateTime(2012,10,1)) 
            select i

任何指导将不胜感激。

4

2 回答 2

4

一些东西,

  1. 您的 reduce 子句中不能有.Count()方法。如果你仔细观察,你会发现你的计数是错误的。从 build 2151 开始,这实际上会引发异常。相反,你想要CategoryCount = g.Sum(x => x.CategoryCount)

  2. 您总是希望 map 的结构与 reduce 的结构相匹配。如果您要构建一个事物列表,那么您应该映射每个事物的单个元素数组,并.SelectMany()在 reduce 步骤中使用。您现在拥有它的方式仅由于可能会在某个时候修复的怪癖而起作用。

  3. 通过将结果构建为 ServiceCall 列表,您将整个文档复制到索引存储中。这不仅效率低下,而且是不必要的。您最好保留一个仅包含 id 的列表。如果您需要检索完整的文档, Raven 有一个.Include()方法可以使用。此处的主要优点是,即使您的索引结果仍然陈旧,也可以保证您获得返回的每个项目的最新数据。

将所有三个放在一起,正确的索引将是:

public class ServiceCallsByCategory
{
    public string Category { get; set; }
    public int CategoryCount { get; set; }
    public int[] ServiceCallIds { get; set; }
}

public class ServiceCalls_CallsByCategory : AbstractIndexCreationTask<ServiceCall, ServiceCallsByCategory>
{
    public ServiceCalls_CallsByCategory()
    {
        Map = docs => from doc in docs
                      select new {
                                     Category = doc.Category,
                                     CategoryCount = 1,
                                     ServiceCallIds = new[] { doc.ID },
                                 };
        Reduce = results => from result in results
                            group result by result.Category
                            into g
                            select new {
                                           Category = g.Key,
                                           CategoryCount = g.Sum(x => x.CategoryCount),
                                           ServiceCallIds = g.SelectMany(i => i.ServiceCallIds)
                                       };
    }
}

使用包含查询它,如下所示:

var q = session.Query<ServiceCallsByCategory, ServiceCalls_CallsByCategory>()
               .Include<ServiceCallsByCategory, ServiceCall>(x => x.ServiceCallIds);

当您需要一个文档时,您仍然可以加载它,session.Load<ServiceCall>(id)但 Raven 不必往返于服务器来获取它。

现在 - 这并没有解决您关于如何按日期过滤结果的问题。为此,您确实需要考虑您要完成的工作。以上所有内容都假设您真的希望一次为每个类别显示每个服务调用。大多数情况下,这不切实际,因为您想对结果进行分页。您可能甚至不想使用我上面描述的内容。我在这里做了一些宏大的假设,但大多数时候人们会按类别过滤,而不是按类别进行分组。

假设您有一个只计算类别的索引(上面的索引没有服务调用列表)。您可以使用它来显示概览屏幕。但是您不会对每个类别中的文档感兴趣,除非您单击一个并深入到详细信息屏幕。那时,您知道您所在的类别,您可以按它过滤并减少到没有静态索引的日期范围:

var q = session.Query<ServiceCall>().Where(x=> x.Category == category && x.ReportedDateTime >= datetime)

如果我错了,并且您确实需要显示所有类别的所有文档,按类别分组并按日期过滤,那么您将不得不采用一种先进的技术,就像我在另一个 StackOverflow 答案中描述的那样。如果这真的是你需要的,请在评论中告诉我,我会看看我是否可以为你写。您将需要 Raven 2.0 才能使其工作。

另外 - 要非常小心您为 ReportedDateTime 存储的内容。如果您要进行任何比较,您需要了解日历时间瞬时时间之间的区别。日历时间有一些怪癖,例如夏令时转换、时区差异等等。瞬时时间跟踪某事发生的那一刻,不管是谁在问。您可能需要瞬时时间供您使用,这意味着要么使用 UTC DateTime,要么切换到DateTimeOffset哪个可以让您在不丢失本地上下文值的情况下表示瞬时时间。

更新

我尝试尝试构建一个索引,该索引将使用我描述的技术让您在类别组中获得所有结果,但仍按日期过滤。不幸的是,这是不可能的。您必须在原始文档中将所有 ServiceCall 组合在一起并在 Map 中表达出来。如果你必须先减少,它根本就不会以同样的方式工作。因此,一旦您处于特定类别中,您真的应该考虑对 ServiceCalls 进行简单查询。

于 2012-12-07T21:30:18.483 回答
0

您能否将 ReportedDateTime 添加到 Map 并将其聚合到 Reduce 中?如果您只关心每个类别的最大值,那么这样的事情就足够了。

Map = docs => from doc in docs
                      select new
                      {
                          Category = doc.Category,
                          CategoryCount = 1,
                          ServiceCalls = doc,
                          ReportedDateTime
                      };
        Reduce = results => from result in results
                            group result by result.Category into g
                            select new
                            {
                                Category = g.Key,
                                CategoryCount = g.Sum(x => x.CategoryCount),
                                ServiceCalls = g.Select(i => i.ServiceCalls)
                                ReportedDateTime = g.Max(rdt => rdt.ReportedDateTime)
                            };

然后,您可以仅根据聚合的 ReportedDateTime 查询它:

var q = from i in session.Query<ServiceCallsByCategory>("ServiceCalls/CallsByCategory")
            where i.ReportedDateTime >= new DateTime(2012,10,1) 
            select i
于 2012-12-07T00:52:50.287 回答