Copy this link when reproducing:
http://www.casperlee.com/en/y/blog/55
Last time, I've downloaded two 3rd-party tools: Lucene.Net & Pangu, and made a few modifications to the system. This time, I'm gonna finish the basic functions of the Fulltext search feature.
1. Add a column "FullTextSearchable" to the table "Resources":
sqlite> alter table Resources add FullTextSearchable Integer NULL default 0;
sqlite> .schema Resources
CREATE TABLE Resources (
ID Integer PRIMARY KEY AUTOINCREMENT,
Password Char(32) NOT NULL,
Name Varchar(600) NOT NULL,
Keywords Varchar(600) NULL,
FileName VarChar(600) NOT NULL,
ResourceTypeID Integer NOT NULL, FullTextSearchable Integer NULL default 0,
FOREIGN KEY(ResourceTypeID) REFERENCES ResourceTypes (ID)
);
2. Open the file "ResourceManage.edmx" in the DAL project.
3. Right click on the blanks, select "Update Model from Database..." from the popped menu.
4. Switch to the "Refresh" tab, select "Resources", and click "Finish" to refresh the model.
5. Made a few modifications to the class "com.casperlee.ResourceManager.Bll.ResourceEx":
I. Rename the property "FullTextSearchingEnabled" to "IsFullTextSearchEnabled":
public bool IsFullTextSearchEnabled
{
get
{
return this.FullTextSearchable == 1;
}
set
{
this.FullTextSearchable = value ? 1 : 0;
}
}
II. Include the field "FullTextSearchable" in the function "CopyTo" and the function "CopyFrom":
public void CopyFrom(Resource aResource)
{
...
this.FullTextSearchable = aResource.FullTextSearchable;
}
public void CopyTo(Resource aResource)
{
...
aResource.FullTextSearchable = this.FullTextSearchable;
}
6. Add a class named "LuceneTool.cs" in the Full text search project:
using Lucene.Net.Analysis.PanGu;
using Lucene.Net.QueryParsers;
using System;
using System.Collections.Generic;
using System.IO;
namespace com.casperlee.ResourceManager.FulltextSearch
{
public class LuceneTool
{
private const string FLD_UNIQUE_KEY = "UniqueKey";
private const string FLD_PLAIN_TEXT = "PlainText";
private string indexFolder;
private PanGuAnalyzer analyzer;
private QueryParser parser;
public LuceneTool(string anIndexFolder)
{
this.indexFolder = anIndexFolder;
this.analyzer = new PanGuAnalyzer();
this.parser = new QueryParser(
Lucene.Net.Util.Version.LUCENE_30,
FLD_PLAIN_TEXT,
analyzer);
}
public void AddDocument(string aKey, string aPlainText)
{
using (Lucene.Net.Index.IndexWriter writer = new Lucene.Net.Index.IndexWriter(
new Lucene.Net.Store.SimpleFSDirectory(new DirectoryInfo(this.indexFolder)),
analyzer,
Lucene.Net.Index.IndexWriter.MaxFieldLength.UNLIMITED))
{
Lucene.Net.Documents.Document doc = new Lucene.Net.Documents.Document();
doc.Add(new Lucene.Net.Documents.Field(
FLD_UNIQUE_KEY,
aKey,
Lucene.Net.Documents.Field.Store.YES,
Lucene.Net.Documents.Field.Index.NOT_ANALYZED));
doc.Add(new Lucene.Net.Documents.Field(
FLD_PLAIN_TEXT,
new StringReader(aPlainText)));
writer.AddDocument(doc);
writer.Optimize();
}
}
public void SearchDocument(string aText, List<int> aIDs)
{
if (string.IsNullOrEmpty(aText))
{
return;
}
Lucene.Net.Search.Query query = parser.Parse(aText);
Lucene.Net.Search.TopDocs topDocs = null;
using (Lucene.Net.Search.IndexSearcher searcher = new Lucene.Net.Search.IndexSearcher(
new Lucene.Net.Store.SimpleFSDirectory(new DirectoryInfo(this.indexFolder)),
true))
{
topDocs = searcher.Search(query, 1000);
for (int i = 0; i < topDocs.TotalHits; i++)
{
Lucene.Net.Documents.Document doc = searcher.Doc(topDocs.ScoreDocs[i].Doc);
aIDs.Add(Convert.ToInt32(doc.GetField(FLD_UNIQUE_KEY).StringValue));
}
}
}
}
}
7. Add a reference of the Full text search project to the Bll project.
8. Create a folder named "Indexes" in the Bin directory. Add the following lines into the class "com.casperlee.ResourceManager.Bll.CEnvironment":
public static string IndexesPath
{
get
{
string folder = Path.Combine(BinPath, "Indexes");
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
return folder;
}
}
9. Made a few modifications to the class "com.casperlee.ResourceManager.Bll.ResourceHandler":
I. Add a new virtual function so that the child class can override it:
protected virtual void ExtraAddResource(
ResourceEx aRawResource,
Resource aSavedResource)
{
}
II. Remove "virtual" from the function "AddResource":
public void AddResource(ResourceEx aResource)
{
...
using (ResourceManageEntities rme = new ResourceManageEntities())
{
...
ExtraAddResource(aResource, r);
}
}
10. Made a few modifications to the class "com.casperlee.ResourceManager.Bll.FulltextResourceHandler":
using System.Collections.Generic;
using System.Linq;
using System.IO;
using com.casperlee.ResourceManager.Dal;
using com.casperlee.ResourceManager.FulltextSearch;
namespace com.casperlee.ResourceManager.Bll
{
public class FulltextResourceHandler: ResourceHandler
{
private LuceneTool luceneTool;
public FulltextResourceHandler()
{
luceneTool = new LuceneTool(CEnvironment.IndexesPath);
}
protected override void ExtraAddResource(
ResourceEx aRawResource,
Resource aSavedResource)
{
base.ExtraAddResource(aRawResource, aSavedResource);
using (FileStream fs = new FileStream(aRawResource.RawTextFileName, FileMode.Open))
{
using (StreamReader sr = new StreamReader(fs))
{
string content = sr.ReadToEnd();
luceneTool.AddDocument(
aSavedResource.ID.ToString(),
content);
}
}
}
public override void Search(string aText, string aPassword, List<ResourceEx> aResult)
{
base.Search(aText, aPassword, aResult);
List<int> ids = new List<int>();
luceneTool.SearchDocument(aText, ids);
if (ids.Count > 0)
{
using (ResourceManageEntities rme = new ResourceManageEntities())
{
for (int i = 0; i < ids.Count; i++)
{
int id = ids[i];
if (aResult.Count<ResourceEx>(x => x.ID == id) > 0)
{
continue;
}
Resource r = rme.Resources.Single<Resource>(x => x.ID == id);
ResourceEx rx = new ResourceEx();
rx.CopyFrom(r);
rx.EntityType = ResourceEntityType.retDB;
rx.Encrypted = true;
rx.Decrypt(CEnvironment.CurrentPassword);
aResult.Add(rx);
}
}
}
}
}
}
11. Done!