まず、検索するすべてのフィールドを含む 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>
そして、それは本当にそれです。噛むことがたくさんあり、改善することがたくさんあります。より良い/より効率的な方法があると思われる場合は、遠慮しないでください。
以下は、いくつかのテスト データのスクリーン ショットです。
