多对多关系RavenDb:文档结构和索引

时间:2018-06-09 11:33:48

标签: c# indexing nosql ravendb ravendb4

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

  

文档类型Contact,其中每条记录可以有多个附加属性(属性的类型在CustomField中定义,值在ContactCustomField中)   enter image description here

考虑到需要对一个查询中突出显示的字段进行过滤/排序(来自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的好处。

2 个答案:

答案 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类型)而不是RQLDocumentQuery在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而不是通常的Querylink)与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的文档。

我希望这会有所帮助。