简单的答案是您不想这样做。文档数据库的重点是文档代表整个聚合根实体(在领域驱动设计术语中)。人们会期望在您描述的文档中,所有“孩子”都与根“父母”相关。
对于现实世界的示例,请考虑具有 LineItems 的 SalesOrder。从订单中只检索一行是否有意义?可能不是。您将检索整个 SalesOrder - 包括所有 LineItems。
这是一个需要理解的重要概念。在 RDBMS(如 SQL Server)中,您需要两个单独的表来存储订单及其行项目。要检索订单,您必须从 SalesOrders 表中读取 1 条记录,并从 LineItems 表中读取 N 条记录。这种反模式通常被称为“Select N+1”,它会导致重大的可伸缩性问题,例如,在批量检索许多订单时。
所以最好的答案是,根本不要查询。做一个简单的session.Load<Parent>("parents/1")
,你将拥有所有的孩子数据,只需一次调用数据库。从而避免 Select N+1 问题。
...
现在是更复杂的答案 - 是的,您可以只取回文档的一部分。通常,您不会为单个文档执行此操作。您可能希望针对具有特定条件的多个文档进行查询。
例如,假设您要回答查询“给我所有具有特定日期的孩子”。这将是一个带有索引投影的查询。
默认情况下,Raven 旨在返回整个文档,而不是部分文档。换句话说,我们通常会回答相关查询“给我所有有特定日期的孩子的父母”。该查询如下所示:
var parents = session.Query<Parent>()
.Where(p=> p.Children.Any(c=> c.Date == theDate))
.ToList();
您可以获取此查询的结果并在客户端使用 linq-to-objects 过滤掉您想要的子项(在上述ToList
调用之后)
var children = parents.SelectMany(p=> p.Children.Where(c=> c.Date == theDate));
这可行,但效率不高,因为您丢弃了从数据库返回的大量数据。
另一种方法是使用带有投影的静态索引在一次调用中执行此操作。一、索引定义:
public class ChildrenIndex : AbstractIndexCreationTask<Parent>
{
public ChildrenIndex()
{
Map = parents => from parent in parents
from child in parent.Children
select new
{
child.Date,
child.Name
};
StoreAllFields(FieldStorage.Yes);
}
}
然后查询:
session.Query<Parent.Child, ChildrenIndex>()
.Where(x => x.Date == theDate)
.AsProjection<Parent.Child>();
请注意,Parent.Child
语法只是因为您有Child
一个嵌套类Parent
.
在索引中,我们映射了我们要投影的字段,并存储这些字段以便可以检索它们。我使用了StoreAllFields
速记语法,但您也可以只用 . 单独标记每个字段Store("FieldName", FieldStorage.Yes)
。
在查询中,我第一次指定Parent.Child
是这样我就可以在Where
谓词中通过它进行查询。留在这里,结果仍然是包含Parent
. 所以我们AsProjection
用来告诉 raven 我们真的想投影我们存储的字段,而不是返回与索引词匹配的文档。
...
现在希望你能看到这对于只从一个父母那里返回一个孩子来说是多余的,你应该为此加载整个父母。
如果您发现自己加载了很多特定的孩子,则可能是您上下文中的孩子确实是它自己的集合,应该在它自己的文档中。您仍然可以通过ParentId
在子文档上存储 a 或ChildrenIds
在父文档上存储列表,或两者兼而有之,来关联父子文档。