例如说我有以下课程
public class Parent
{
public string Id { get; set; }
public string Name { get; set; }
public IList<Child> Children { get; set; }
public class Child
{
public string Name { get; set; }
public DateTime Date { get; set; }
}
}
以下文件
var document = new Parent
{
Id = "parent/1",
Name = "1",
Children = new List<Parent.Child>
{
new Parent.Child {Date = new DateTime(2013, 1, 2)},
new Parent.Child {Date = new DateTime(2013, 1, 3)},
new Parent.Child {Date = new DateTime(2013, 1, 4)},
}
};
我如何:
答案 0 :(得分:2)
简单的答案是你不想这样做。文档数据库的重点是文档代表整个聚合根实体(在域驱动设计术语中)。人们可以预期,在您所描述的文件中,所有“儿童”都与根“父母”相关。
对于一个真实世界的示例,请考虑具有LineItem的SalesOrder。从订单中只检索一行是否有意义?可能不是。您将检索整个SalesOrder - 包括所有LineItems。
这是一个需要理解的重要概念。在RDBMS(如SQL Server)中,您需要两个单独的表来存储订单及其订单项。要检索订单,您必须从SalesOrders表中读取1条记录,并从LineItems表中读取N条记录。这种反模式通常被称为“选择N + 1”,例如,当批量检索许多订单时,它会导致严重的可扩展性问题。
所以最好的答案是,根本不要查询。做一个简单的session.Load<Parent>("parents/1")
,你也可以拥有所有的子数据,只需一次调用数据库。因此避免选择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
来告诉乌鸦我们真的想要预测我们存储的字段,而不是回馈与索引术语匹配的文档。
...
现在希望你可以看到,从一个父母那里只返回一个孩子这样做有点过分了,你应该加载整个父母。
如果您发现自己经常加载特定的孩子,那么您的上下文中的孩子可能确实是自己的聚合,并且应该在自己的文档中。您仍然可以通过在子文档上存储ParentId
或在父项上存储ChildrenIds
列表或两者来关联父级和子级。