2012-07-23 27 views
45

Me doy cuenta de que se han formulado muchas preguntas relacionadas con la búsqueda de texto completo y Entity Framework, pero espero que esta pregunta sea un poco diferente.Entity Framework, primer código y búsqueda de texto completo

Estoy usando Entity Framework, Code First y necesito hacer una búsqueda de texto completo. Cuando necesito realizar la búsqueda de texto completo, normalmente tendré otros criterios/restricciones, como omitir las primeras 500 filas o filtrar en otra columna, etc.

Veo que esto se ha manejado utilizando una tabla valorada funciones - ver http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspx. Y esta parece ser la idea correcta.

Desafortunadamente, las funciones con valores de tabla no son compatibles hasta Entity Framework 5.0 (e incluso entonces, creo que no son compatibles con Code First).

Mi verdadera pregunta es cuáles son las sugerencias para la mejor forma de manejar esto, tanto para Entity Framework 4.3 como para Entity Framework 5.0. Sin embargo, para ser específicos:

  1. Aparte de SQL dinámico (a través de System.Data.Entity.DbSet.SqlQuery, por ejemplo), ¿hay opciones disponibles para Entity Framework 4.3?

  2. Si actualizo a Entity Framework 5.0, ¿hay alguna forma de que pueda usar primero las funciones con valores de tabla?

Gracias, Eric

+0

En cuanto a la pregunta (1), creo que esta es su única esperanza – billy

+5

Sugiero usar Lucene.Net para la búsqueda de texto completo. – LeffeBrune

+1

Eche un vistazo en Lucene.Net :) –

Respuesta

16

he encontrado que la mejor manera de implementar esto es para instalar y configurar de texto de búsqueda completa en SQL Server y luego usar un procedimiento almacenado. Pase sus argumentos a SQL, permita que el DB haga su trabajo y devuelva un objeto complejo o asigne los resultados a una entidad. No necesariamente tiene que tener SQL dinámico, pero puede ser óptimo. Por ejemplo, si necesita paginación, puede pasar PáginaNúmero y PageSize en cada solicitud sin la necesidad de SQL dinámico. Sin embargo, si la cantidad de argumentos fluctúa por consulta, será la solución óptima.

+3

¡A veces nos olvidamos de que siempre podemos recurrir al procedimiento almacenado probado y verdadero! También prefiero este método al pirateo interceptor. –

2

Como los otros tipos mencionados, yo diría que empezar a usar Lucene.NET

Lucene tiene una curva de aprendizaje bastante alto, pero he encontrado un envoltorio para que calificó como "SimpleLucene", que se puede encontrar en CodePlex

Permítanme citar un par de bloques de código del blog para mostrarles lo fácil que es usarlos. Empecé a usarlo, pero lo entendí muy rápido.

primer lugar, obtener algunas entidades de su repositorio, o en su caso, utilizar Entity Framework

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"}, 
      }; 
     } 
    } 
} 

La siguiente cosa que quieres hacer es crear una definición de índice

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()); 
    } 
} 

y crear un índice de búsqueda para ello.

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

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

Entonces, ahora tiene un índice de búsqueda. Lo único que queda por hacer es ... ¡buscar!Se pueden hacer cosas muy sorprendentes, pero puede ser tan fácil como esto: (para mayores ejemplos, véase el blog o la documentación en 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

recientemente he tenido un requisito similar y terminó escribiendo una extensión IQueryable específicamente para Microsoft Access índice de texto completo, disponible aquí su IQueryableFreeTextExtensions

+3

El enlace está roto. –

+2

Enlace roto. Te lo quitaste. :(También mencionado aquí: http://effts.codeplex.com/discussions/554652 –

+1

Encontrado aquí http: //www.balsamicsolutions.net/Blog/Post/2/Búsqueda de texto completo en Microsoft's-Entity-Framework –

47

el uso de interceptores introducidas en EF6, podría marcar la búsqueda de texto completo en LINQ y luego volver a colocarlo en DbCommand como se describe en http://www.entityframework.info/Home/FullTextSearch:

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); 
    } 
} 

Por ejemplo, si usted tiene la clase de nota con el FTS-indexado campo NoteText:

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

y mapa de EF para que

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

y el contexto para ello:

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()); 
    } 
} 

que puede tener sintaxis bastante simple a la consulta 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(); 
     } 
    } 
} 

que va a generar SQL como

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) 

Por favor nótese que se debe utilizar la variable local y no se puede mover dentro de la envoltura FTS expresión como

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

¿Qué es 'NoteMap'? –

+0

He agregado el ejemplo NoteMap clase – Ben

+0

Gracias @Ben, no me di cuenta de que EF podría configurarse de esa manera. –

2

El ejemplo aquí http://www.entityframework.info/Home/FullTextSearch no es una solución completa. Deberá analizar cómo funciona la búsqueda de texto completo. Imagine que tiene un campo de búsqueda y el usuario escribe 2 palabras para buscar. El código anterior arrojará una excepción. Primero debe realizar el procesamiento previo en la frase de búsqueda para pasarla a la consulta utilizando AND u OR lógico.

por ejemplo la frase de búsqueda es "blah2 bla", entonces usted necesita para convertir esto en:

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

solución completa sería:

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); 
    } 

estos métodos usos y operador lógico. Puede pasar eso como argumento y usar el método para operadores AND u OR.

Debe evitar los caracteres no alfanuméricos, de lo contrario arrojaría una excepción cuando un usuario ingrese caracteres alfanuméricos y no tenga validada el nivel del modelo del sitio del servidor.

Cuestiones relacionadas