NHibernate.Search 基于Lucene.NET的全文索引

      NHibernate.Search现在是NHiberante Contrilb下面的一个还没有发布的项目,也是从Hibernate.Search移植而来,把NHibernate和Lucene.NET结合在一起,ORM持久化对象到数据库中,Lucene.NET提供索引及查询支持.
     下面在实际使用一下NHibernate.Search的使用:
     由于这个项目还没有发布,它的很多特性也是一直在变化,所以现在只是以我下载的版本为准,如果你想尝试此项目,请下载最下面的代码,里面包括自己编译生成的NHibernate.Search及最新的Lucene.net相关的dll.

一.配置
    1.修改你的NHibernate配置信息的地方,同样添加NHibernate.Search的配置信息.

<configSections>
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate"/>
<section name="nhs-configuration" type="NHibernate.Search.Cfg.ConfigurationSectionHandler, NHibernate.Search" 
requirePermission="false" /> <sectionGroup name="system.web.extensions" type="System.Web.Configuration.SystemWebExtensionsSectionGroup,
System.Web.Extensions, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"
> ..... </configSections>
        需要注意的是,在NHibernate.Search之前的版本中,曾经NHibernate.Search的配置信息和NHIbernate的放在一起.接下来,我们来看下具体的配置块:
 <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
        <bytecode-provider type="lcg"/>
        <reflection-optimizer use="true"/>
        <session-factory name="NHibernateSearch.Demo">
            <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider, NHibernate</property>
            <property name="connection.connection_string">
                Data Source=|DataDirectory|Demo.db3;Version=3;Compress=False;synchronous=OFF;
            </property>
            <property name="show_sql">true</property>
            <property name="dialect">NHibernate.Dialect.SQLiteDialect</property>
            <property name="connection.driver_class">NHibernate.Driver.SQLite20Driver</property>
            <property name="prepare_sql">true</property>
            <property name="cache.provider_class">NHibernate.Cache.HashtableCacheProvider, NHibernate</property>
            <property name="cache.use_query_cache">true</property>
            <mapping assembly="NHibernateSearch.Demo.Model"/>
        </session-factory>
    </hibernate-configuration>
 <nhs-configuration xmlns='urn:nhs-configuration-1.0'>
        <search-factory  sessionFactoryName="NHibernateSearch.Demo">
            <property name='hibernate.search.default.directory_provider'>NHibernate.Search.Store.FSDirectoryProvider, 
NHibernate.Search</property> <property name='hibernate.search.default.indexBase'>~/Index</property> <property name='hibernate.search.default.indexBase.create'>true</property> </search-factory> </nhs-configuration>

     上面的是NHibernate的配置信息,由于我在示例程序中使用的是SQLite数据库,所以这个Demo也是一个SQLite+NHibernate的案例,而且里注意SQLite的连接字符串Data Source=|DataDirectory|Demo.db3,这样的话,只要把数据文件放在App_Data下面就可以运行了,而不用再去填写数据库文件的绝对路径.
     下面是NHibernate.Search的配置,我们采用基于文件目录的全文检索,索引文件放在根目录下面的Index文件夹下面.
     另外,我们设置一下NHibernate的事件监听(如果不我们设置的话,我们要手动去调用,才能够使NHibernate在持久化对象时,同时处理全文索引),现在这个事件配置还不支持直接在NHibernate这样配置:

  
       <listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-insert'/>
       <listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-update'/>
      <listener class='NHibernate.Search.Event.FullTextIndexEventListener, NHibernate.Search' type='post-delete'/>

      现在我们只能在创建SessionFactory前,手动添加这个配置信息:

               Configuration configuration = new Configuration();
                configuration.SetListener(NHibernate.Event.ListenerType.PostUpdate, new FullTextIndexEventListener());
                configuration.SetListener(NHibernate.Event.ListenerType.PostInsert, new FullTextIndexEventListener());
                configuration.SetListener(NHibernate.Event.ListenerType.PostDelete, new FullTextIndexEventListener());
                configuration.Configure();
                sessionFactory=  configuration.BuildSessionFactory();

      我们为更新,删除,添加都配置了全文索引的事件监听,这样就能够保证索引文件里的数据和实际数据库里的保持一致,不要担心,这的性能也有考虑,这里可以支持最大提交数量,而不一定非要每条操作都去更改索引文件.

二.使用
      首先我们要在NHibernate实体对象这里添加一些NHibernate.Search的一些关于全文索引的属性声明.不知道这里以后会不会支持xml的配置而不是修改原来的对象代码.

    [Indexed(Index = "Book")] 
    public class Book
    {
        [DocumentId]
        public  virtual string BookID
        {
            get;
            set;
        }
        [Field(Index.Tokenized, Store = Store.Yes)]
        public virtual string Title
        {
            get;
            set;
        }
        [Field(Index.Tokenized, Store = Store.Yes)]
        public virtual string Authors
        {
            get;
            set;
        }
        [Field(Index.Tokenized, Store = Store.Yes)]
        public virtual string Publisher
        {
            get;
            set;
        }
        [Field(Index.Tokenized, Store = Store.Yes)]
        public virtual string Summary
        {
            get;
            set;
        }
    }

     用过Lucene.Net的朋友们可能会发现,其实这些标识,原来在不使用NHibernate时是直接针对字段的.而现在只是针对一个业务对象的属性.这样的话,但数据添加或者更改时,就会自动的进行索引,但数据进行删除时,也自动更新索引,而可以不进行人为的干预.
    上面是一个对象Book,其中包含名称,作者,出版社,简介.我们要想实现对这几个属性进行全文检索的话,以前只能够同时拼几个LiKE,如果针对数据库,数据库数据的话肯定性能不会好,但是如果针对索引文件就不一样了.首先我们看一下怎么来实现搜索:
    

public   static IList<BookSearchResult>  FindBooks(string query)
        {
            IList<BookSearchResult> results = new List<BookSearchResult>();
            Analyzer analyzer = new Lucene.Net.Analysis.Standard.StandardAnalyzer();
   MultiFieldQueryParser parser = new MultiFieldQueryParser(new string[] { "Title", "Summary", "Authors", "Publisher"
}, analyzer); Query queryObj; try { queryObj = parser.Parse(query); } catch (ParseException) { return results; } IFullTextSession session = Search.CreateFullTextSession(NHibernateHelper.GetCurrentSession());
///1 System.Type targetType = typeof(Book); IQuery nhQuery = session.CreateFullTextQuery(queryObj, new Type[] { targetType }); IList<Book> books = nhQuery.List<Book>(); NHibernate.Cfg.Configuration cf = new Configuration().Configure(); SearchFactoryImpl searchFactory = SearchFactoryImpl.GetSearchFactory(cf); IDirectoryProvider provider = searchFactory.GetDirectoryProviders(targetType)[0]; Workspace workspace = new Workspace(searchFactory); IndexReader indexReader = workspace.GetIndexReader(provider, targetType); Query simplifiedQuery = queryObj.Rewrite(indexReader); SimpleHTMLFormatter formatter = new SimpleHTMLFormatter("<b class='term'>", "</b>"); Highlighter hTitle = GetHighlighter(simplifiedQuery, formatter, "Title", 100); Highlighter hSummary = GetHighlighter(simplifiedQuery, formatter, "Summary", 200); Highlighter hAuthors = GetHighlighter(simplifiedQuery, formatter, "Authors", 100); Highlighter hPublisher = GetHighlighter(simplifiedQuery, formatter, "Publisher", 100); foreach (Book book in books) { BookSearchResult result = new BookSearchResult(book); TokenStream tsTitle = analyzer.TokenStream("Title", new System.IO.StringReader(book.Title ?? string.Empty)); result.HighlightedTitle = hTitle.GetBestFragment(tsTitle, book.Title); TokenStream tsAuthors = analyzer.TokenStream("Authors", new System.IO.StringReader(book.Authors ?? string.Empty)); result.HighlightedAuthors = hAuthors.GetBestFragment(tsAuthors, book.Authors); TokenStream tsPublisher = analyzer.TokenStream("Publisher", new System.IO.StringReader(book.Publisher ??
string.Empty)); result.HighlightedPublisher = hPublisher.GetBestFragment(tsPublisher, book.Publisher); TokenStream tsSummary = analyzer.TokenStream("Summary", new System.IO.StringReader(book.Summary ??
string.Empty)); result.HighlightedSummary = hSummary.GetBestFragments(tsSummary, book.Summary, 3, " ... <br /><br /> ... "); results.Add(result); } return results; }

      相信使用过Lucene.Net的朋友对上面的代码并不难理解,所有使用IFullTextSession的操作,都会进行全文索引的处理.另外这里还有多字段解析和分词的技术,就不详细介绍了.上面还使用了对查询关键字进行高亮显示,其中的BookSearchResult实体也只是对Book进行了包装,进行了显示的一些处理.具体请查看源代码.
      你可以在上面注释的///1处,添加对Book的一些操作,就可以显示的查看索引文件是否也同时进行了更新了.

二.结果
     下载直接运行整个项目,输入”程序”关键字,你会发现,在很短时间内,列出来了符合的记录,并黑色显示匹配的字(至于与不进行索引的搜索性能对比,留给以后再做吧):
   screenshot31

 

三.资料及参考
    1.NHibernate.Search using Lucene.NET Full Text Index  http://blogs.intesoft.net/post/2008/03/NHibernateSearch-using-Lucene-NET-Full-Text-Index-Part1.aspx
    2.NHibernate Search   http://darioquintana.com.ar/blogging/?p=21
    3.Lucene.NET and NHibernate.Search on medium trust http://www.klopfenstein.net/lorenz.aspx/lucene-net-and-nhibernate-search-on-medium-trust
    4.Using NHibernate.Search with ActiveRecord http://using.castleproject.org/display/AR/Using+NHibernate.Search+with+ActiveRecord

     本次项目文件下载(NHibernate+SQLite+Lucene.Net).(for Vs 2008)

 

作者:孤独侠客似水流年
出处:http://lonely7345.cnblogs.com
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

posted @ 2009-03-17 00:00 孤独侠客 阅读(4298) 评论(30) 编辑 收藏

 回复 引用 查看   
#1楼2009-03-17 00:19 | Jeffrey Zhao      
Lucene.NET好是好,可是,可是……API太丑了阿。这个类涉及的很糟糕,命名空间乱用,成员名也是Java风格的,真是用一次难受一次。
呵呵。

 回复 引用 查看   
#2楼[楼主]2009-03-17 00:22 | 孤独侠客      
@Jeffrey Zhao
老赵这么晚还没睡,佩服...命名空间确实是多了点,而且很深,而且有些同样的东西没放在同一个名字空间下面. 不过提供的功能还是相当不错的.开源的.也可以改嘛.呵呵

 回复 引用 查看   
#3楼2009-03-17 00:32 | Jeffrey Zhao      
--引用--------------------------------------------------
孤独侠客: @Jeffrey Zhao
老赵这么晚还没睡,佩服...命名空间确实是多了点,而且很深,而且有些同样的东西没放在同一个名字空间下面. 不过提供的功能还是相当不错的.开源的.也可以改嘛.呵呵
--------------------------------------------------------
希望有人能改,可惜太庞大了,改起来……估计只是说说的,呵呵。

 回复 引用   
#4楼2009-03-17 00:46 | 0663[未注册用户]
全文索引我好像200W数据要生成N久(企业黄页http://www.12580520.com)
 回复 引用 查看   
#5楼2009-03-17 01:03 | Jeffrey Zhao      
@0663
分成n各部分同时生成,再合并。

 回复 引用 查看   
#6楼2009-03-17 08:31 | 沉默杨仔      
学习了
 回复 引用 查看   
#7楼[楼主]2009-03-17 08:36 | 孤独侠客      
@Jeffrey Zhao
这个n实际测试一下,取个合适的值

 回复 引用 查看   
#8楼2009-03-17 08:48 | kiler      
全文索引的话,还是每天做一次比较合适,因为需要全文检索的数据对检索并不是要求绝对实时准确的,也可以考虑做增量索引,这个不耗性能,Lucene.NET也支持。
 回复 引用 查看   
#9楼[楼主]2009-03-17 08:55 | 孤独侠客      
@kiler

用有变化的数据分批来更新索引.如果数据量变化的频率比较小,确实可以考虑

 回复 引用 查看   
#10楼2009-03-17 08:56 | 温景良(Jason)      
看起来很怪!!!!!!!学习了,多写点文章
 回复 引用 查看   
#11楼2009-03-17 09:05 | Jeffrey Zhao      
@kiler
一天一次绝对了,不过的确没必要实时。
类似的策略很多,要选择最合适的,比如可建内存磁盘,增量部分加在内存中,定时合并。

 回复 引用 查看   
#12楼2009-03-17 09:35 | 猫的宠物鱼      
索引文件15G,查询非常慢,基本在1-3秒之间。如何解决?
还有排序后有内存泄露,无法使用缓存,又如何解决。
苦恼中。。。。

 回复 引用 查看   
#13楼2009-03-17 09:48 | 山顶栋人      
@0663
索引的速度跟硬盘的性能有很大关系

 回复 引用 查看   
#14楼2009-03-17 09:56 | Jeffrey Zhao      
@猫的宠物鱼
嗯,检查索引方式,查询方式,磁盘速度等,再者可以设法拆分索引数据,并作聚合查询。

 回复 引用 查看   
#15楼[楼主]2009-03-17 10:05 | 孤独侠客      
@猫的宠物鱼

可以考虑按分类拆分成小的索引文件.

 回复 引用 查看   
#16楼2009-03-17 10:48 | 猫的宠物鱼      
@Jeffrey Zhao
@孤独侠客

也试过拆分成若干小索引文件,但效果依然不理想。

 回复 引用 查看   
#17楼[楼主]2009-03-17 11:19 | 孤独侠客      
@猫的宠物鱼


硬盘速度咋样,读取文件差别挺大的.

 回复 引用   
#18楼2009-03-17 11:24 | !A.Z[未注册用户]
--引用--------------------------------------------------
Jeffrey Zhao: Lucene.NET好是好,可是,可是……API太丑了阿。这个类涉及的很糟糕,命名空间乱用,成员名也是Java风格的,真是用一次难受一次。
呵呵。
--------------------------------------------------------


聊胜于无

 回复 引用 查看   
#19楼2009-03-17 12:50 | shawnliu      
索引文件需要做下partition 这样可以避免很多问题
outofmemory可能需要改变你的查询规则了 lucene经常出现这个错误

 回复 引用 查看   
#20楼2009-03-18 09:35 | JimLiu      
怎样才能建立索引呢? 我添加了监听器,但是我发布新文章的时候,没有建立索引
 回复 引用 查看   
#21楼[楼主]2009-03-18 11:34 | 孤独侠客      
@JimLiu

得注意上面提到的每一个细节.包括配置细节.你不是通过数据库手动添加的吧?

 回复 引用 查看   
#22楼2009-03-18 12:19 | JimLiu      
@孤独侠客
我的数据库里已经有了一些数据,如何对这些数据进行索引
我新发布的文章也是通过ISession发布的

 回复 引用 查看   
#23楼2009-03-18 13:30 | JimLiu      
还有,您给的Example我也运行不起来,执行搜索后出现
-----------------
无法加载 DLL“sqlite3”: 找不到指定的模块。 (异常来自 HRESULT:0x8007007E)。
-----------------
这样的错误提示
似乎是sqlite有问题

 回复 引用 查看   
#24楼2009-03-18 13:34 | 古道轻风      
例子怎么运行不起来?
BookSearch.cs 第48行
异常为 用户代码未处理ADOException
cannot open connection


 回复 引用 查看   
#25楼[楼主]2009-03-18 20:29 | 孤独侠客      
@JimLiu
@古道轻风


抱歉,放错了System.Data.Sqlite的程序集,请重新下载附件,我已经更换过了.

 回复 引用 查看   
#26楼[楼主]2009-03-18 20:32 | 孤独侠客      
--引用--------------------------------------------------
JimLiu: @孤独侠客
我的数据库里已经有了一些数据,如何对这些数据进行索引
我新发布的文章也是通过ISession发布的
--------------------------------------------------------

这个我也没有找到.呵呵,应该有的吧.不过实在不行,你直接通过Lucene.Net建立一个索引就行了

 回复 引用 查看   
#27楼2009-05-24 12:54 | 滔滔不绝      
请问下LZ,如果要换成Sql server的数据库,需要修改些什么?
 回复 引用 查看   
#28楼2010-04-23 16:35 | sig556      
请问下LZ <bytecode-provider type="lcg"/>
上面这个是什么意思?

 回复 引用 查看   
#29楼2010-07-10 19:00 | euler      
即没有错误,也搜索不到任何结果。
请问搂主,这是什么原因?

 回复 引用 查看   
#30楼[楼主]2010-08-21 11:09 | 孤独侠客      
@euler
调试看一下