Ist es möglich, die Volltextsuche (FTS) mit LINQ zu verwenden?


76

Ich frage mich, ob es möglich ist, FTS mit LINQ unter Verwendung von .NET Framework 3.5 zu verwenden. Ich suche in der Dokumentation, dass ich noch nichts Nützliches gefunden habe.

Hat jemand irgendwelche Erfahrungen damit?

Antworten:


77

Ja. Sie müssen jedoch zuerst eine SQL Server-Funktion erstellen und diese aufrufen, da LINQ standardmäßig ein Like verwendet.

Dieser Blog-Beitrag, der das Detail erklärt, aber dies ist der Auszug:

Damit es funktioniert, müssen Sie eine Tabellenwertfunktion erstellen, die nur eine CONTAINSTABLE-Abfrage basierend auf den von Ihnen übergebenen Schlüsselwörtern ausführt.

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

Anschließend fügen Sie diese Funktion Ihrem LINQ 2 SQL-Modell hinzu, und Sie können jetzt Abfragen wie schreiben.

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

12

Die Volltextsuche wird von LINQ To SQL nicht unterstützt.

Sie können jedoch eine gespeicherte Prozedur verwenden, die FTS verwendet, und die LINQ To SQL-Abfrage Daten daraus abrufen lassen.


10

Wenn Sie keine Joins erstellen und Ihren C # -Code vereinfachen möchten, können Sie eine SQL-Funktion erstellen und diese in der "from" -Klausel verwenden:

CREATE FUNCTION ad_Search
(
      @keyword nvarchar(4000)
)
RETURNS TABLE
AS
RETURN
(
      select * from Ad where 
      (CONTAINS(Description, @keyword) OR CONTAINS(Title, @keyword))
)

Verwenden Sie Ihr DBML nach dem Aktualisieren in linq:

string searchKeyword = "word and subword";
var result = from ad in context.ad_Search(searchKeyword)
                 select ad;

Dies erzeugt einfaches SQL wie folgt:

SELECT [t0].ID, [t0].Title, [t0].Description
FROM [dbo].[ad_Search](@p0) AS [t0]

Dies funktioniert bei der Suche nach mehreren Spalten, wie Sie aus der Implementierung der Funktion ad_Search ersehen können.


9

Ich glaube nicht. Sie können 'enthält' für ein Feld verwenden, es wird jedoch nur eine LIKEAbfrage generiert . Wenn Sie Volltext verwenden möchten, würde ich empfehlen, einen gespeicherten Prozess für die Abfrage zu verwenden und ihn dann an LINQ zurückzugeben


5

Nein, die Volltextsuche ist etwas sehr Spezifisches für SQL Server (bei dem Text durch Wörter indiziert wird und Abfragen diesen Index treffen, anstatt ein Zeichenarray zu durchlaufen). Linq unterstützt dies nicht. Aufrufe von .Contains () treffen auf die nicht verwalteten Zeichenfolgenfunktionen, profitieren jedoch nicht von der Indizierung.


0

Ich habe einen funktionierenden Prototyp erstellt, nur für SQL Server CONTAINS und keine Platzhalterspalten. Damit können Sie CONTAINS wie normale LINQ-Funktionen verwenden:

var query = context.CreateObjectSet<MyFile>()
    .Where(file => file.FileName.Contains("pdf")
        && FullTextFunctions.ContainsBinary(file.FileTable_Ref.file_stream, "Hello"));

Du wirst brauchen:

1. Funktionsdefinitionen in Code und EDMX zur Unterstützung des Schlüsselworts CONTAINS .

2. Schreiben Sie EF SQL mit EFProviderWrapperToolkit / EFTracingProvider neu, da CONTAINS keine Funktion ist und das generierte SQL das Ergebnis standardmäßig als Bit behandelt .

ABER:

1.Contains ist nicht wirklich eine Funktion und Sie können keine booleschen Ergebnisse daraus auswählen. Es kann nur unter Bedingungen verwendet werden.

2.Der unten stehende SQL-Umschreibcode wird wahrscheinlich unterbrochen, wenn Abfragen nicht parametrisierte Zeichenfolgen mit Sonderzeichen enthalten.

Quelle meines Prototyps

Funktionsdefinitionen: (EDMX)

Unter edmx: StorageModels / Schema

<Function Name="conTAINs" BuiltIn="true" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" ReturnType="bit" Schema="dbo">
    <Parameter Name="dataColumn" Type="varbinary" Mode="In" />
    <Parameter Name="keywords" Type="nvarchar" Mode="In" />
</Function>
<Function Name="conTAInS" BuiltIn="true" IsComposable="true" ParameterTypeSemantics="AllowImplicitConversion" ReturnType="bit" Schema="dbo">
    <Parameter Name="textColumn" Type="nvarchar" Mode="In" />
    <Parameter Name="keywords" Type="nvarchar" Mode="In" />
</Function>

PS: Die seltsamen Fälle von Zeichen werden verwendet, um dieselbe Funktion mit verschiedenen Parametertypen (varbinary und nvarchar) zu aktivieren.

Funktionsdefinitionen: (Code)

using System.Data.Objects.DataClasses;

public static class FullTextFunctions
{
    [EdmFunction("MyModel.Store", "conTAINs")]
    public static bool ContainsBinary(byte[] dataColumn, string keywords)
    {
        throw new System.NotSupportedException("Direct calls are not supported.");
    }

    [EdmFunction("MyModel.Store", "conTAInS")]
    public static bool ContainsString(string textColumn, string keywords)
    {
        throw new System.NotSupportedException("Direct calls are not supported.");
    }
}

PS: "MyModel.Store" entspricht dem Wert in edmx: StorageModels / Schema / @ Namespace

Schreiben Sie EF SQL neu: (von EFProviderWrapperToolkit)

using EFProviderWrapperToolkit;
using EFTracingProvider;

public class TracedMyDataContext : MyDataContext
{
    public TracedMyDataContext()
        : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(
            "name=MyDataContext", "EFTracingProvider"))
    {
        var tracingConnection = (EFTracingConnection) ((EntityConnection) Connection).StoreConnection;
        tracingConnection.CommandExecuting += TracedMyDataContext_CommandExecuting;
    }

    protected static void TracedMyDataContext_CommandExecuting(object sender, CommandExecutionEventArgs e)
    {
        e.Command.CommandText = FixFullTextContainsBinary(e.Command.CommandText);
        e.Command.CommandText = FixFullTextContainsString(e.Command.CommandText);
    }


    private static string FixFullTextContainsBinary(string commandText, int startIndex = 0)
    {
        var patternBeg = "(conTAINs(";
        var patternEnd = ")) = 1";
        var exprBeg = commandText.IndexOf(patternBeg, startIndex, StringComparison.Ordinal);
        if (exprBeg == -1)
            return commandText;
        var exprEnd = FindEnd(commandText, exprBeg + patternBeg.Length, ')');
        if (commandText.Substring(exprEnd).StartsWith(patternEnd))
        {
            var newCommandText = commandText.Substring(0, exprEnd + 2) + commandText.Substring(exprEnd + patternEnd.Length);
            return FixFullTextContainsBinary(newCommandText, exprEnd + 2);
        }
        return commandText;
    }

    private static string FixFullTextContainsString(string commandText, int startIndex = 0)
    {
        var patternBeg = "(conTAInS(";
        var patternEnd = ")) = 1";
        var exprBeg = commandText.IndexOf(patternBeg, startIndex, StringComparison.Ordinal);
        if (exprBeg == -1)
            return commandText;
        var exprEnd = FindEnd(commandText, exprBeg + patternBeg.Length, ')');
        if (exprEnd != -1 && commandText.Substring(exprEnd).StartsWith(patternEnd))
        {
            var newCommandText = commandText.Substring(0, exprEnd + 2) + commandText.Substring(exprEnd + patternEnd.Length);
            return FixFullTextContainsString(newCommandText, exprEnd + 2);
        }
        return commandText;
    }

    private static int FindEnd(string commandText, int startIndex, char endChar)
    {
        // TODO: handle escape chars between parens/squares/quotes
        var lvlParan = 0;
        var lvlSquare = 0;
        var lvlQuoteS = 0;
        var lvlQuoteD = 0;
        for (var i = startIndex; i < commandText.Length; i++)
        {
            var c = commandText[i];
            if (c == endChar && lvlParan == 0 && lvlSquare == 0
                && (lvlQuoteS % 2) == 0 && (lvlQuoteD % 2) == 0)
                return i;
            switch (c)
            {
                case '(':
                    ++lvlParan;
                    break;
                case ')':
                    --lvlParan;
                    break;
                case '[':
                    ++lvlSquare;
                    break;
                case ']':
                    --lvlSquare;
                    break;
                case '\'':
                    ++lvlQuoteS;
                    break;
                case '"':
                    ++lvlQuoteD;
                    break;
            }
        }
        return -1;
    }
}

Aktivieren Sie das EFProviderWrapperToolkit:

Wenn Sie es per Nuget erhalten, sollten diese Zeilen in Ihre app.config oder web.config eingefügt werden:

<system.data>
    <DbProviderFactories>
        <add name="EFTracingProvider" invariant="EFTracingProvider" description="Tracing Provider Wrapper" type="EFTracingProvider.EFTracingProviderFactory, EFTracingProvider, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b" />
        <add name="EFProviderWrapper" invariant="EFProviderWrapper" description="Generic Provider Wrapper" type="EFProviderWrapperToolkit.EFProviderWrapperFactory, EFProviderWrapperToolkit, Version=1.0.0.0, Culture=neutral, PublicKeyToken=def642f226e0e59b" />
    </DbProviderFactories>
</system.data>
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.