2012-07-23 22 views
45

Mi rendo conto che sono state poste molte domande relative alla ricerca a testo integrale e a Entity Framework, ma spero che questa domanda sia un po 'diversa.Entity Framework, ricerca codice prima e testo completo

Sto utilizzando Entity Framework, Code First e devo effettuare una ricerca a testo integrale. Quando ho bisogno di eseguire la ricerca a testo integrale, ho in genere anche altri criteri/restrizioni - come saltare le prime 500 righe, o filtrare su un'altra colonna, ecc.

Vedo che questo è stato gestito utilizzando la tabella di valore funzioni - vedi http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspx. E questa sembra l'idea giusta.

Sfortunatamente, le funzioni con valori di tabella non sono supportate fino a Entity Framework 5.0 (e anche allora, credo, non sono supportate per Code First).

La mia vera domanda è quali sono i suggerimenti per il modo migliore di gestirlo, sia per Entity Framework 4.3 che per Entity Framework 5.0. Ma per essere precisi:

  1. Altro che SQL dinamico (via System.Data.Entity.DbSet.SqlQuery, per esempio), ci sono tutte le opzioni disponibili per Entity Framework 4.3?

  2. Se si esegue l'aggiornamento a Entity Framework 5.0, esiste un modo per utilizzare prima le funzioni con valori di tabella con il codice?

Grazie, Eric

+0

Come per la domanda (1), credo che questa sia la tua unica speranza – billy

+5

Ti suggerisco di utilizzare Lucene.Net per la ricerca a testo integrale. – LeffeBrune

+1

Dai un'occhiata a Lucene.Net :) –

risposta

16

ho trovato che il modo più semplice per implementare questo è quello di installare e configurare full-text-search in SQL Server e quindi utilizzare una stored procedure. Passa i tuoi argomenti a SQL, consenti al DB di fare il suo lavoro e restituire un oggetto complesso o mappare i risultati a un'entità. Non è necessario avere SQL dinamico, ma potrebbe essere ottimale. Ad esempio, se è necessario il paging, è possibile passare in PageNumber e PageSize su ogni richiesta senza la necessità di SQL dinamico. Tuttavia, se il numero di argomenti fluttua per query, sarà la soluzione ottimale.

+3

A volte ci dimentichiamo che possiamo sempre ricorrere alla stored procedure provata e vera! Preferisco anche questo metodo per l'hack dell'intercettore. –

2

Come gli altri ragazzi citati, vorrei dire iniziare a utilizzare Lucene.NET

Lucene ha una curva di apprendimento piuttosto alto, ma ho trovato un wrapper per questo chiamato "SimpleLucene", che può essere trovato su CodePlex

Lasciami citare un paio di blocchi di codice dal blog per mostrarti quanto è facile da usare. Ho appena iniziato a usarlo, ma ho capito molto velocemente.

primo luogo, ottenere alcune entità dal repository, o nel tuo caso, utilizzare 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 prossima cosa che voglio fare è creare un indice definizione

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

e creare un indice di ricerca per questo.

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

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

Quindi, ora avete un indice di ricerca. L'unica cosa che rimane da fare è ... cercare!Si possono fare cose piuttosto sorprendente, ma può essere facile come questo: (per maggiori esempi vedere il blog o la documentazione su 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

Recentemente ho avuto un requisito simile e ha finito per scrivere un prolungamento IQueryable specificamente per Microsoft pieno accesso indice di testo, la sua disponibile qui IQueryableFreeTextExtensions

+3

Il collegamento è interrotto. –

+2

Collegamento interrotto. L'hai portato via. :(Anche qui citato: http://effts.codeplex.com/discussions/554652 –

+1

Trovato qui http: //www.balsamicsolutions.net/Blog/Posta/2/Ricerca full-text in Microsoft-Entity-Framework –

47

Utilizzando intercettori introdotte nel EF6, si potrebbe segnare la ricerca a testo integrale in LINQ e poi sostituirlo in DbCommand come descritto in 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); 
    } 
} 

Per esempio, se si dispone di classe Nota con FTS-indicizzati campo NoteText:

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

e EF mappa per esso

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

e il contesto per esso:

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

si può avere sintassi abbastanza semplice per la query 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(); 
     } 
    } 
} 

che genererà SQL come

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) 

Si prega di notare che si dovrebbe usare variabile locale e non può muoversi FTS di avvolgimento interno espressione come

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

Che cos'è 'NoteMap'? –

+0

Ho aggiunto la classe NoteMap di esempio – Ben

+0

Grazie a @Ben, non ho capito che EF poteva essere configurato in quel modo. –

2

L'esempio qui http://www.entityframework.info/Home/FullTextSearch non è soluzione completa. Dovrai cercare di capire come funziona la ricerca di testo completo. Immagina di avere un campo di ricerca e l'utente digita 2 parole per colpire la ricerca. Il codice precedente genererà un'eccezione. È necessario eseguire prima la pre-elaborazione della frase di ricerca per passarla alla query utilizzando AND o OR logico.

ad esempio la ricerca frase è "bla blah2" allora avete bisogno di convertire questo in:

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

Soluzione completa sarebbe:

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

questo metodi usi e operatore logico. È possibile passare tale argomento come argomento e utilizzare il metodo per entrambi gli operatori AND o OR.

È necessario non immettere caratteri alfanumerici altrimenti si genererebbe un'eccezione quando un utente immette caratteri alfanumerici e non è presente alcuna convalida del livello del modello del sito server.

Problemi correlati