嵌套查询的RavenDB索引

时间:2012-12-06 21:41:12

标签: c# linq indexing ravendb

我是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

任何指导都会受到青睐。

2 个答案:

答案 0 :(得分:4)

一些事情,

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

  2. 您始终希望地图的结构与reduce的结构相匹配。如果您要构建一个事物列表,那么您应该映射每个事物的单个元素数组,并在reduce步骤中使用.SelectMany()。你现在拥有它的方式只能起作用,因为它可能会在某个时刻得到修复。

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

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

    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)
                                           };
        }
    }
    

    使用includes查询它看起来像这样:

    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)
    

    如果我错了,您确实需要显示所有类别的所有文档,按类别分组,并按日期过滤,那么您将不得不采用类似我所描述的高级技术in this other StackOverflow answer 。如果这是真的你需要什么,请在评论中告诉我,我会看看我是否可以为你写。您将需要Raven 2.0才能使其正常工作。

    另外 - 要非常小心为ReportedDateTime存储的内容。如果您要进行任何比较,您需要了解日历时间瞬时时间之间的区别。日历时间有一些怪癖,如夏令时转换,时区差异等。瞬间记录发生事件的时刻,无论是谁在询问。您可能希望使用瞬时时间,这意味着使用UTC DateTime或切换到DateTimeOffset,这样您就可以在不丢失本地上下文值的情况下表示即时时间。

    <强>更新

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

答案 1 :(得分:0)

您可以将ReportedDateTime添加到地图并在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