RavenDB:如何从 MultiMapIndex 中正确 query/filter 嵌套值?
RavenDB: How to properly query/filter a nested value from a MultiMapIndex?
我的应用程序有一个要求,即应该能够 filter/search for Pairs
by the Number
of the related Contact
。
A Pair
总是有对存储的 Contact
的引用,但联系人的号码没有,也不会存储在引用中。所以我尝试为此创建一个自定义索引,因为 Pair
和 Contact
存储在不同的集合中。
索引的简化示例如下所示。
public class Pairs_Search : AbstractMultiMapIndexCreationTask<Pairs_Search.Result>
{
public class Result
{
public string Id { get; set; }
public string Workspace { get; set; }
public ContactResult Contact { get; set; }
public bool HasContactDetails { get; set; }
}
public class ContactResult
{
public string Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
}
public Pairs_Search()
{
AddMap<Pair>(pairs => pairs
.Select(p => new
{
p.Id,
p.Workspace,
Contact = new
{
p.Contact.Id,
p.Contact.Name,
Number = 0
},
// Mark this items as WITHOUT contact details.
HasContactDetails = false,
}
)
);
AddMap<Contact>(contacts => contacts
.Select(c => new
{
Id = (string) null,
Workspace = (string) null,
Contact = new
{
c.Id,
Name = c.DisplayName,
c.Number
},
// Mark this items as WITH contact details.
HasContactDetails = true,
}
)
);
Reduce = results => results
// First group by the contact ID. This will
// create a group with 2 or more items. One with the contact
// details, and one or more with pair details.
// They are all marked by a boolean flag 'HasContactDetails'.
.GroupBy(x => x.Contact.Id)
// We are going to enrich each item in the current group, that is
// marked as 'HasContactDetails = false', with the contact number.
// We need that so that we can filter on it later.
.Select(group =>
group
.Select(i => new
{
i.Id,
i.Workspace,
Contact = new
{
i.Contact.Id,
i.Contact.Name,
// Does the current item have the contact details?
Number = i.HasContactDetails
// Yes, in this case we use the previously set contact number.
? i.Contact.Number
// No, find the item with the contact details and grab the number.
: group.Single(x => x.HasContactDetails).Contact.Number
},
// Pass on the flag that indicates wheter or not
// this item has the contact details. We are going
// to need it later.
i.HasContactDetails
}
)
// We don't need the items with the contact details
// anymore, so filter them out.
.Where(x => !x.HasContactDetails)
)
// Flatten all the small lists to one big list.
.SelectMany(x => x);
// Mark the following fields of the result as searchable.
Index(x => x.Contact.Number, FieldIndexing.Search);
}
}
我已经设置了一个完整的示例来重现我遇到的问题。您可以找到示例 here.
创建索引工作正常。查询索引也能正常工作,因为它正确地匹配了配对和联系人,并用联系人的号码丰富了索引结果。但是当我尝试在嵌套的 Number
属性 上使用 .Where()
或 .Search()
时,它无法从索引中正确过滤结果数据集。
如您在下面的代码示例中所见(在完整示例中也可用),没有任何过滤的索引有效。
private static async Task ThisOneWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.ToListAsync();
LogResults("ThisOneWorks()", results);
}
// Output:
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Hermione Granger' with number '71'
// ThisOneWorks(): Pair 'Albus Dumbledore' with number '72'
}
过滤非嵌套值也有效(在完整示例中也可用)。如您所见,它过滤掉了具有不同工作区的那个。
private static async Task ThisOneWithWorkspaceFilterWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.ToListAsync();
LogResults("ThisOneWithWorkspaceFilterWorks()", results);
}
// Output:
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Hermione Granger' with number '71'
}
当我在 Workspace
和 Number
属性上尝试 filter/search 时,我希望得到两个与联系人哈利·波特相关的结果。但是我只是得到一个空的数据集。
private static async Task ThisOneWithWorkspaceAndNumberFilterDoesntWork()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.Where(x => x.Contact.Number == 70)
.ToListAsync();
LogResults("ThisOneWithWorkspaceAndNumberFilterDoesntWork()", results);
}
// Output:
// ThisOneWithWorkspaceAndNumberFilterDoesntWork(): EMPTY RESULTS!
}
谁能告诉我我做错了什么?任何帮助将不胜感激!
解决方法是将 ContactResult 存储在不同的集合中,
在这种情况下,这就是所谓的相关文件t,
当您创建索引时,您将“索引相关文档”
学习中的演示示例:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
该示例针对基本地图索引,但原理与多地图相同。
从索引 class 中删除 public class ContactResult
并用类似的东西定义索引:
select new Result
{
....
Number = LoadDocument<Contact>(Pair.Contact).Number
....
}
我的应用程序有一个要求,即应该能够 filter/search for Pairs
by the Number
of the related Contact
。
A Pair
总是有对存储的 Contact
的引用,但联系人的号码没有,也不会存储在引用中。所以我尝试为此创建一个自定义索引,因为 Pair
和 Contact
存储在不同的集合中。
索引的简化示例如下所示。
public class Pairs_Search : AbstractMultiMapIndexCreationTask<Pairs_Search.Result>
{
public class Result
{
public string Id { get; set; }
public string Workspace { get; set; }
public ContactResult Contact { get; set; }
public bool HasContactDetails { get; set; }
}
public class ContactResult
{
public string Id { get; set; }
public string Name { get; set; }
public int Number { get; set; }
}
public Pairs_Search()
{
AddMap<Pair>(pairs => pairs
.Select(p => new
{
p.Id,
p.Workspace,
Contact = new
{
p.Contact.Id,
p.Contact.Name,
Number = 0
},
// Mark this items as WITHOUT contact details.
HasContactDetails = false,
}
)
);
AddMap<Contact>(contacts => contacts
.Select(c => new
{
Id = (string) null,
Workspace = (string) null,
Contact = new
{
c.Id,
Name = c.DisplayName,
c.Number
},
// Mark this items as WITH contact details.
HasContactDetails = true,
}
)
);
Reduce = results => results
// First group by the contact ID. This will
// create a group with 2 or more items. One with the contact
// details, and one or more with pair details.
// They are all marked by a boolean flag 'HasContactDetails'.
.GroupBy(x => x.Contact.Id)
// We are going to enrich each item in the current group, that is
// marked as 'HasContactDetails = false', with the contact number.
// We need that so that we can filter on it later.
.Select(group =>
group
.Select(i => new
{
i.Id,
i.Workspace,
Contact = new
{
i.Contact.Id,
i.Contact.Name,
// Does the current item have the contact details?
Number = i.HasContactDetails
// Yes, in this case we use the previously set contact number.
? i.Contact.Number
// No, find the item with the contact details and grab the number.
: group.Single(x => x.HasContactDetails).Contact.Number
},
// Pass on the flag that indicates wheter or not
// this item has the contact details. We are going
// to need it later.
i.HasContactDetails
}
)
// We don't need the items with the contact details
// anymore, so filter them out.
.Where(x => !x.HasContactDetails)
)
// Flatten all the small lists to one big list.
.SelectMany(x => x);
// Mark the following fields of the result as searchable.
Index(x => x.Contact.Number, FieldIndexing.Search);
}
}
我已经设置了一个完整的示例来重现我遇到的问题。您可以找到示例 here.
创建索引工作正常。查询索引也能正常工作,因为它正确地匹配了配对和联系人,并用联系人的号码丰富了索引结果。但是当我尝试在嵌套的 Number
属性 上使用 .Where()
或 .Search()
时,它无法从索引中正确过滤结果数据集。
如您在下面的代码示例中所见(在完整示例中也可用),没有任何过滤的索引有效。
private static async Task ThisOneWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.ToListAsync();
LogResults("ThisOneWorks()", results);
}
// Output:
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWorks(): Pair 'Hermione Granger' with number '71'
// ThisOneWorks(): Pair 'Albus Dumbledore' with number '72'
}
过滤非嵌套值也有效(在完整示例中也可用)。如您所见,它过滤掉了具有不同工作区的那个。
private static async Task ThisOneWithWorkspaceFilterWorks()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.ToListAsync();
LogResults("ThisOneWithWorkspaceFilterWorks()", results);
}
// Output:
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Harry Potter' with number '70'
// ThisOneWithWorkspaceFilterWorks(): Pair 'Hermione Granger' with number '71'
}
当我在 Workspace
和 Number
属性上尝试 filter/search 时,我希望得到两个与联系人哈利·波特相关的结果。但是我只是得到一个空的数据集。
private static async Task ThisOneWithWorkspaceAndNumberFilterDoesntWork()
{
using (var session = Store.OpenAsyncSession())
{
var results = await session
.Query<Pairs_Search.Result, Pairs_Search>()
.Where(x => x.Workspace == "hogwarts")
.Where(x => x.Contact.Number == 70)
.ToListAsync();
LogResults("ThisOneWithWorkspaceAndNumberFilterDoesntWork()", results);
}
// Output:
// ThisOneWithWorkspaceAndNumberFilterDoesntWork(): EMPTY RESULTS!
}
谁能告诉我我做错了什么?任何帮助将不胜感激!
解决方法是将 ContactResult 存储在不同的集合中,
在这种情况下,这就是所谓的相关文件t,
当您创建索引时,您将“索引相关文档”
学习中的演示示例:
https://demo.ravendb.net/demos/csharp/related-documents/index-related-documents
该示例针对基本地图索引,但原理与多地图相同。
从索引 class 中删除 public class ContactResult
并用类似的东西定义索引:
select new Result
{
....
Number = LoadDocument<Contact>(Pair.Contact).Number
....
}