1

如何为以下关系模式构建 NoSQL 模型和索引(最好是 RavenDb v4)?

文档类型Contact,其中每条记录可以有多个附加属性(属性的类型在 中定义,CustomField值在 中ContactCustomField在此处输入图像描述

考虑到需要在一个查询中对突出显示的字段进行过滤/排序(联系人中的所有字段加上自定义字段)。


我看到的可能选项:

选项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 的好处。

4

2 回答 2

2

更新答案(2018 年 6 月 29 日)

成功的关键在于 Raven 的一项被低估的功能 -具有动态字段的索引。它允许保持逻辑数据结构并避免创建扇出索引

使用方法是像上面选项 #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; }
}

whereContact.CustomFields.Key是对该自定义字段的引用CustonField.IdContact.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.

明白了

如果您在 C# 中使用通常的 Query 对象(IRavenQueryable类型)而不是RQLor来编写查询,则会有一个很大的问题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

这是无证的,是我与 Oren Eini(又名 Ayende Rahien)的讨论,您可以在其中了解有关该主题的更多信息。

PS 我的一般建议是通过DocumentQuery而不是通常的Query链接)与 Raven 进行交互,因为 LINQ 集成仍然非常薄弱,开发人员可能会不断地在这里和那里发现错误。


初步答复(2018 年 6 月 9 日)

正如 Oren Eini (aka Ayende Rahien) 所建议的那样,要走的路是选项 #2 -ContactCustomField在查询中包括一个单独的集合。

因此,尽管使用了 NoSQL 数据库,但关系方法是唯一可行的方法。

于 2018-06-09T11:43:47.003 回答
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}"
}

这将加载联系人,然后加载关系文档的数据。

我没有测试过这个确切的例子,但它是基于我在自己的项目中实现的一个工作示例。您可能需要进行一些调整。

您当然需要对其进行调整以包含许多文档,但它应该让您对如何使用关系有一个基本的了解。

您还应该检查文档关系的文档。

我希望这有帮助。

于 2018-06-11T01:21:22.053 回答