2014-01-11 34 views
4

我想我在實現LinqToHql生成器類時缺少一些基本的東西。NHibernate LinqToHqlGenerator for SQL Server 2008全文索引'Containing'關鍵字

我已經成功註冊2008 contains查詢使用自定義的方言有註冊在SQL Server:

RegisterFunction("contains", new StandardSQLFunction("contains", null)); 

我只是要查詢一個類全文索引:

public class SearchName 
{ 
    public virtual Guid Id {get; set;} 
    public virtual string Name {get; set;} // this is the search field 
} 

包含功能在HQL中正常工作:

var names = Session.CreateQuery("from SearchName where contains(Name,:keywords)") 
        .SetString("keywords", "john") 
        .List(); 

和生成的SQL是完美的:

select searchname0_.Id as Id4_, 
     searchname0_.Name as Name4_ 
from Search_Name searchname0_ 
where contains(searchname0_.Name, 'john' /* @p0 */) 

下一個挑戰是實施LINQ到HQL發生器:

public class MyLinqtoHqlGeneratorsRegistry : 
    DefaultLinqToHqlGeneratorsRegistry 
    { 
     public MyLinqtoHqlGeneratorsRegistry() 
     { 
      this.Merge(new ContainsGenerator()); 
     } 
    } 

    public class ContainsGenerator : BaseHqlGeneratorForMethod 
    { 
     public ContainsGenerator() 
     { 
      SupportedMethods = new[] { 
       ReflectionHelper.GetMethodDefinition<SearchName>(d => d.Name.Contains(String.Empty)) 
      }; 
     } 

     public override HqlTreeNode BuildHql(MethodInfo method, 
      System.Linq.Expressions.Expression targetObject, 
      ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, 
      HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) 
     { 
      return treeBuilder.MethodCall("contains", 
        visitor.Visit(targetObject).AsExpression(), 
        visitor.Visit(arguments[0]).AsExpression() 
       ); 
     } 
    } 
} 

調用這樣的方法:

var namesLinq = Session.Query<SearchName>().Where(x=> x.Name.Contains("john")).ToList(); 

不幸的是,這似乎沒有覆蓋內置的Contains方法,並且生成的SQL是錯誤的:

select searchname0_.Id as Id4_, 
     searchname0_.Name as Name4_ 
from Search_Name searchname0_ 
where searchname0_.Name like ('%' + 'john' /* @p0 */ + '%') 

是不可能重寫默認的Contains方法,還是我犯了一個愚蠢的錯誤?

PS - 我使用NHibernate 3.3.1.4000

+0

有可能是另一種方法來取,你看到這篇博客的HTTP ://fabiomaulo.blogspot.co.uk/2010/07/nhibernate-linq-provider-extension.html - 顯示如何擴展LINQ提供程序(您可能無法將「Contains」作爲已註冊的函數進行覆蓋) – Rippo

+0

@ Rippo是的,我看過那篇文章。它提供了我用來創建自己的擴展的大部分信息。 –

+0

我在說的是你可能需要拿出另一個方法名來代替'Contains'來使用'FullTextContains',因爲Fabio正在使用'IsLike'來轉換爲'%foo%' – Rippo

回答

8

好了,我終於想通了!

首先,我設法從我的配置中刪除註冊碼:

... 
.ExposeConfiguration(cfg => 
    { 
     cfg.LinqToHqlGeneratorsRegistry<MyLinqtoHqlGeneratorsRegistry>(); 
     ... 
    } 

二,不要試圖覆蓋現有的LINQ的行爲。我將我的Contains擴展方法移至全文類。

三,正確構建Hql樹。

對於其他人試圖執行一個SQL 2008自由文本中包含搜索,這裏的完整實現:​​

public static class DialectExtensions 
    { 
     public static bool Contains(this SearchName sn, string searchString) 
     { 
      // this is just a placeholder for the method info. 
      // It does not otherwise matter. 
      return false; 
     } 
    } 

    public class MyLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry 
    { 
     public MyLinqtoHqlGeneratorsRegistry() 
      : base() 
     { 
      RegisterGenerator(ReflectionHelper.GetMethod(() => 
       DialectExtensions.Contains(null, null)), 
       new ContainsGenerator()); 
     } 
    } 

    public class ContainsGenerator : BaseHqlGeneratorForMethod 
    { 
     string fullTextFieldName = "Name"; 

     public ContainsGenerator() 
      : base() 
     { 
      SupportedMethods = new[] { 
       ReflectionHelper.GetMethodDefinition(() => 
       DialectExtensions.Contains(null, null)) 
      }; 
     } 

     public override HqlTreeNode BuildHql(MethodInfo method, 
      System.Linq.Expressions.Expression targetObject, 
      ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, 
      HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) 
     { 
      // cannot figure out how to interrogate the model class to get an 
      // arbitrary field name... 
      // perhaps the RegisterGenerator() call above could be used to pass a 
      // property name to the ContainsGenerator constructor? 
      // in our case, we only have one full text searchable class, and its 
      // full-text searchable field is "Name" 
      HqlExpression[] args = new HqlExpression[2] { 
       treeBuilder.Ident(fullTextFieldName).AsExpression(), 
       visitor.Visit(arguments[1]).AsExpression() 
       }; 
      return treeBuilder.BooleanMethodCall("contains", args); 
     } 
    } 

對於上述工作,您必須聲明和使用自定義的話:

public class CustomMsSql2008Dialect : NHibernate.Dialect.MsSql2008Dialect 
{ 
    public CustomMsSql2008Dialect() 
    { 
     RegisterFunction(
      "contains", 
      new StandardSQLFunction("contains", null) 
      ); 
    } 
} 

然後你就可以使用新的包含搜索這種方式:

var namesLinq = Session.Query<SearchName>().Where(x => x.Contains("john")).ToList(); 

...以及由此產生的SQL是完美的! (至少如果你只有一個表,你正在執行全文搜索)

編輯:更新的實施,以支持超過一個FULLTEXT'包含'搜索每個查詢。

下面是修改後的版本:

public static class DialectExtensions 
    { 
     public static bool FullTextContains(this string source, string pattern) 
     { 
      return false; 
     } 
    } 

    public class MyLinqtoHqlGeneratorsRegistry : DefaultLinqToHqlGeneratorsRegistry 
    { 
     public MyLinqtoHqlGeneratorsRegistry() 
      : base() 
     { 
      RegisterGenerator(ReflectionHelper.GetMethod(() => DialectExtensions.FullTextContains(null, null)), 
          new FullTextContainsGenerator()); 
     } 
    } 

    public class FullTextContainsGenerator : BaseHqlGeneratorForMethod 
    { 
     public FullTextContainsGenerator() 
     { 
      SupportedMethods = new[] { ReflectionHelper.GetMethod(() => DialectExtensions.FullTextContains(null, null)) }; 
     } 

     public override HqlTreeNode BuildHql(MethodInfo method, 
      System.Linq.Expressions.Expression targetObject, 
      ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, 
      HqlTreeBuilder treeBuilder, IHqlExpressionVisitor visitor) 
     { 
      HqlExpression[] args = new HqlExpression[2] { 
       visitor.Visit(arguments[0]).AsExpression(), 
       visitor.Visit(arguments[1]).AsExpression() 
      }; 
      return treeBuilder.BooleanMethodCall("contains", args); 
     } 
    } 

要使用修改後的版本,語法稍有不同:

var namesLinq = Session.Query<SearchName>().Where(x => x.Name.FullTextContains("john")).ToList(); 
+0

這是非常好的,如果你從來沒有使用包含'%foo%'的默認linq ......好的工作 – Rippo

+0

那麼,使用LIKE查詢一個varchar(300)字段的數百萬條記錄並不會讓我開心!另外,儘管NHibernate.Search可用(並且是在數據庫實例中使用Fulltext的最佳選擇),但並非每個人都有能力在運行中爲其生產系統添加新的Lucene環境。 –

+0

必須同意Rippo。很好,甚至很棒,看看可配置和可擴展的NHibernate是如何。好! +1 –