项目结构:
LuceneSearch.Data层
SampleData.cs
namespace LuceneSearch.Model {
public class SampleData {
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
}
SelectedList.cs
namespace LuceneSearch.Model {
public class SelectedList {
public string Value { get; set; }
public string Text { get; set; }
}
}
SampleDataRepository.cs
using System.Collections.Generic;
using System.Linq;
using LuceneSearch.Model;
namespace LuceneSearch.Repository
{
public static class SampleDataRepository
{
public static SampleData Get(int id)
{
return GetAll().SingleOrDefault(x => x.Id.Equals(id));
}
public static List<SampleData> GetAll()
{
return new
List<SampleData> {
new SampleData {Id = 1, Name = "Belgrad", Description = "City in Serbia"},
new SampleData {Id = 2, Name = "Moscow", Description = "City in Russia"},
new SampleData {Id = 3, Name = "Chicago", Description = "City in USA"},
new SampleData {Id = 4, Name = "Mumbai", Description = "City in India"},
new SampleData {Id = 5, Name = "Hong-Kong", Description = "City in Hong-Kong"},
};
}
}
}
LuceneSearch.Library层
GoLucene.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using Lucene.Net.Analysis.Standard;
using Lucene.Net.Documents;
using Lucene.Net.Index;
using Lucene.Net.QueryParsers;
using Lucene.Net.Search;
using Lucene.Net.Store;
using LuceneSearch.Model;
using Version = Lucene.Net.Util.Version;
namespace LuceneSearch.Service
{
public static class GoLucene
{
// 属性
public static string _luceneDir =Path.Combine(HttpContext.Current.Request.PhysicalApplicationPath, "lucene_index");
private static FSDirectory _directoryTemp;
private static FSDirectory _directory
{
get
{
if (_directoryTemp == null) _directoryTemp = FSDirectory.Open(new DirectoryInfo(_luceneDir));
if (IndexWriter.IsLocked(_directoryTemp)) IndexWriter.Unlock(_directoryTemp);
var lockFilePath = Path.Combine(_luceneDir, "write.lock");
if (File.Exists(lockFilePath)) File.Delete(lockFilePath);
return _directoryTemp;
}
}
// 搜索方法
public static IEnumerable<SampleData> GetAllIndexRecords()
{
// 验证搜索索引
if (!System.IO.Directory.EnumerateFiles(_luceneDir).Any()) return new List<SampleData>();
// 设置lucene搜索器
var searcher = new IndexSearcher(_directory, false);
var reader = IndexReader.Open(_directory, false);
var docs = new List<Document>();
var term = reader.TermDocs();
// v 2.9.4: use 'term.Doc()'
// v 3.0.3: use 'term.Doc'
while (term.Next()) docs.Add(searcher.Doc(term.Doc));
reader.Dispose();
searcher.Dispose();
return _mapLuceneToDataList(docs);
}
public static IEnumerable<SampleData> Search(string input, string fieldName = "")
{
if (string.IsNullOrEmpty(input)) return new List<SampleData>();
var terms = input.Trim().Replace("-", " ").Split(' ')
.Where(x => !string.IsNullOrEmpty(x)).Select(x => x.Trim() + "*");
input = string.Join(" ", terms);
return _search(input, fieldName);
}
public static IEnumerable<SampleData> SearchDefault(string input, string fieldName = "")
{
return string.IsNullOrEmpty(input) ? new List<SampleData>() : _search(input, fieldName);
}
// 主要搜索方法
private static IEnumerable<SampleData> _search(string searchQuery, string searchField = "")
{
// 验证
if (string.IsNullOrEmpty(searchQuery.Replace("*", "").Replace("?", ""))) return new List<SampleData>();
// 设置Lucene搜索器
using (var searcher = new IndexSearcher(_directory, false))
{
var hits_limit = 1000;
var analyzer = new StandardAnalyzer(Version.LUCENE_30);
// 搜索单字段
if (!string.IsNullOrEmpty(searchField))
{
var parser = new QueryParser(Version.LUCENE_30, searchField, analyzer);
var query = parseQuery(searchQuery, parser);
var hits = searcher.Search(query, hits_limit).ScoreDocs;
var results = _mapLuceneToDataList(hits, searcher);
analyzer.Close();
searcher.Dispose();
return results;
}
// 搜索多个字段(按照RELEVANCE排序)
else
{
var parser = new MultiFieldQueryParser
(Version.LUCENE_30, new[] { "Id", "Name", "Description" }, analyzer);
var query = parseQuery(searchQuery, parser);
var hits = searcher.Search(query, null, hits_limit, Sort.INDEXORDER).ScoreDocs;
var results = _mapLuceneToDataList(hits, searcher);
analyzer.Close();
searcher.Dispose();
return results;
}
}
}
private static Query parseQuery(string searchQuery, QueryParser parser)
{
Query query;
try
{
query = parser.Parse(searchQuery.Trim());
}
catch (ParseException)
{
query = parser.Parse(QueryParser.Escape(searchQuery.Trim()));
}
return query;
}
// 将Lucene搜索索引映射到数据
private static IEnumerable<SampleData> _mapLuceneToDataList(IEnumerable<Document> hits)
{
return hits.Select(_mapLuceneDocumentToData).ToList();
}
private static IEnumerable<SampleData> _mapLuceneToDataList(IEnumerable<ScoreDoc> hits, IndexSearcher searcher)
{
// v 2.9.4: use 'hit.doc'
// v 3.0.3: use 'hit.Doc'
return hits.Select(hit => _mapLuceneDocumentToData(searcher.Doc(hit.Doc))).ToList();
}
private static SampleData _mapLuceneDocumentToData(Document doc)
{
return new SampleData
{
Id = Convert.ToInt32(doc.Get("Id")),
Name = doc.Get("Name"),
Description = doc.Get("Description")
};
}
// 添加/更新/清除 搜索索引数据
public static void AddUpdateLuceneIndex(SampleData sampleData)
{
AddUpdateLuceneIndex(new List<SampleData> { sampleData });
}
public static void AddUpdateLuceneIndex(IEnumerable<SampleData> sampleDatas)
{
// 初始lucene
var analyzer = new StandardAnalyzer(Version.LUCENE_30);
using (var writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED))
{
// add data to lucene search index (replaces older entries if any)
//将数据添加到lucene搜索索引(如果有的话替换旧条目)
foreach (var sampleData in sampleDatas) _addToLuceneIndex(sampleData, writer);
// 关闭
analyzer.Close();
writer.Dispose();
}
}
public static void ClearLuceneIndexRecord(int record_id)
{
// 初始lucene
var analyzer = new StandardAnalyzer(Version.LUCENE_30);
using (var writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED))
{
// 删除旧索引条目
var searchQuery = new TermQuery(new Term("Id", record_id.ToString()));
writer.DeleteDocuments(searchQuery);
// 关闭
analyzer.Close();
writer.Dispose();
}
}
public static bool ClearLuceneIndex()
{
try
{
var analyzer = new StandardAnalyzer(Version.LUCENE_30);
using (var writer = new IndexWriter(_directory, analyzer, true, IndexWriter.MaxFieldLength.UNLIMITED))
{
// 删除旧索引条目
writer.DeleteAll();
// 关闭
analyzer.Close();
writer.Dispose();
}
}
catch (Exception)
{
return false;
}
return true;
}
public static void Optimize()
{
var analyzer = new StandardAnalyzer(Version.LUCENE_30);
using (var writer = new IndexWriter(_directory, analyzer, IndexWriter.MaxFieldLength.UNLIMITED))
{
analyzer.Close();
writer.Optimize();
writer.Dispose();
}
}
private static void _addToLuceneIndex(SampleData sampleData, IndexWriter writer)
{
// 删除旧索引条目
var searchQuery = new TermQuery(new Term("Id", sampleData.Id.ToString()));
writer.DeleteDocuments(searchQuery);
// 添加新的索引条目
var doc = new Document();
// 添加映射到db字段的lucene字段
doc.Add(new Field("Id", sampleData.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
doc.Add(new Field("Name", sampleData.Name, Field.Store.YES, Field.Index.ANALYZED));
doc.Add(new Field("Description", sampleData.Description, Field.Store.YES, Field.Index.ANALYZED));
// 添加条目到索引
writer.AddDocument(doc);
}
}
}
LuceneSearch.Mvc层
HomeController.cs
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using LuceneSearch.Repository;
using LuceneSearch.Service;
using LuceneSearch.Model;
using MvcLuceneSampleApp.ViewModels;
namespace MvcLuceneSampleApp.Controllers
{
public class HomeController : Controller
{
public ActionResult Index(string searchTerm, string searchField, bool? searchDefault, int? limit)
{
//创建默认的Lucene搜索索引目录
if (!Directory.Exists(GoLucene._luceneDir))
{
Directory.CreateDirectory(GoLucene._luceneDir);
}
//执行Lucene搜索
List<SampleData> _searchResults;
if (searchDefault == true)
{
_searchResults = (string.IsNullOrEmpty(searchField) ? GoLucene.SearchDefault(searchTerm) : GoLucene.SearchDefault(searchTerm, searchField)).ToList();
}
else
{
_searchResults = (string.IsNullOrEmpty(searchField)
? GoLucene.Search(searchTerm)
: GoLucene.Search(searchTerm, searchField)).ToList();
}
if (string.IsNullOrEmpty(searchTerm) && !_searchResults.Any())
{
_searchResults = GoLucene.GetAllIndexRecords().ToList();
}
//设置和返回视图模型
var search_field_list = new
List<SelectedList> {
new SelectedList {Text = "(所有字段)", Value = ""},
new SelectedList {Text = "Id", Value = "Id"},
new SelectedList {Text = "Name", Value = "Name"},
new SelectedList {Text = "Description", Value = "Description"}
};
//限制显示数据库记录数
var limitDb = limit == null ? 3 : Convert.ToInt32(limit);
List<SampleData> allSampleData;
if (limitDb > 0)
{
allSampleData = SampleDataRepository.GetAll().ToList().Take(limitDb).ToList();
ViewBag.Limit = SampleDataRepository.GetAll().Count - limitDb;
}
else allSampleData = SampleDataRepository.GetAll();
return View(new IndexViewModel
{
AllSampleData = allSampleData,
SearchIndexData = _searchResults,
SampleData = new SampleData { Id = 9, Name = "埃尔帕索", Description = "德克萨斯城市" },
SearchFieldList = search_field_list,
});
}
public ActionResult Search(string searchTerm, string searchField, string searchDefault)
{
return RedirectToAction("Index", new { searchTerm, searchField, searchDefault });
}
public ActionResult CreateIndex()
{
GoLucene.AddUpdateLuceneIndex(SampleDataRepository.GetAll());
TempData["Result"] = "搜索索引已成功创建!";
return RedirectToAction("Index");
}
[HttpPost]
public ActionResult AddToIndex(SampleData sampleData)
{
GoLucene.AddUpdateLuceneIndex(sampleData);
TempData["Result"] = "记录被成功添加到搜索索引!";
return RedirectToAction("Index");
}
public ActionResult ClearIndex()
{
if (GoLucene.ClearLuceneIndex())
TempData["Result"] = "搜索索引已成功清除!";
else
TempData["ResultFail"] = "索引已锁定,无法清除,请稍后重试或手动清除!";
return RedirectToAction("Index");
}
public ActionResult ClearIndexRecord(int id)
{
GoLucene.ClearLuceneIndexRecord(id);
TempData["Result"] = "搜索索引记录已成功删除!";
return RedirectToAction("Index");
}
public ActionResult OptimizeIndex()
{
GoLucene.Optimize();
TempData["Result"] = "搜索索引成功优化!";
return RedirectToAction("Index");
}
}
}
IndexViewModel.cs
using System.Collections.Generic;
using LuceneSearch.Model;
namespace MvcLuceneSampleApp.ViewModels
{
public class IndexViewModel
{
public int Limit { get; set; }
public bool SearchDefault { get; set; }
public SampleData SampleData { get; set; }
public IEnumerable<SampleData> AllSampleData { get; set; }
public IEnumerable<SampleData> SearchIndexData { get; set; }
public IList<SelectedList> SearchFieldList { get; set; }
public string SearchTerm { get; set; }
public string SearchField { get; set; }
}
}
Index.cshtml
@model MvcLuceneSampleApp.ViewModels.IndexViewModel
<div class="content_left">
<span style="color: green;">@TempData["Result"]</span>
<span style="color: red;">@TempData["ResultFail"]</span>
</div>
<div class="clear"></div>
<div class="col_12">
<fieldset>
<legend>数据库记录 (@Html.ActionLink("从数据库创建搜索索引 [+]", "CreateIndex"))</legend>
<table class="grid">
<tr>
<th>@Html.LabelFor(m => Model.SampleData.Id)</th>
<th>@Html.LabelFor(m => Model.SampleData.Name)</th>
<th>@Html.LabelFor(m => Model.SampleData.Description)</th>
</tr>
@foreach (var record in Model.AllSampleData)
{
<tr>
<td>@record.Id</td>
<td>@record.Name</td>
<td>@record.Description</td>
</tr>
}
</table>
@if (ViewBag.Limit > 0)
{
<div class="margin_top10"> <b>@ViewBag.Limit</b> 条更多记录... (<a href="\?limit=0">查看全部</a>)</div>
}
</fieldset>
</div>
<div class="col_12">
<fieldset>
<legend>搜索(自定义,适用于大多数基本场景)</legend>
<div class="content_left margin_top5">
<p>尝试这些搜索: <em>"1 3 5", "City", "Russia India", "bel mos ind"</em></p>
</div>
@using (Html.BeginForm("Search", "Home"))
{
<div class="content_left margin_top13 margin_left30">
@Html.CheckBoxFor(m => m.SearchDefault)
@Html.LabelFor(m => m.SearchDefault, "使用默认的Lucene查询")
</div>
<div class="content_left margin_right20">
@Html.TextBoxFor(m => m.SearchTerm, new { @class = "big", style = "width:650px;", autocomplete = "off" })
</div>
<div class="content_left margin_top15 margin_right30">
@Html.DropDownListFor(m => m.SearchField, Model.SearchFieldList.Select(x => new SelectListItem { Text = x.Text, Value = x.Value }),
new { style = "width:150px;" })
</div>
<div class="content_right margin_top8">
<input type="submit" value="搜索" />
</div>
<div class="clear"></div> }
</fieldset>
</div>
<div class="col_8">
<fieldset>
<legend>
Search index
(@Html.ActionLink("优化", "OptimizeIndex"))
(@Html.ActionLink("清除 [X]", "ClearIndex"))
</legend>
@if (Model.SearchIndexData.Any())
{
<table class="grid">
<tr>
<th>@Html.LabelFor(m => Model.SampleData.Id)</th>
<th>@Html.LabelFor(m => Model.SampleData.Name)</th>
<th>@Html.LabelFor(m => Model.SampleData.Description)</th>
<th></th>
</tr>
@foreach (var record in Model.SearchIndexData)
{
<tr>
<td>@record.Id</td>
<td>@record.Name</td>
<td>@record.Description</td>
<td>@Html.ActionLink("删除", "ClearIndexRecord", new { record.Id })</td>
</tr>
}
</table>
}
else
{
<text><br />找不到搜索索引记录...<br /></text>
}
</fieldset>
</div>
<div class="col_4">
<fieldset class="add_record">
<legend>添加/更新 搜索索引记录</legend>
使用现有的ID进行更新
@using (Html.BeginForm("AddToIndex", "Home"))
{
<div class="form_horizontal">
<p>
@Html.LabelFor(m => m.SampleData.Id)<br />
@Html.TextBoxFor(m => m.SampleData.Id, new { style = "width:30px;" })
</p>
<p>
@Html.LabelFor(m => m.SampleData.Name)<br />
@Html.TextBoxFor(m => m.SampleData.Name, new { style = "width:100px;" })
</p>
<p>
@Html.LabelFor(m => m.SampleData.Description)<br />
@Html.TextBoxFor(m => m.SampleData.Description, new { style = "width:120px;" })
</p>
</div>
<div class="clear"></div>
<input type="submit" value="添加/更新 记录" /> }
</fieldset>
</div>
<script type="text/javascript">
$(document).ready(function () {
$('#SearchTerm').focus();
$('.grid tr:even').css("background", "silver");
});
</script>
_Layout.cshtml
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Lucene.Net | MVC</title>
<link href="@Url.Content("~/Content/style.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/kickstart/kickstart.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/style.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/style.lib.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/style.local.css")" rel="stylesheet" type="text/css" />
<script src="http://code.jquery.com/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">window.jQuery || document.write('<script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")"><\/script>')</script>
<script src="@Url.Content("~/Scripts/kickstart.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/prettify.js")" type="text/javascript"></script>
</head>
<body>
<div class="header clearfix">
<div class="top_menu"><a href="/">主页</a></div>
<h2>Lucene.Net for MVC => 创建更容易!</h2>
<h3>简单的Lucene.Net使用示例站点</h3>
</div>
<div class="content clearfix">
@RenderBody()
</div>
<div class="footer">© @DateTime.Now.Year
</div>
</body>
</html>
_Layout.cshtml
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<title>Lucene.Net | MVC</title>
<link href="@Url.Content("~/Content/style.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/kickstart/kickstart.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/style.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/style.lib.css")" rel="stylesheet" type="text/css" />
<link href="@Url.Content("~/Content/style.local.css")" rel="stylesheet" type="text/css" />
<script src="http://code.jquery.com/jquery.min.js" type="text/javascript"></script>
<script type="text/javascript">window.jQuery || document.write('<script src="@Url.Content("~/Scripts/jquery-1.7.2.min.js")"><\/script>')</script>
<script src="@Url.Content("~/Scripts/kickstart.js")" type="text/javascript"></script>
<script src="@Url.Content("~/Scripts/prettify.js")" type="text/javascript"></script>
</head>
<body>
<div class="header clearfix">
<div class="top_menu"><a href="/">主页</a></div>
<h2>Lucene.Net for MVC => 创建更容易!</h2>
<h3>简单的Lucene.Net使用示例站点</h3>
</div>
<div class="content clearfix">
@RenderBody()
</div>
<div class="footer">
© @DateTime.Now.Year
</div>
</body>
</html>