معرفی کتابخانه ی مشهور و قدرتمند Lucene.net -- قسمت چهارم
  در این مقاله به تشریح نحوه ی استفاده از کتابخانه  Lucene.net در یک وب سایت و همچنین نحوه ی انجام عملیات Paging می پردازم
   C#
   ۱۳۹۸۶
   دانلود
   مرتضی صحراگرد
   ۱۳۸۹/۴/۱۹
ارسال لینک صفحه برای دوستان ارسال لینک صفحه برای دوستان  اضافه کردن به علاقه مندیها اضافه کردن به علاقه مندیها   نسخه قابل چاپ نسخه قابل چاپ

 

مقدمه:

این مقاله، چهارمین قسمت از سلسله مقالات معرفی کتابخانه Lucene.net می باشد. برای فراگیری صحیح این مقاله حتما باید قسمت های قبلی را فراگرفته باشید.

مقدمه:

در قسمت های پیشین نحوه ی ایجاد ایندکس ها، جستجوی ساده و پیشرفته و بروز رسانی ایندکس ها را ملاحظه نموده اید. استفاده از روش های جستجویی که تاکنون معرفی شده اند در محیط هایی همچون برنامه های تحت ویندوز که معمولا یک کاربر در حال استفاده از برنامه می باشد بسیار ایده آل می باشد. اما در محیط هایی که بیش از یک نفر در حال استفاده از برنامه هستند (مانند برنامه های تحت وب) و یا به طور کلی در محیط هایی که انجام عملیات به شکل مالتی ترید (Multi Thread) می باشد، باید ملاحظاتی را در نظر گرفت.

آغاز:

قبل از شروع این مقاله و برای یادآوری، تابع جستجویی که در اولین قسمت از مقالات نوشتیم را مجددا مرور می کنیم.

private List<News> SearchIndexes(string InputText)
{
    List<News> results = new List<News>();
    Lucene.Net.Search.BooleanQuery.SetMaxClauseCount(int.MaxValue);
    IndexReader reader = IndexReader.Open(GetNewsIndexDirectory());
    IndexSearcher searcher = new IndexSearcher(reader);
    Hits hits = null;
 
    //are there any wild cards in use?
    if (InputText.Contains("*"))
    {
        WildcardQuery query = new WildcardQuery(new Term("Body", InputText));
        hits = searcher.Search(query);
    }
    //is this a multi term query?
    else if (InputText.Contains(" "))
    {
        MultiPhraseQuery query = new MultiPhraseQuery();
        foreach (string s in InputText.Split(' '))
        {
            query.Add(new Term("Body", s));
        }
        hits = searcher.Search(query);
    }
    //single term query
    else
    {
        PhraseQuery query = new PhraseQuery();
        query.Add(new Term("Body", InputText));
        hits = searcher.Search(query);
    }
 
    for (int i = 0; i < hits.Length(); i++)
    {
        Document doc = hits.Doc(i);
        News news = new News();
        news.NewsID = Convert.ToInt32(doc.GetField("NewsID").StringValue());
        news.Title = doc.GetField("Title").StringValue();
        results.Add(news);
    }
    return results;
}

تابع بالا جهت استفاده در برنامه های تحت وب نیازمند تغییراتی می باشد. عنوان این تغییرات را در قسمت زیر ملاحظه می نمایید.

  1. با توجه به اینکه ایندکس ها در فایل های فیزیکی و روی هارد دیسک ذخیره می شوند، باید نحوه دسترسی به این فایل توسط ترید های مختلف را کنترل نمود.
  2. دستور IndexReader.Open زمانبر ترین و پر هزینه ترین دستور در تابع بالا می باشد. باید تدبیری بیندیشیم تا این دستور فقط یکبار اجرا شود و IndexReader مربوطه در اختیار ترید های مختلف جهت جستجو قرار گیرد.
  3. با توجه به اینکه ممکن است نتیجه جستجو حاوی هزاران و حتی صد ها هزار نتیجه باشد، باید امکان انجام عملیات صفحه بندی یا همان Paging را به کاربر بدهیم.

1 . برای کنترل نحوه دسترسی ترید های مختلف می توان از روش های مختلفی استفاده نمود که بستگی به نوع استفاده و طراحی نرم افزار شما دارد. ساده ترین راه برای انجام این کار استفاده از lock می باشد. به طور مثال اگر شما فقط یه صفحه یا فرم جستجو دارید، می توانید از روش زیر استفاده نمایید.

private static object lockObject=new object();
public void DoSearch(string searchTerm)
{
    lock (lockObject)
    {
      List<News> results=  LuceneSearch.SearchIndexes(searchTerm);
    }
}

ولی در صورتی که جستجوی شما در صفحات و یا فرم های مختلف انجام می شود، می توانید انجام این کار را در محل مناسب خود انجام دهید تا دسترسی همه ترید ها به فایل های ایندکس کنترل شده باشد. با توجه به اینکه این مطلب به نوع طراحی و پیاده سازی نرم افزار دارد، از بحث بیشتر در این زمینه خودداری می نماییم.

2. برای اینکه دستور IndexReader.Open را فقط یک بار اجرا نموده و سپس در اختیار ترید های مختلف قرار دهیم فقط کافیست که یک متغیر از نوع static ایجاد نموده و به شکل زیر IndexReader و در حقیقت IndexSearcher را در اختیار تابع جستجو قرار دهیم.

private static IndexSearcher _Searcher;
private static IndexSearcher Searcher
{
    get
    {
        if (_Searcher == null)
        {
            IndexReader reader = IndexReader.Open(GetNewsIndexDirectory());
                _Searcher = new IndexSearcher(reader);
        }
        return _Searcher;
    }
 
}

اگر به تابعی که در ابتدای صفحه نوشته شده است دقت نمایید، متوجه می شوید که عملکرد این تابع به دو قسمت تقسیم می شود. قسمت اول پر نمودن کلاس Hits و قسمت دوم پر نمودن لیست news می باشد. همانطور که در مقالات قبلی نیز ذکر شده است، کلاس Hits فقط حاوی تعداد و آدرس محل رکورد های یافت شده در عملیات جستجو می باشد. بنابراین پر شده کلاس Hits بسیار سریع انجام می شود و این کلاس اصطلاحا بسیار light-weight می باشد.

برای انجام عملیات صفحه بندی یا Paging ما متد ابتدای صفحه را به دو متد مجزا تبدیل می کنیم. علت انجام این کار را در ادامه مقاله ملاحظه خواهید نمود.

private static Hits GetHits(string inputText)
{
    Lucene.Net.Search.BooleanQuery.SetMaxClauseCount(int.MaxValue);
    Hits hits = null;
 
    //are there any wild cards in use?
    if (inputText.Contains("*"))
    {
        WildcardQuery query = new WildcardQuery(new Term("Body", inputText));
        hits = Searcher.Search(query);
    }
    //is this a multi term query?
    else if (inputText.Contains(" "))
    {
        MultiPhraseQuery query = new MultiPhraseQuery();
        foreach (string s in inputText.Split(' '))
        {
            query.Add(new Term("Body", s));
        }
        hits = Searcher.Search(query);
    }
    //single term query
    else
    {
        PhraseQuery query = new PhraseQuery();
        query.Add(new Term("Body", inputText));
        hits = Searcher.Search(query);
    }
    return hits;
 
}
 
public static List<News> SearchIndexes(string inputText)
{
    Hits hits = GetHits(inputText);
    List<News> results = new List<News>();
 
    for (int i = 0; i < hits.Length(); i++)
    {
        Document doc = hits.Doc(i);
        News news = new News();
        news.NewsID = Convert.ToInt32(doc.GetField("NewsID").StringValue());
        news.Title = doc.GetField("Title").StringValue();
        results.Add(news);
    }
 
    return results;
}

خوب، تا اینجای کار فقط ظاهر نحوه ی جستجو را تغییر داده ایم و قابلیت Paging هنوز وجود ندارد.

جهت انجام عملیات Paging به سه پارامتر مهم نیاز داریم

  1. تعداد کل نتایج جستجو جهت تولید دکمه های Paging
  2. تعداد نمایش رکوردها در صفحه و عبارت دیگر تعداد رکورد هایی از جستجو را که قصد داریم در یک صفحه نمایش دهیم.
  3. ایندکس شروع نتایج جستجو. به طور مثال اگر قرار باشد که در هر صفحه 10 نتیجه را نمایش دهیم و تعداد نتایج پیدا شده برای جستجو بیش از 10 نتیجه باشد، ایندکس شروع رکورد های نمایش داده شده در صفحه اول 0 است و ایندکس آخرین رکورد در این صفحه 9 می باشد.  هنگامی که کاربر روی دکمه صفحه دوم کلیک می کند باید نتایجی نمایش داده شود که ایندکس شروع آن 10 بوده و ایندکس رکورد آخر حداکثر (زیرا ممکن است که تعداد کل نتایج پیدا شده کمتر از 20 عدد باشد) 19 باشد.

برای بدست آوردن تعداد کل نتایج، تابع GetResultsCount را نوشته ایم

public static int GetResultsCount(string inputText)
{
    return GetHits(inputText).Length();
}

 و تابع SearchIndexes را نیز طوری تغییر داده ایم که فقط نتایج مورد نظر ما را برگرداند.

public static List<News> SearchIndexes(string inputText, int startRowIndex, int pageSize)
{
    Hits hits = GetHits( inputText);
    List<News> results = new List<News>();
 
 
    for (int i = startRowIndex; i < (startRowIndex + pageSize) && i < hits.Length(); i++)
    {
        Document doc = hits.Doc(i);
        News news = new News();
        news.NewsID = Convert.ToInt32(doc.GetField("NewsID").StringValue());
        news.Title = doc.GetField("Title").StringValue();
        results.Add(news);
 
    }
    return results;
}

اکنون ما به هدف خود رسیده ایم و امکان انجام عملیات Paging را نیز فراهم نموده ایم.

سورس انجام شده در این مقاله از لینک بالای صفحه قابل دریافت می باشد.