8

私は次のものを持っているとしましょう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真に等しい
  • IsUnavailableに等しい
  • Id除外された 1 人のユーザー (検索を行っているユーザー) とは異なります。

また、結果が次の基準に完全または部分的に一致するようにしたいのですが、検索語が提供された場合にのみ、それ以外の場合は制約を無視したいと考えています。

  • JobRole= []
  • ExpertiseAreas[ value-1value-2value-n ]の項目が含まれます
  • OrganisationalAreas[ value-1value-2value-n ]の項目が含まれます

このクエリから返されるユーザーのリストは、すべてが同じように基準に一致するとは限りません。いくつかは、他のものよりも一致します。そのため、結果がどれだけ一致するかによって結果を並べ替えたいと思います。

結果を表示するときに、ユーザーが検索にどれだけ一致したかを示す星の評価 (1 ~ 5) を各結果に付けたいと考えています。

私はこれを行う方法を考え出すのに数日を費やしました。だから私は今、私自身の質問に答えて、うまくいけばあなたの努力を節約します. もちろん、答えは完璧ではないので、改善できる場合はそうしてください。

4

1 に答える 1

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 クエリ文字列ジェネレーターを作成していないことに注意してください。実際、私はこれを書きました。それは素晴らしいことでしたが、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最後に、新しいウィジェットを使用して、クエリ統計とともに結果のリストを返します。私のように以前に使用したことがない場合は、paramsTupleを使用せずにメソッドから複数の値を返す簡単な方法であることがわかります。outそれでおしまい。したがって、メソッドの戻り値の型を定義してから、次のように「Tuple.Create()」を使用します。

public static Tuple<List<User>, RavenQueryStatistics> FindMentors(...)
{
    ...
    return Tuple.Create(mentors, stats);
}

以上がクエリです。

しかし、私が言及したそのクールな星評価はどうですか? 私は棒に月を求めるタイプのコーダーなので、 ratyと呼ばれる素敵な jQuery プラグインを使用しました。HTML5 + カミソリ + 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>

そして、それは本当にそれです。噛むことがたくさんあり、改善することがたくさんあります。より良い/より効率的な方法があると思われる場合は、遠慮しないでください。

以下は、いくつかのテスト データのスクリーン ショットです。

ここに画像の説明を入力

于 2012-11-07T13:40:56.140 に答える