如何为以下关系模式构建NoSQL模型和索引(最好是RavenDb v4)?
文档类型
Contact
,其中每条记录可以有多个附加属性(属性的类型在CustomField
中定义,值在ContactCustomField
中)
考虑到需要对一个查询中突出显示的字段进行过滤/排序(来自Contact plus自定义字段的所有字段)。
我看到可能的选项:
选项#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
另一种方法是保持标准化结构(如上图所示)。然后它会工作 - 我只需要在ContactCustomField
的查询中加入Contact
。
缺点是没有利用NoSQL的好处。
答案 0 :(得分:1)
更新回答(2018年6月29日)
成功的关键在于一个被低估的Raven的特征 - Indexes with Dynamic Fields。它允许保持逻辑数据结构并避免创建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
存储该自定义字段的值。
为了过滤/搜索自定义字段,我们需要以下索引:
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
的普通属性。
<强>疑难杂症强>
如果使用通常的Query对象(IRavenQueryable
类型)而不是RQL
或DocumentQuery
在C#中编写查询,则会有很大的问题。这就是我们命名动态字段的方式 - 它是特定格式的复合名称: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数据库,关系方法是唯一的方法。
答案 1 :(得分:0)
为此你可能想要使用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的文档。
我希望这会有所帮助。