Mi rendo conto che sono state poste molte domande relative alla ricerca a testo integrale e ad Entity Framework, ma spero che questa domanda sia un po ‘diversa.
Sto usando Entity Framework, Code First e ho bisogno di fare una ricerca testuale. Quando ho bisogno di eseguire la ricerca di testo completo, in genere avrò anche altri criteri / restrizioni – come saltare le prime 500 righe o filtrare su un’altra colonna, ecc.
Vedo che questo è stato gestito utilizzando le funzioni con valori di tabella – 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 per gestire questo, sia per Entity Framework 4.3 che per Entity Framework 5.0. Ma per essere specifici:
Oltre all’SQL dinamico (tramite System.Data.Entity.DbSet.SqlQuery
, ad esempio), sono disponibili opzioni per Entity Framework 4.3?
Se eseguo l’aggiornamento a Entity Framework 5.0, esiste un modo per utilizzare prima le funzioni con valori di tabella con il codice?
Grazie, Eric
Utilizzando gli intercettori introdotti in EF6, è ansible contrassegnare la ricerca di testo completo in linq e quindi sostituirla 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 interceptionContext) { } public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { } public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext interceptionContext) { RewriteFullTextQuery(command); } public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext interceptionContext) { } public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext
Ad esempio, se si dispone di una nota di class con campo indicizzato FTS NoteText:
public class Note { public int NoteId { get; set; } public string NoteText { get; set; } }
e mappa EF per questo
public class NoteMap : EntityTypeConfiguration { public NoteMap() { // Primary Key HasKey(t => t.NoteId); } }
e contesto per questo:
public class MyContext : DbContext { static MyContext() { DbInterception.Add(new FtsInterceptor()); } public MyContext(string nameOrConnectionString) : base(nameOrConnectionString) { } public DbSet Notes { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new NoteMap()); } }
puoi avere una syntax 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(); } } }
Questo 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 noti che è necessario utilizzare la variabile locale e non è ansible spostare il wrapper FTS all’interno di espressioni come
var q = db.Notes.Where(n => n.NoteText.Contains(FtsInterceptor.Fts("john")));
Ho trovato che il modo più semplice per implementare questo è quello di configurare e configurare la ricerca full-text 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 object complesso o mappare i risultati a un’ quadro. Non è necessario avere SQL dinamico, ma potrebbe essere ottimale. Ad esempio, se è necessario il paging, è ansible 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.
Recentemente ho avuto un requisito simile e ho finito per scrivere un’estensione IQueryable specificamente per l’accesso all’indice di testo completo di Microsoft, è disponibile qui IQueryableFreeTextExtensions
Come menzionato dagli altri ragazzi, direi di iniziare a usare Lucene.NET
Lucene ha una curva di apprendimento piuttosto elevata, ma ho trovato un wrapper 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.
Per prima cosa, prendi alcune entity framework dal tuo repository o, nel tuo caso, usa Entity Framework
public class Repository { public IList Products { get { return new List { 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 vuoi fare è creare una definizione di indice
public class ProductIndexDefinition : IIndexDefinition { 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 hai un indice di ricerca. L’unica cosa che rimane da fare è … cercare! Puoi fare cose incredibili, ma può essere facile come questo: (per maggiori esempi vedi 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 converter = (doc) => { return new ProductSearchResult { Id = int.Parse(doc.GetValues("id")[0]), Name = doc.GetValues("name")[0] }; }; IList results = searchService.SearchIndex(searcher, query, converter);
L’esempio qui http://www.entityframework.info/Home/FullTextSearch non è una 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.
per esempio la tua frase di ricerca è “blah blah2”, quindi devi convertirlo in:
var searchTerm = @"\"blah\" AND/OR \"blah2\" ";
La 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 metodo utilizza l'operatore logico AND. È ansible passare tale argomento come argomento e utilizzare il metodo per entrambi gli operatori AND o OR.
È necessario sfuggire a caratteri alfanumerici altrimenti altrimenti si genera un'eccezione quando un utente immette caratteri alfanumerici e non è presente alcuna convalida del livello del modello del sito server.