2009-02-19 8 views
21

Non c'è ricerca a testo integrale integrato in Linq e là non sembrano essere molti messaggi su tema così ho avuto un gioco intorno e si avvicinò con questo metodo per la mia classe utlity:testo Cerca in Linq

public static IEnumerable<TSource> GenericFullTextSearch<TSource>(string text, MyDataContext context) 
{ 
    //Find LINQ Table attribute 
    object[] info = typeof(TSource).GetCustomAttributes(typeof(System.Data.Linq.Mapping.TableAttribute), true); 
    //Get table name 
    String table = (info[0] as System.Data.Linq.Mapping.TableAttribute).Name; 
    //Full text search on that table 
    return context.ExecuteQuery<TSource>(String.Concat("SELECT * FROM ", table, " WHERE CONTAINS(*, {0})"), text); 
} 

E ha aggiunto questo wrapper per ogni classe parziale LINQ in cui v'è un indice completo

public static IEnumerable<Pet> FullTextSearch(string text, MyDataContext context) 
{ 
    return (LinqUtilities.GenericFullTextSearch<Pet>(text, context) as IEnumerable<Pet>); 
} 

Così ora posso fare ricerche full text con roba pulita come

var Pets = Pet.FullTextSearch(helloimatextbox.Text, MyDataContext).Skip(10).Take(10); 

Suppongo che al momento sia necessaria solo una ricerca di base. Qualcuno può migliorare su questo? È possibile implementare come metodo di estensione ed evitare il wrapper?

+4

One/problema unoptimal pericoloso per quanto riguarda la vostra richiesta è che il .Skip(). Take() verrà eseguito lato client, non lato server. Quindi se fai un FTS che restituisce 10^6 risultati e vuoi avere solo i primi 10, tutti 10^6 di essi saranno restituiti dal database, e solo allora eseguirai il filtraggio. –

+0

Sì, su un set di dati che sarebbe grande considerando un'altra tecnica;) – ctrlalt3nd

+0

Possibile duplicato di [È possibile utilizzare la ricerca di testo completo (FTS) con LINQ?] (Http://stackoverflow.com/questions/224475/is -it-possibile-usare-full-text-search-fts-with-linq) –

risposta

-1

si può semplicemente fare qualcosa di simile

var results = (from tags in _dataContext.View_GetDeterminationTags 
        where tags.TagName.Contains(TagName) || 
        SqlMethods.Like(tags.TagName,TagName) 
        select new DeterminationTags 
        { 
         Row = tags.Row, 
         Record = tags.Record, 
         TagID = tags.TagID, 
         TagName = tags.TagName, 
         DateTagged = tags.DateTagged, 
         DeterminationID = tags.DeterminationID, 
         DeterminationMemberID = tags.DeterminationMemberID, 
         MemberID = tags.MemberID, 
         TotalTagged = tags.TotalTagged.Value 
        }).ToList(); 

Avviso dove TagName.Contains anche la SQLMethods.Like basta fare un uso

using System.Data.Linq.SqlClient; 

per ottenere l'accesso a tale SQLMethods.

+2

Questo. contiene un LIKE '% TAGNAME%', che non è ottimale. –

+0

'LIKE' non è una ricerca' FULLTEXT'. – Dementic

-2

dswatik - la ragione per voler ricerca a testo integrale è che .contains traduce in

SELECT * FROM MYTABLE WHERE COLUMNNAME LIKE '%TEXT%' 

che ignora gli indici ed è orribile su un grande tavolo.

2

Metodo più bello leggermente (prende rango in vigore) utilizzando CONTAINSTABLE

String pkey = context.Mapping.GetTable(typeof(TSource)).RowType.DataMembers.SingleOrDefault(x => x.IsPrimaryKey).Name; 
string query = String.Concat(@"SELECT * 
    FROM ", table, @" AS FT_TBL INNER JOIN 
    CONTAINSTABLE(", table, @", *, {0}) AS KEY_TBL 
    ON FT_TBL.", pkey, @" = KEY_TBL.[KEY] 
    ORDER BY KEY_TBL.[RANK] DESC"); 
return context.ExecuteQuery<TSource>(query, text); 
-1

Ho cercato di risolvere il problema esatto. Mi piace scrivere la mia logica SQL nel mio LINQtoSQL ma avevo bisogno di un modo per fare ricerca full-text. in questo momento sto solo usando le funzioni SQL e poi chiamando le funzioni definite dall'utente in linea delle query di linq. non sono sicuro che sia il modo più efficace. Che cosa ne pensate?

3

ero abbastanza frustrato con la mancanza di chiari esempi ... soprattutto quando ci sono potenzialmente sono necessari grandi set di dati e paging. Quindi, ecco un esempio che comprende spera tutto il necessario :-)

create function TS_projectResourceSearch 
    ( @KeyStr nvarchar(4000), 
     @OwnId int, 
     @SkipN int, 
     @TakeN int) 
    returns @srch_rslt table (ProjectResourceId bigint not null, Ranking int not null) 
    as 
    begin 

     declare @TakeLast int 
     set @TakeLast = @SkipN + @TakeN 
     set @SkipN = @SkipN + 1 

     insert into @srch_rslt 
     select pr.ProjectResourceId, Ranking 
     from 
     (
      select t.[KEY] as ProjectResourceId, t.[RANK] as Ranking, ROW_NUMBER() over (order by t.[Rank] desc) row_num 
      from containstable(ProjectResource,(ResourceInfo, ResourceName), @KeyStr) 
      as t   
     ) as r 
     join ProjectResource pr on r.ProjectResourceId = pr.ProjectResourceId 
     where (pr.CreatorPersonId = @OwnId 
      or pr.ResourceAvailType < 40) 
      and r.row_num between @SkipN and @TakeLast 
     order by r.Ranking desc 

     return 
    end 
    go 


    select * from ts_projectResourceSearch(' "test*" ',1002, 0,1) 

godere, Patrick

2

Io uso un piccolo hack utilizzando Provider Wrapper tecniche. Ho un codice C# che riscrive la parola magica in SQL con FTS search per MS SQL (puoi aggiustare per ogni server che ti piace).

se avete MyEntities classe del contesto, creare sottoclasse come

public class MyEntitiesWithWrappers : MyEntities 
{ 
    private IEFTraceListener listener; 
    public string FullTextPrefix = "-FTSPREFIX-"; 

    public MyEntitiesWithWrappers(): this("name=MyEntities") 
    { 
    } 

    public MyEntitiesWithWrappers(string connectionString) 
     : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(connectionString,"EFTracingProvider")) 
    { 
     TracingConnection.CommandExecuting += RewriteFullTextQuery; 
    } 

    /// <summary> 
    /// Rewrites query that contains predefined prefix like: where n.NOTETEXT.Contains(Db.FullTextPrefix + text) with SQL server FTS 
    /// To be removed when EF will support FTS 
    /// </summary> 
    /// <param name="o"></param> 
    /// <param name="args"></param> 
    public void RewriteFullTextQuery(object o, CommandExecutionEventArgs args) 
    { 
     var text = args.Command.CommandText; 
     for (int i = 0; i < args.Command.Parameters.Count; i++) 
     { 
      DbParameter parameter = args.Command.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; 
       parameter.Size = 4096; 
       if (value.IndexOf(FullTextPrefix) >= 0) 
       { 
        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; 
        args.Command.CommandText = Regex.Replace(text, 
         string.Format(@"\(\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE '~')\)", parameter.ParameterName), 
         string.Format(@"contains([$1].[$2], @{0})", parameter.ParameterName)); 
       } 
      } 
     } 
    } 

    } 

E poi usare in questo modo:

var fullTextSearch = Db.FullTextPrefix + textToSearch; 
var q = Db.Notes.Where(n => !n.Private && n.NoteText.Contains(fullTextSearch));