多对多关系RavenDb:文档结构和索引
Many-to-many relationship RavenDb: Document structure and index
如何为以下关系模式构建 NoSQL 模型和索引(最好是 RavenDb v4)?
Document type Contact
, where each record can have multiple additional properties (type of the property is defined in CustomField
and the value in ContactCustomField
)
考虑需要 filter/sort 在一个查询中突出显示的字段(联系人中的所有字段加上自定义字段)。
我认为可能的选择:
选项 #1
自然地,我会想象以下持久模型:
public class Contact
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
// Where the key is CustomField.Id and the value is ContactCustomField.Value
public Dictionary<string, string> CustomValues { get; set; }
}
public class CustomField
{
public string Id { get; set; }
public string Code { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
}
但是,为如下查询构建索引(抱歉混合语法)让我感到困惑:
SELECT Name, Address, Phone, CustomValues
FROM Contact
WHERE Name LIKE '*John*' AND CustomValues.Any(v => v.Key == "11" && v.Value == "student")
选项 #2
另一种方法是保持标准化结构(如上图所示)。然后它会起作用 - 我只需要在 Contact
.
的查询中包含 ContactCustomField
缺点是没有利用 NoSQL 的好处。
更新答案(2018 年 6 月 29 日)
成功的关键在于 Raven 的一项被低估的功能 - Indexes with Dynamic Fields. It allows to keep logical data structure and avoids creating a fanout index。
使用方法是像上面选项 #1 中描述的那样构建集合:
public class Contact
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public Dictionary<string, object> CustomFields { get; set; }
}
public class CustomField
{
public string Id { get; set; }
public string Code { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
}
其中 Contact.CustomFields.Key
是对 CustonField.Id
的引用,Contact.CustomFields.Value
存储该自定义字段的值。
为了在自定义字段上filter/search,我们需要以下索引:
public class MyIndex : AbstractIndexCreationTask<Contact>
{
public MyIndex()
{
Map = contacts =>
from e in contacts
select new
{
_ = e.CustomFields.Select( x => CreateField ($"{nameof(Contact.CustomFields)}_{x.Key}", x.Value))
};
}
}
该索引将涵盖字典的所有键值对,因为它们是 Contact
.
的普通属性
陷阱
如果您在 C# 中使用常用的查询对象(IRavenQueryable
类型)而不是 RQL
或 DocumentQuery
编写查询,则会遇到很大的问题。这是我们命名动态字段的方式 - 它是特定格式的复合名称:dictionary_name + underscore + key_name
。它允许我们构建像
这样的查询
var q = s.Query<Person, MyIndex>()
.Where(p => p.CustomFields["Age"].Equals(4));
在幕后转换为 RQL:
from index 'MyIndex' where CustomFields_Age = $p1
它没有记录,here 是我与 Oren Eini(又名 Ayende Rahien)的讨论,您可以在其中了解有关该主题的更多信息。
P.S。我的一般建议是通过 DocumentQuery
而不是通常的 Query
(link) 与 Raven 交互,因为 LINQ 集成仍然很薄弱,开发人员可能会不断地发现错误。
初步回答(2018 年 6 月 9 日)
由于 Oren Eini(又名 Ayende Rahien)suggested,方法是选项 #2 - 在查询中包含一个单独的 ContactCustomField
集合。
所以尽管使用了 NoSQL 数据库,关系方法是唯一的方法。
为此,您可能希望使用 Map-Reduced 索引。
地图:
docs.Contacts.SelectMany(doc => (doc, next) => new{
// Contact Fields
doc.Id,
doc.Name,
doc.Address,
doc.Phone,
doc.CustomFieldLoaded = LoadDocument<string>(doc.CustomValueField, "CustomFieldLoaded"),
doc.CustomValues
});
减少:
from result in results
group result by {result.Id, result.Name, result.Address, result.Phone, result.CustomValues, result.CustomFieldLoaded} into g
select new{
g.Key.Id,
g.Key.Name,
g.Key.Address,
g.Key.Phone,
g.Key.CustomFieldLoaded = new {},
g.Key.CustomValues = g.CustomValues.Select(c=> g.Key.CustomFieldLoaded[g.Key.CustomValues.IndexOf(c)])
}
您的文档将如下所示:
{
"Name": "John Doe",
"Address": "1234 Elm St",
"Phone": "000-000-0000",
CustomValues: "{COLLECTION}/{DOCUMENTID}"
}
这将加载联系人,然后加载关系文档的数据。
我没有测试过这个确切的示例,但它基于我在自己的项目中实现的一个工作示例。您可能需要做一些调整。
您当然需要调整它以包含许多文档,但它应该让您对如何使用关系有一个基本的了解。
您还应该查看 document relationships 的文档。
希望对您有所帮助。
如何为以下关系模式构建 NoSQL 模型和索引(最好是 RavenDb v4)?
Document type
Contact
, where each record can have multiple additional properties (type of the property is defined inCustomField
and the value inContactCustomField
)
考虑需要 filter/sort 在一个查询中突出显示的字段(联系人中的所有字段加上自定义字段)。
我认为可能的选择:
选项 #1
自然地,我会想象以下持久模型:
public class Contact
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
// Where the key is CustomField.Id and the value is ContactCustomField.Value
public Dictionary<string, string> CustomValues { get; set; }
}
public class CustomField
{
public string Id { get; set; }
public string Code { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
}
但是,为如下查询构建索引(抱歉混合语法)让我感到困惑:
SELECT Name, Address, Phone, CustomValues
FROM Contact
WHERE Name LIKE '*John*' AND CustomValues.Any(v => v.Key == "11" && v.Value == "student")
选项 #2
另一种方法是保持标准化结构(如上图所示)。然后它会起作用 - 我只需要在 Contact
.
ContactCustomField
缺点是没有利用 NoSQL 的好处。
更新答案(2018 年 6 月 29 日)
成功的关键在于 Raven 的一项被低估的功能 - Indexes with Dynamic Fields. It allows to keep logical data structure and avoids creating a fanout index。
使用方法是像上面选项 #1 中描述的那样构建集合:
public class Contact
{
public string Id { get; set; }
public string Name { get; set; }
public string Address { get; set; }
public string Phone { get; set; }
public Dictionary<string, object> CustomFields { get; set; }
}
public class CustomField
{
public string Id { get; set; }
public string Code { get; set; }
public string DataType { get; set; }
public string Description { get; set; }
}
其中 Contact.CustomFields.Key
是对 CustonField.Id
的引用,Contact.CustomFields.Value
存储该自定义字段的值。
为了在自定义字段上filter/search,我们需要以下索引:
public class MyIndex : AbstractIndexCreationTask<Contact>
{
public MyIndex()
{
Map = contacts =>
from e in contacts
select new
{
_ = e.CustomFields.Select( x => CreateField ($"{nameof(Contact.CustomFields)}_{x.Key}", x.Value))
};
}
}
该索引将涵盖字典的所有键值对,因为它们是 Contact
.
陷阱
如果您在 C# 中使用常用的查询对象(IRavenQueryable
类型)而不是 RQL
或 DocumentQuery
编写查询,则会遇到很大的问题。这是我们命名动态字段的方式 - 它是特定格式的复合名称:dictionary_name + underscore + key_name
。它允许我们构建像
var q = s.Query<Person, MyIndex>()
.Where(p => p.CustomFields["Age"].Equals(4));
在幕后转换为 RQL:
from index 'MyIndex' where CustomFields_Age = $p1
它没有记录,here 是我与 Oren Eini(又名 Ayende Rahien)的讨论,您可以在其中了解有关该主题的更多信息。
P.S。我的一般建议是通过 DocumentQuery
而不是通常的 Query
(link) 与 Raven 交互,因为 LINQ 集成仍然很薄弱,开发人员可能会不断地发现错误。
初步回答(2018 年 6 月 9 日)
由于 Oren Eini(又名 Ayende Rahien)suggested,方法是选项 #2 - 在查询中包含一个单独的 ContactCustomField
集合。
所以尽管使用了 NoSQL 数据库,关系方法是唯一的方法。
为此,您可能希望使用 Map-Reduced 索引。
地图:
docs.Contacts.SelectMany(doc => (doc, next) => new{
// Contact Fields
doc.Id,
doc.Name,
doc.Address,
doc.Phone,
doc.CustomFieldLoaded = LoadDocument<string>(doc.CustomValueField, "CustomFieldLoaded"),
doc.CustomValues
});
减少:
from result in results
group result by {result.Id, result.Name, result.Address, result.Phone, result.CustomValues, result.CustomFieldLoaded} into g
select new{
g.Key.Id,
g.Key.Name,
g.Key.Address,
g.Key.Phone,
g.Key.CustomFieldLoaded = new {},
g.Key.CustomValues = g.CustomValues.Select(c=> g.Key.CustomFieldLoaded[g.Key.CustomValues.IndexOf(c)])
}
您的文档将如下所示:
{
"Name": "John Doe",
"Address": "1234 Elm St",
"Phone": "000-000-0000",
CustomValues: "{COLLECTION}/{DOCUMENTID}"
}
这将加载联系人,然后加载关系文档的数据。
我没有测试过这个确切的示例,但它基于我在自己的项目中实现的一个工作示例。您可能需要做一些调整。
您当然需要调整它以包含许多文档,但它应该让您对如何使用关系有一个基本的了解。
您还应该查看 document relationships 的文档。
希望对您有所帮助。