【问题标题】:RavenDB index for nested query用于嵌套查询的 RavenDB 索引
【发布时间】:2012-12-06 21:41:11
【问题描述】:

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

非常感谢任何指导。

【问题讨论】:

    标签: c# linq indexing ravendb


    【解决方案1】:

    一些事情,

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

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

    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&lt;ServiceCall&gt;(id) 加载它,但 Raven 不必往返于服务器来获取它。

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

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

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

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

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

    更新

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

    【讨论】:

    • 马特,这里有一些很棒且非常有用的建议。感谢您抽出宝贵时间。为什么 SO 是一个如此伟大的社区的主要例子。我认为对我来说最重要的是我需要以不同的方法来使用我的应用程序。为了记录,我正在使用backbone.js重写这个映射应用程序secure.mankato-mn.gov/maps/Police-Service-Calls.html。我(也许是幼稚的)希望是一次完成所有事情,让骨干完成工作。我的 POC 工作得很好,但显然我必须在索引中进行过滤才能使其工作。回到绘图板。
    • “你现在拥有它的方式只是因为可能会在某个时候修复的怪癖”
    • 查看该应用程序,您需要一个调用来构建您的类别列表,并且需要一个调用来提取该类别的数据。当您单击类别时,您将回调您的服务以提取该类别的 json。
    • 更新的版本有更多的功能,但基本上这就是我要走的路线。我只是想尝试一种不同的方法。
    【解决方案2】:

    您能否将 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
    

    【讨论】:

    • 兰斯,感谢您的回复。有几件事。首先,索引不返回任何结果。我不明白为什么它不会,但在 Raven Studio 中查询它我什么也没得到,在我的应用程序中查询它我什么也没得到。其次,如果它确实按预期运行,我不知道基于聚合日期的查询会起作用。我正在寻找仅返回特定日期范围内报告的服务调用。在 linq to sql 中测试上述查询将返回最大日期,但仍会返回类别内的所有调用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-21
    相关资源
    最近更新 更多