2012-07-23 85 views
45

我意識到有很多問題都被問及全文搜索和實體框架,但我希望這個問題有點不同。實體框架,代碼優先和全文搜索

我使用的是實體框架,Code First,需要做全文搜索。當我需要執行全文搜索時,我通常還會有其他標準/限制 - 例如跳過前500行或在另一列上過濾等。

我看到使用表值處理功能 - 請參閱http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspx。這似乎是正確的想法。

不幸的是,直到實體框架5.0(甚至那時,我相信,它們不被Code First支持),表值函數才被支持。

我真正的問題是什麼建議最好的方式來處理這個,無論是實體框架4.3和實體框架5.0。但要具體:

  1. 除動態SQL(通過System.Data.Entity.DbSet.SqlQuery,例如),是否有任何可供選擇的實體框架4.3?

  2. 如果我升級到實體框架5.0,有沒有辦法讓代碼優先使用表值函數?

感謝, 埃裏克

+0

至於問題(1),我相信這是你唯一的希望 – billy 2012-07-26 22:26:51

+5

我建議使用Lucene.Net進行全文搜索。 – LeffeBrune 2012-08-02 16:22:49

+1

看看Lucene.Net :) – 2012-08-15 05:42:52

回答

16

我發現,最簡單的方式來實現,這是安裝和配置SQL Server中的全文搜索,然後使用存儲過程。將你的參數傳遞給SQL,允許DB完成它的工作並返回一個複雜對象或將結果映射到一個實體。您不必擁有動態SQL,但它可能是最佳的。例如,如果您需要分頁,則可以在每個請求上傳遞PageNumberPageSize而不需要動態SQL。但是,如果每個查詢的參數數量都是波動的,它將是最佳解決方案。

+3

有時候我們會忘記我們總是可以迴避經過驗證的真實存儲過程!我也更喜歡這種方法來攔截黑客。 – 2016-08-24 13:17:46

2

正如其他人所提到的,我要說開始使用Lucene.NET

Lucene的具有相當高的學習曲線,但我發現了一個包裝它稱爲「SimpleLucene」,可以在CodePlex

找到

讓我引用博客中的幾個代碼塊來向您展示它的使用方式。我剛剛開始使用它,但它的速度非常快。

首先,從你的資料庫得到一些實體,或在您的情況下,使用實體框架

public class Repository 
{ 
    public IList<Product> Products { 
     get { 
      return new List<Product> { 
       new Product { Id = 1, Name = "Football" }, 
       new Product { Id = 2, Name = "Coffee Cup"}, 
       new Product { Id = 3, Name = "Nike Trainers"}, 
       new Product { Id = 4, Name = "Apple iPod Nano"}, 
       new Product { Id = 5, Name = "Asus eeePC"}, 
      }; 
     } 
    } 
} 

你想要做的下一件事就是創建一個索引定義

public class ProductIndexDefinition : IIndexDefinition<Product> { 
    public Document Convert(Product p) { 
     var document = new Document(); 
     document.Add(new Field("id", p.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED)); 
     document.Add(new Field("name", p.Name, Field.Store.YES, Field.Index.ANALYZED)); 
     return document; 
    } 

    public Term GetIndex(Product p) { 
     return new Term("id", p.Id.ToString()); 
    } 
} 

,並創建它的搜索索引。

var writer = new DirectoryIndexWriter(
    new DirectoryInfo(@"c:\index"), true); 

var service = new IndexService(); 
service.IndexEntities(writer, Repository().Products, ProductIndexDefinition()); 

所以,你現在有一個可搜索的索引。唯一剩下要做的就是..,搜索!你可以做非常了不起的事情,但它可以是因爲這很容易:(更大的例子中看到的博客或在CodePlex上的文檔)

var searcher = new DirectoryIndexSearcher(
       new DirectoryInfo(@"c:\index"), true); 

var query = new TermQuery(new Term("name", "Football")); 

var searchService = new SearchService(); 

Func<Document, ProductSearchResult> converter = (doc) => { 
    return new ProductSearchResult { 
     Id = int.Parse(doc.GetValues("id")[0]), 
     Name = doc.GetValues("name")[0] 
    }; 
}; 

IList<Product> results = searchService.SearchIndex(searcher, query, converter); 
2

我最近也有類似的要求,結束了專門寫一個IQueryable擴展微軟全文索引訪問,其可在這裏IQueryableFreeTextExtensions

+3

鏈接已損壞。 – 2015-04-02 06:08:46

+2

斷開的鏈接。你把它拿走了。 :(這裏也提到:http://effts.c​​odeplex.com/discussions/554652 – 2015-05-04 02:49:43

+1

在這裏找到它http://www.balsamicsolutions。net/Blog/Post/2/Full-text-search-in-Microsoft's-Entity-Framework – 2015-07-21 06:13:52

47

使用EF6介紹攔截器,你可能標誌着LINQ的全文檢索,然後在http://www.entityframework.info/Home/FullTextSearch描述取代它的DbCommand:

public class FtsInterceptor : IDbCommandInterceptor 
{ 
    private const string FullTextPrefix = "-FTSPREFIX-"; 

    public static string Fts(string search) 
    { 
     return string.Format("({0}{1})", FullTextPrefix, search); 
    } 

    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) 
    { 
    } 

    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext) 
    { 
    } 

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) 
    { 
     RewriteFullTextQuery(command); 
    } 

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext) 
    { 
    } 

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) 
    { 
     RewriteFullTextQuery(command); 
    } 

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext) 
    { 
    } 

    public static void RewriteFullTextQuery(DbCommand cmd) 
    { 
     string text = cmd.CommandText; 
     for (int i = 0; i < cmd.Parameters.Count; i++) 
     { 
      DbParameter parameter = cmd.Parameters[i]; 
      if (parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength)) 
      { 
       if (parameter.Value == DBNull.Value) 
        continue; 
       var value = (string)parameter.Value; 
       if (value.IndexOf(FullTextPrefix) >= 0) 
       { 
        parameter.Size = 4096; 
        parameter.DbType = DbType.AnsiStringFixedLength; 
        value = value.Replace(FullTextPrefix, ""); // remove prefix we added n linq query 
        value = value.Substring(1, value.Length - 2); 
        // remove %% escaping by linq translator from string.Contains to sql LIKE 
        parameter.Value = value; 
        cmd.CommandText = Regex.Replace(text, 
         string.Format(
          @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')", 
          parameter.ParameterName), 
         string.Format(@"contains([$1].[$2], @{0})", 
            parameter.ParameterName)); 
        if (text == cmd.CommandText) 
         throw new Exception("FTS was not replaced on: " + text); 
        text = cmd.CommandText; 
       } 
      } 
     } 
    } 

} 
static class LanguageExtensions 
{ 
    public static bool In<T>(this T source, params T[] list) 
    { 
     return (list as IList<T>).Contains(source); 
    } 
} 

舉例來說,如果你有一流的注意與FTS索引字段NoteText:

public class Note 
{ 
    public int NoteId { get; set; } 
    public string NoteText { get; set; } 
} 

和EF地圖它

public class NoteMap : EntityTypeConfiguration<Note> 
{ 
    public NoteMap() 
    { 
     // Primary Key 
     HasKey(t => t.NoteId); 
    } 
} 

,併爲它的上下文:

public class MyContext : DbContext 
{ 
    static MyContext() 
    { 
     DbInterception.Add(new FtsInterceptor()); 
    } 

    public MyContext(string nameOrConnectionString) : base(nameOrConnectionString) 
    { 
    } 

    public DbSet<Note> Notes { get; set; } 

    protected override void OnModelCreating(DbModelBuilder modelBuilder) 
    { 
     modelBuilder.Configurations.Add(new NoteMap()); 
    } 
} 

你可以有FTS查詢語法非常簡單:

class Program 
{ 
    static void Main(string[] args) 
    { 
     var s = FtsInterceptor.Fts("john"); 

     using (var db = new MyContext("CONNSTRING")) 
     { 
      var q = db.Notes.Where(n => n.NoteText.Contains(s)); 
      var result = q.Take(10).ToList(); 
     } 
    } 
} 

這將生成SQL像

exec sp_executesql N'SELECT TOP (10) 
[Extent1].[NoteId] AS [NoteId], 
[Extent1].[NoteText] AS [NoteText] 
FROM [NS].[NOTES] AS [Extent1] 
WHERE contains([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 char(4096)',@p__linq__0='(john) 

請注意,您應該使用局部變量,不能移動FTS包裝內表達喜歡

var q = db.Notes.Where(n => n.NoteText.Contains(FtsInterceptor.Fts("john"))); 
+0

什麼是NoteMap? – 2014-04-03 14:38:35

+0

我添加了示例NoteMap類 – Ben 2014-04-03 14:47:42

+0

謝謝@Ben,沒有意識到EF可以以這種方式進行配置。 – 2014-04-03 14:59:00

2

這裏的例子http://www.entityframework.info/Home/FullTextSearch是不完整的解決方案。您需要了解全文搜索的工作原理。想象一下,你有一個搜索字段,用戶輸入2個單詞來搜索。上面的代碼會拋出一個異常。您需要先對搜索短語進行預處理,然後使用邏輯「與」或「或」將其傳遞給查詢。

例如搜索短語「嗒嗒blah2」,那麼你需要將其轉換成:

var searchTerm = @"\"blah\" AND/OR \"blah2\" "; 

完整的解決方案是:

value = Regex.Replace(value, @"\s+", " "); //replace multiplespaces 
        value = Regex.Replace(value, @"[^a-zA-Z0-9 -]", "").Trim();//remove non-alphanumeric characters and trim spaces 

        if (value.Any(Char.IsWhiteSpace)) 
        { 
         value = PreProcessSearchKey(value); 
        } 


public static string PreProcessSearchKey(string searchKey) 
    { 
     var splitedKeyWords = searchKey.Split(null); //split from whitespaces 

     // string[] addDoubleQuotes = new string[splitedKeyWords.Length]; 

     for (int j = 0; j < splitedKeyWords.Length; j++) 
     { 
      splitedKeyWords[j] = $"\"{splitedKeyWords[j]}\""; 
     } 

     return string.Join(" AND ", splitedKeyWords); 
    } 

此方法的用途和邏輯運算。您可以將其作爲參數傳遞給AND和OR運算符。

您必須轉義非字母數字字符,否則當用戶輸入字母數字字符並且您沒有適當的服務器站點模型級別驗證時,它會引發異常。