说我有以下User
public class User
{
// ... lots of other stuff
public string Id{ get; set; }
public double Relevance { get; set; }
public bool IsMentor { get; set; }
public string JobRole { get; set; }
public bool IsUnavailable { get; set; }
public List<string> ExpertiseAreas { get; set; }
public List<string> OrganisationalAreas { get; set; }
}
现在我想执行搜索,找到完全符合以下条件的所有用户:
IsMentor
等于 true IsUnavailable
等于 false Id
不等于单个被排除的用户(执行此操作的用户)
搜索)我还希望结果完全或部分匹配以下条件,但仅在提供搜索词的情况下,否则我希望忽略约束。
JobRole
= [ value ] ExpertiseAreas
包含[ value-1 , value-2 , value-n ]中的项目OrganisationalAreas
包含[ value-1 , value-2 中的项目,
值正 从此查询返回的用户列表可能并非都符合条件。有些人会比其他人更好。所以我想根据它们的匹配程度来订购我的结果。
当我显示结果时,我希望每个结果都有一个星级(1-5),表示用户与搜索匹配的程度。
我花了几天时间研究如何做到这一点。所以我现在回答我自己的问题,希望能为你节省一些力气。答案当然不是完美的,所以,如果你能改进它,请这样做。
答案 0 :(得分:17)
首先,我需要一个包含我将要搜索的所有字段的RavenDB索引。这很容易。
<强>索引强>
public class User_FindMentor : AbstractIndexCreationTask<User>
{
public User_FindMentor()
{
Map = users => users.Select(user => new
{
user.Id,
user.IsUnavailable,
user.IsMentor,
user.OrganisationalAreas,
user.ExpertiseAreas,
user.JobRole
});
}
}
接下来我需要一个服务方法来执行查询。这就是所有魔法发生的地方。
搜索服务
public static Tuple<List<User>, RavenQueryStatistics> FindMentors(
IDocumentSession db,
string excludedUserId = null,
string expertiseAreas = null,
string jobRoles = null,
string organisationalAreas = null,
int take = 50)
{
RavenQueryStatistics stats;
var query = db
.Advanced
.LuceneQuery<User, RavenIndexes.User_FindMentor>()
.Statistics(out stats)
.Take(take)
.WhereEquals("IsMentor", true).AndAlso()
.WhereEquals("IsUnavailable", false).AndAlso()
.Not.WhereEquals("Id", excludedUserId);
if (expertiseAreas.HasValue())
query = query
.AndAlso()
.WhereIn("ExpertiseAreas", expertiseAreas.SafeSplit());
if (jobRoles.HasValue())
query = query
.AndAlso()
.WhereIn("JobRole", jobRoles.SafeSplit());
if (organisationalAreas.HasValue())
query = query
.AndAlso()
.WhereIn("OrganisationalAreas", organisationalAreas.SafeSplit());
var mentors = query.ToList();
if (mentors.Count > 0)
{
var max = db.GetRelevance(mentors[0]);
mentors.ForEach(mentor =>
mentor.Relevance = Math.Floor((db.GetRelevance(mentor)/max)*5));
}
return Tuple.Create(mentors, stats);
}
请注意,在下面的代码段中,我不编写了我自己的Lucene Query字符串生成器。事实上,我确实写了这个,这很美,但后来我发现RavenDB有一个很多更好的流畅界面来构建动态查询。因此,请保存您的眼泪,并从一开始就使用本机查询界面。
RavenQueryStatistics stats;
var query = db
.Advanced
.LuceneQuery<User, RavenIndexes.User_FindMentor>()
.Statistics(out stats)
.Take(take)
.WhereEquals("IsMentor", true).AndAlso()
.WhereEquals("IsUnavailable", false).AndAlso()
.Not.WhereEquals("Id", excludedUserId);
接下来,您可以看到我正在检查搜索是否已经过了查询条件元素的任何值,例如:
if (expertiseAreas.HasValue())
query = query
.AndAlso()
.WhereIn("ExpertiseAreas", expertiseAreas.SafeSplit());
这使用了一些我认为通常有用的扩展方法:
public static bool HasValue(this string candidate)
{
return !string.IsNullOrEmpty(candidate);
}
public static bool IsEmpty(this string candidate)
{
return string.IsNullOrEmpty(candidate);
}
public static string[] SafeSplit(this string commaDelimited)
{
return commaDelimited.IsEmpty() ? new string[] { } : commaDelimited.Split(',');
}
然后我们得到了每个结果的Relevance
。请记住,我希望我的结果显示1到5颗星,因此我希望我的相关性值在此范围内进行标准化。为此,我必须找出最大相关性,在这种情况下是列表中第一个用户的值。这是因为如果您没有指定排序顺序,Raven会自动神奇地按相关性对结果进行排序 - 非常方便。
if (mentors.Count > 0)
{
var max = db.GetRelevance(mentors[0]);
mentors.ForEach(mentor =>
mentor.Relevance = Math.Floor((db.GetRelevance(mentor)/max)*5));
}
提取相关性依赖于另一种扩展方法,该方法从ravendb文档的元数据中提取lucene得分,如下所示:
public static double GetRelevance<T>(this IDocumentSession db, T candidate)
{
return db
.Advanced
.GetMetadataFor(candidate)
.Value<double>("Temp-Index-Score");
}
最后,我们使用新的Tuple
小部件返回结果列表以及查询统计信息。如果你和我一样,之前没有使用Tuple
,那么结果是一种简单的方法,可以在不使用out
参数的情况下从方法中发回多个值。而已。因此,定义您的方法返回类型,然后使用'Tuple.Create()',如下所示:
public static Tuple<List<User>, RavenQueryStatistics> FindMentors(...)
{
...
return Tuple.Create(mentors, stats);
}
这就是查询。
但是我提到的这个很酷的星级呢?好吧,因为我是那种想要棒上月球的编码器,我使用了一个名为raty的好jQuery插件,对我来说效果很好。这里有一些HTML5 + razor + jQuery给你的想法:
<div id="find-mentor-results">
@foreach (User user in Model.Results)
{
...stuff
<div class="row">
<img id="headshot" src="@user.Headshot" alt="headshot"/>
<h5>@user.DisplayName</h5>
<div class="star-rating" data-relevance="@user.Relevance"></div>
</div>
...stuff
}
</div>
<script>
$(function () {
$('.star-rating').raty({
readOnly: true,
score: function () {
return $(this).attr('data-relevance');
}
});
});
</script>
真的就是这样。有很多可以咀嚼,有很多需要改进的地方。如果您认为有更好/更有效的方式,请不要退缩。
以下是一些测试数据的屏幕截图: