如何实现多映射索引的连接与投影以关联多集合数据?
解决方案:基于RavenDB多Map-Reduce索引实现系统通知关联查询
针对你的需求,我们可以通过统一中间结构的多Map-Reduce索引解决映射与输出结构不一致的问题,同时支持分页查询,无需多步操作或Include。
1. 定义索引输出结构
先创建索引的中间输出类,统一承载实体和通知的聚合信息:
public class SystemNotificationsIndexResult { public string SystemId { get; set; } public string EntityId { get; set; } public string EntityName { get; set; } public List<NotificationEntry> Notifications { get; set; } = new(); public class NotificationEntry { public string NotificationId { get; set; } public string Message { get; set; } } }
2. 实现多Map-Reduce索引
通过AbstractMultiMapIndexCreationTask分别映射三个集合,再通过Reduce聚合数据:
public class SystemNotificationsIndex : AbstractMultiMapIndexCreationTask<SystemNotificationsIndexResult> { public SystemNotificationsIndex() { // 映射Components集合:输出实体基本信息,通知列表为空 AddMap<Component>(components => from component in components select new SystemNotificationsIndexResult { SystemId = component.SystemId, EntityId = component.Id, EntityName = component.Name, Notifications = new List<NotificationEntry>() }); // 映射Resources集合:同Components逻辑 AddMap<Resource>(resources => from resource in resources select new SystemNotificationsIndexResult { SystemId = resource.SystemId, EntityId = resource.Id, EntityName = resource.Name, Notifications = new List<NotificationEntry>() }); // 映射Notifications集合:通过RecipientID加载关联实体的SystemId,输出通知信息 AddMap<Notification>(notifications => from notification in notifications let recipient = LoadDocument<dynamic>(notification.RecipientID) where recipient != null // 过滤无效的RecipientID select new SystemNotificationsIndexResult { SystemId = recipient.SystemId, EntityId = notification.RecipientID, EntityName = null, // 留空,Reduce阶段合并实体名称 Notifications = new List<NotificationEntry> { new NotificationEntry { NotificationId = notification.Id, Message = notification.Message } } }); // Reduce逻辑:按SystemId+EntityId分组,合并实体名称和通知列表 Reduce = results => from result in results group result by new { result.SystemId, result.EntityId } into g select new SystemNotificationsIndexResult { SystemId = g.Key.SystemId, EntityId = g.Key.EntityId, // 取来自Component/Resource的非空实体名称 EntityName = g.FirstOrDefault(x => !string.IsNullOrEmpty(x.EntityName))?.EntityName, // 合并所有关联的通知 Notifications = g.SelectMany(x => x.Notifications).ToList() }; // 为SystemId创建索引,加速查询过滤 Index(x => x.SystemId, FieldIndexing.Analyzed); } }
3. 查询索引并投影结果
直接查询索引,按SystemId过滤并支持分页,最后投影成你需要的返回结构:
// 定义返回结构的DTO public class SystemNotificationsResponse { public string System { get; set; } public List<EntityNotificationGroup> Notifications { get; set; } = new(); } public class EntityNotificationGroup { public string Entity { get; set; } public string EntityName { get; set; } public List<NotificationDto> Notifications { get; set; } = new(); } public class NotificationDto { public string Id { get; set; } public string Message { get; set; } } // 查询方法 public async Task<SystemNotificationsResponse> GetSystemNotificationsAsync(string systemId, int pageSize, int pageNumber) { using var session = DocumentStoreHolder.Store.OpenAsyncSession(); var indexedResults = await session.Query<SystemNotificationsIndexResult, SystemNotificationsIndex>() .Where(x => x.SystemId == systemId) .Skip((pageNumber - 1) * pageSize) .Take(pageSize) .ToListAsync(); return new SystemNotificationsResponse { System = systemId, Notifications = indexedResults.Select(r => new EntityNotificationGroup { Entity = r.EntityId, EntityName = r.EntityName, Notifications = r.Notifications.Select(n => new NotificationDto { Id = n.NotificationId, Message = n.Message }).ToList() }).ToList() }; }
关键说明
- Map阶段的关联加载:在Notifications的Map中使用
LoadDocument获取关联实体的SystemId,这是RavenDB支持的索引内关联,不会产生额外查询开销。 - Reduce的聚合逻辑:通过
SystemId+EntityId分组,确保每个实体只聚合一次,同时合并来自Component/Resource的名称和所有关联通知。 - 分页支持:直接在索引查询中使用
Skip/Take,一次查询即可拿到分页后的聚合数据,无需多步操作。 - 数据一致性:索引会自动监听三个集合的文档变化,实时更新聚合结果,保证数据最新。
内容的提问来源于stack exchange,提问作者Sinaesthetic
相关产品推荐
相关产品推荐

