【问题标题】:How do I perform a reasonably complex RavenDB query and include the Lucene Score in the results?如何执行相当复杂的 RavenDB 查询并在结果中包含 Lucene 分数?
【发布时间】:2012-11-07 13:40:56
【问题描述】:

假设我有以下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 等于
  • Id 不等于单个排除用户(执行 搜索)

我还希望结果完全或部分匹配以下条件,但前提是提供了搜索词,否则我希望忽略约束。

  • JobRole = []
  • ExpertiseAreas 包含来自 [value-1value-2value-n]
  • 的项目
  • OrganisationalAreas 包含来自 [value-1value-2 的项目, 值-n]

从该查询返回的用户列表可能并不完全符合条件。有些会比其他匹配更好。所以我想根据结果的匹配程度对结果进行排序。

当我显示我的结果时,我希望给每个结果一个星级 (1-5),表明用户与搜索的匹配程度。

我花了几天时间研究如何做到这一点。因此,我现在将回答我自己的问题,并希望为您节省一些精力。答案当然不会是完美的,所以如果你能改进它,请这样做。

【问题讨论】:

    标签: jquery razor ravendb


    【解决方案1】:

    首先,我需要一个 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);
    }
    

    请注意,在下面的代码 sn-p 中,我没有编写了我自己的 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);
    }
    

    这就是查询。

    但是我提到的那个很酷的星级呢?好吧,因为我是那种想要moon-on-a-stick 的编码员,所以我使用了一个不错的jQuery 插件,名为raty,它对我来说效果很好。这是一些 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>
    

    确实如此。有很多需要咀嚼,有很多需要改进。如果您认为有更好/更有效的方法,请不要犹豫。

    这是一些测试数据的屏幕截图:

    【讨论】:

    • 感谢您对此进行研究并将其发布到堆栈中。我发现它很有用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-04-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多