在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
相关产品推荐
相关产品推荐

