You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

在RavenDB中建模位置历史:获取指定时间点的实体状态

RavenDB 建模与查询方案:获取所有Item指定时间点的最新位置

一、推荐建模方式:独立文档存储位置变更

优先选择每个位置变更事件作为独立文档存储,这是最契合RavenDB写优化特性的方案,尤其适合Item位置频繁变更的场景:

文档结构示例(JSON):

{
  "ItemId": "item-001",
  "DateTime": "2024-05-20T14:30:00",
  "Location": "Storage1",
  "Type": "ItemLocationChange"
}
  • 优势:每次位置变更只需新增文档,无需修改旧数据,完全避免并发更新冲突,写入效率极高。
  • 劣势:查询时需要聚合处理,但可以通过预建索引解决。

如果Item位置变更频率极低(比如每周才变一次),也可以选择单文档存储Item的完整位置历史,但这种方案在高并发写入时容易出现冲突,仅适合低变更场景:

{
  "Id": "items/item-001",
  "LocationHistory": [
    { "DateTime": "2024-05-20T14:30:00", "Location": "Storage1" },
    { "DateTime": "2024-05-20T16:45:00", "Location": "Building2" }
  ]
}

二、最佳查询方案:Map-Reduce索引预计算

针对独立文档的建模方式,创建Map-Reduce索引预计算每个Item的最新位置记录,是性能最优的查询方式:

1. 创建Map-Reduce索引

用C#定义索引(其他客户端语法逻辑一致):

public class ItemLatestLocationIndex : AbstractIndexCreationTask<ItemLocationChange, ItemLatestLocationResult>
{
    public class ItemLatestLocationResult
    {
        public string ItemId { get; set; }
        public DateTime DateTime { get; set; }
        public string Location { get; set; }
    }

    public ItemLatestLocationIndex()
    {
        // Map阶段:提取所有位置变更的核心字段
        Map = changes => from change in changes
                         select new
                         {
                             change.ItemId,
                             change.DateTime,
                             change.Location
                         };

        // Reduce阶段:按ItemId分组,取每组内时间最新的记录
        Reduce = results => from result in results
                            group result by result.ItemId into itemGroup
                            let latestChange = itemGroup.OrderByDescending(x => x.DateTime).First()
                            select new
                            {
                                ItemId = itemGroup.Key,
                                latestChange.DateTime,
                                latestChange.Location
                            };
    }
}

这个索引会后台自动维护,实时更新每个Item的最新位置记录。

2. 执行时间过滤查询

直接查询索引结果,过滤出时间早于N的记录即可:

var cutoffDateTime = new DateTime(2024, 5, 20, 17, 0, 0);
var latestLocations = session
    .Query<ItemLatestLocationIndex.Result, ItemLatestLocationIndex>()
    .Where(x => x.DateTime < cutoffDateTime)
    .ToList();

三、按日期查询的特殊处理

如果需要按自然日查询(比如获取某天所有Item的最新位置),且要包含当天无变更的Item,需要结合Item主文档做左连接:

假设你有存储Item基本信息的Item集合,查询语句如下:

var targetDate = new DateOnly(2024, 5, 20);
var dailyLocations = (from item in session.Query<Item>()
                      join change in session.Query<ItemLocationChange>()
                          on item.Id equals change.ItemId into changeGroup
                      from latestDailyChange in changeGroup
                          .Where(c => c.DateTime.Date == targetDate)
                          .OrderByDescending(c => c.DateTime)
                          .Take(1)
                          .DefaultIfEmpty()
                      select new
                      {
                          ItemId = item.Id,
                          Location = latestDailyChange?.Location ?? "未变更(沿用之前位置)"
                      }).ToList();

如果没有Item主文档,无法直接获取“无变更的Item”,因为RavenDB无法枚举所有曾经存在过的ItemId(除非你在索引中保留所有历史ItemId,但会占用额外存储)。

四、备选方案:时序集合

如果你的位置变更数据是高频率时间序列(比如每分钟上报一次),可以使用RavenDB的时序集合,它专门优化了时间序列数据的存储和查询,但灵活性不如普通文档+Map-Reduce方案,适合纯时间序列场景。

内容的提问来源于stack exchange,提问作者Shaddix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 16:27:48