2012-11-07 16 views
8

Nói rằng tôi đã sau UserLàm cách nào để thực hiện truy vấn RavenDB phức tạp hợp lý và bao gồm Điểm Lucene trong kết quả?

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; } 
} 

Bây giờ tôi muốn thực hiện tìm kiếm sẽ tìm thấy tất cả những người sử dụng phù hợp với đầy đủ các tiêu chuẩn sau:

  • IsMentor bằng đúng
  • IsUnavailable bằng false
  • Id không bằng một, sử dụng loại trừ duy nhất (người làm việc tìm kiếm )

Tôi cũng muốn các kết quả đầy đủ hoặc một phần phù hợp với các tiêu chí sau nhưng chỉ khi điều kiện tìm kiếm được cung cấp, nếu không tôi muốn hạn chế bị bỏ qua.

  • JobRole = [giá trị]
  • ExpertiseAreas chứa các mục từ [giá trị 1, giá trị 2, giá trị n]
  • OrganisationalAreas chứa các mục từ [giá trị gia tăng 1, giá trị-2, giá trị-n]

Danh sách Người dùng được trả về từ truy vấn này có thể không đồng đều với tiêu chí. Một số sẽ phù hợp hơn những người khác. Vì vậy, tôi muốn đặt hàng kết quả của mình theo mức độ phù hợp của chúng.

Khi tôi hiển thị kết quả của mình, tôi muốn mỗi kết quả được xếp hạng theo sao (1-5) cho biết mức độ phù hợp của người dùng đối với tìm kiếm.

Tôi đã dành một vài ngày để tìm hiểu cách thực hiện việc này. Vì vậy, bây giờ tôi sẽ trả lời câu hỏi của riêng tôi và hy vọng giúp bạn tiết kiệm một số nỗ lực. Câu trả lời sẽ không được hoàn hảo tất nhiên, vì vậy xin vui lòng, nếu bạn có thể cải thiện nó, làm như vậy.

Trả lời

16

Trước tiên, tôi cần chỉ mục RavenDB bao gồm tất cả các trường tôi sẽ tìm kiếm. Điều này thật dễ dàng.

Index

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 
     }); 
    } 
} 

Tiếp theo, tôi cần một phương pháp dịch vụ để thực hiện các truy vấn. Đây là nơi tất cả các phép thuật xảy ra.

Dịch vụ tìm kiếm

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); 
} 

Lưu ý trong đoạn mã dưới đây, tôi có không viết máy phát điện chuỗi Lucene Query của riêng tôi. Tôi đã làm, trên thực tế, viết này, và nó là một thứ của cái đẹp, nhưng sau đó tôi phát hiện ra rằng RavenDB có nhiều giao diện thông thạo hơn để xây dựng các truy vấn động.Vì vậy, hãy tiết kiệm nước mắt của bạn và sử dụng giao diện truy vấn gốc từ đầu.

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); 

Tiếp theo, bạn có thể thấy rằng tôi đang kiểm tra hay không tìm kiếm đã trôi qua trong bất kỳ giá trị cho các yếu tố điều kiện của truy vấn, ví dụ:

if (expertiseAreas.HasValue()) 
    query = query 
      .AndAlso() 
      .WhereIn("ExpertiseAreas", expertiseAreas.SafeSplit()); 

này sử dụng một vài phương pháp mở rộng mà Tôi thấy hữu ích nói chung:

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(','); 
} 

Sau đó, chúng tôi có bit hoạt động trong số Relevance của mỗi kết quả. Hãy nhớ rằng tôi muốn hiển thị kết quả từ 1 đến 5 sao vì vậy tôi muốn giá trị Mức độ liên quan của tôi được chuẩn hóa trong phạm vi này. Để làm điều này tôi phải tìm ra mức độ liên quan tối đa, trong trường hợp này là giá trị của Người dùng đầu tiên trong danh sách. Điều này là do Raven tự động sắp xếp các kết quả theo mức độ liên quan nếu bạn không chỉ định thứ tự sắp xếp - rất tiện dụng.

if (mentors.Count > 0) 
{ 
    var max = db.GetRelevance(mentors[0]); 
    mentors.ForEach(mentor => 
        mentor.Relevance = Math.Floor((db.GetRelevance(mentor)/max)*5)); 
} 

Extracting sự phù hợp dựa vào thêm một phương pháp khuyến nông mà kéo số điểm Lucene từ siêu dữ liệu tài liệu ravendb của, như thế này:

public static double GetRelevance<T>(this IDocumentSession db, T candidate) 
{ 
    return db 
     .Advanced 
     .GetMetadataFor(candidate) 
     .Value<double>("Temp-Index-Score"); 
} 

Cuối cùng chúng tôi trở lại danh sách kết quả cùng với số liệu thống kê truy vấn sử dụng tiện ích Tuple mới. Nếu bạn, như tôi, đã không sử dụng Tuple trước, nó quay ra được một cách dễ dàng để gửi nhiều hơn một giá trị trở lại từ một phương pháp mà không sử dụng out params. Đó là nó. Vì vậy, hãy xác định loại trả về của phương thức và sau đó sử dụng 'Tuple.Create()', như sau:

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

Và đó là truy vấn.

Nhưng những gì về điều đó mát mẻ sao giá tôi đã đề cập? Cũng kể từ khi tôi là loại coder ai muốn mặt trăng-on-a-stick, tôi sử dụng một plugin jQuery đẹp gọi raty mà chỉ làm việc tốt cho tôi. Dưới đây là một số HTML5 + dao cạo + jQuery để cung cấp cho bạn ý tưởng:

<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> 

Và đó thực sự là nó. Rất nhiều để nhai, rất nhiều để cải thiện. Đừng giữ lại nếu bạn nghĩ rằng có một cách tốt hơn/hiệu quả hơn.

Đây là một ảnh chụp màn hình của một số dữ liệu thử nghiệm:

enter image description here

+1

Cảm ơn bạn đã đặt việc nghiên cứu này và gửi nó cho chồng. Tôi thấy nó khá hữu ích. – jamesamuir

Các vấn đề liên quan