2012-07-23 81 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运算符。

您必须转义非字母数字字符,否则当用户输入字母数字字符并且您没有适当的服务器站点模型级别验证时,它会引发异常。