25 Stimmen

Volltextsuche in Linq

Es gibt keine Volltextsuche, die in Linq eingebaut ist, und es scheint nicht viele Beiträge zu diesem Thema zu geben, also habe ich ein wenig herumgespielt und bin auf diese Methode für meine Utlity-Klasse gekommen:

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

Und fügte diesen Wrapper zu jeder partiellen Linq-Klasse hinzu, in der es einen Volltextindex gibt

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

So kann ich jetzt Volltextsuchen durchführen mit netten Dingen wie

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

Ich gehe davon aus, dass im Moment nur eine sehr einfache Suche notwendig ist. Kann jemand dies verbessern? Ist es möglich, dies als Erweiterungsmethode zu implementieren und den Wrapper zu vermeiden?

4 Stimmen

Ein gefährliches/unoptimales Problem bei Ihrer Abfrage ist, dass .Skip().Take() clientseitig und nicht serverseitig ausgeführt wird. Wenn Sie also eine FTS durchführen, die 10^6 Ergebnisse liefert und Sie nur die ersten 10 haben möchten, werden alle 10^6 von der Datenbank zurückgegeben, und erst dann führen Sie die Filterung durch.

0 Stimmen

Ja, bei einem so großen Datensatz würde ich eine andere Technik in Betracht ziehen ;)

1 Stimmen

9voto

Simon Sabin Punkte 1

Die sauberste Lösung ist die Verwendung einer Inline-Tabelle bewertet Funktion in SQL und fügen Sie es zu Ihrem Modell

http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspx

Damit das funktioniert, müssen Sie eine tabellenbasierte Funktion erstellen, die Folgendes tut nichts weiter tut als eine CONTAINSTABLE Abfrage basierend auf den Schlüsselwörtern, die Sie eingeben,

create function udf_sessionSearch
      (@keywords nvarchar(4000)) returns table as   return (select [SessionId],[rank]
            from containstable(Session,(description,title),@keywords))

Sie fügen dann diese Funktion zu Ihrem LINQ 2 SQL-Modell hinzu und schon haben Sie können Sie nun Abfragen schreiben wie.

var sessList = from s   in DB.Sessions
               join fts in DB.udf_sessionSearch(SearchText) on s.sessionId equals fts.SessionId
               select s;

2 Stimmen

Dies erfordert auch eine udf pro Tabelle von dem, was ich sehen kann, die OPs Lösung sollte mit allen Tabellen arbeiten. Vielen Dank für den Beitrag aber

4voto

user603714 Punkte 1

Ich war ziemlich frustriert über den Mangel an klaren Beispielen... vor allem, wenn es sich um potenziell große Datensätze handelt und ein Paging erforderlich ist. Hier ist also ein Beispiel, das hoffentlich alles umfasst, was Sie brauchen könnten :-)

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)

Genießen Sie, Patrick

4voto

Ben Punkte 2352

Ich verwende einen kleinen Hack mit Anbieter Wrapper Techniken. Ich habe einen c#-Code, der magische Wort in SQL mit FTS-Suche für MS SQL neu zu schreiben (Sie können für jeden Server anpassen Sie mögen).

wenn Sie die Kontextklasse MyEntities haben, erstellen Sie eine Unterklasse wie

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

Und dann verwenden Sie es so:

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

3voto

ctrlalt3nd Punkte 1302

Eine etwas schönere Methode (die den Rang berücksichtigt) ist die Verwendung von 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);

2voto

Rosco Punkte 1784

.NET Core 2.1 und höher unterstützt eine Erweiterungsmethode, die die Verwendung von FREETEXT y FREETEXTTABLE sucht

using Microsoft.EntityFrameworkCore;

var results = dbContext.MyTable
        .Where(e => EF.Functions.FreeText("*", "search criteria"));

El FreeText Funktion ist definiert in Microsoft.EntityFrameworkCore.SqlServer daher muss Ihr Projekt auf dieses Paket verweisen.

Dokumentation

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X