【问题标题】:Many-to-many relationship RavenDb: Document structure and index多对多关系 RavenDb:文档结构和索引
【发布时间】:2018-06-09 11:33:48
【问题描述】:

如何为以下关系模式构建 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

另一种方法是保持标准化结构(如上图所示)。然后它就可以工作了——我只需要在Contact的查询中包含ContactCustomField

缺点是没有利用 NoSQL 的好处。

【问题讨论】:

    标签: c# indexing nosql ravendb ravendb4


    【解决方案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 的普通属性。

    遇到问题

    如果您在 C# 中使用通常的 Query 对象(IRavenQueryable 类型)而不是 RQLDocumentQuery 编写查询,则会有一个很大的问题。这是我们命名动态字段的方式 - 它是特定格式的复合名称: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)的讨论,您可以在其中了解有关该主题的更多信息。

    附:我的一般建议是通过 DocumentQuery 而不是通常的 Query (link) 与 Raven 进行交互,因为 LINQ 集成仍然很弱,开发人员可能会不断地发现错误。


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

    因为它是 Oren Eini(又名 Ayende Rahien)的 suggested,所以要采用的方法是选项 #2 - 在查询中包括一个单独的 ContactCustomField 集合。

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

    【讨论】:

      【解决方案2】:

      为此,您可能希望使用 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 的文档。

      我希望这会有所帮助。

      【讨论】:

        猜你喜欢
        • 2015-07-04
        • 2012-11-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多