web-dev-qa-db-ja.com

LINQで全文検索(FTS)を使用することは可能ですか?

.NET Framework 3.5を使用して、LINQでFTSを使用することは可能ですか?まだ役に立たないドキュメントを探しています。

誰かこれについて何か経験がありますか?

75
Edwin Jarvis

はい。ただし、最初にSQLサーバー関数を作成し、それを呼び出す必要があります。デフォルトでは、LINQは同様のものを使用します。

詳細 ブログ投稿 詳細を説明しますが、これは抜粋です:

これを機能させるには、渡すキーワードに基づいてCONTAINSTABLEクエリ以外に何もしないテーブル値関数を作成する必要があります。

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

次に、この関数をLINQ 2 SQLモデルに追加すると、彼はそのようなクエリを記述できるようになります。

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

いいえ。全文検索はLINQ To SQLではサポートされていません。

つまり、あなたはcan FTSを使用するストアドプロシージャを使用し、そこからLINQ To SQLクエリでデータを取得します。

12

結合を作成せず、C#コードを簡略化したい場合は、SQL関数を作成して「from」句で使用できます。

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

DBMLを更新したら、linqで使用します。

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

これにより、次のような単純なSQLが生成されます。

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

これは、ad_Search関数の実装からわかるように、いくつかの列による検索で機能します。

9

私はそうは思いません。フィールドで「contains」を使用できますが、生成されるのはLIKEクエリのみです。フルテキストを使用したい場合は、ストアドプロシージャを使用してクエリを実行し、それをLINQに戻すことをお勧めします

9
Glenn Slaven

いいえ、全文検索はSQLサーバーに非常に固有のものです(テキストには単語でインデックスが付けられており、クエリはこの配列にヒットするのではなく、文字配列を走査します)。 Linqはこれをサポートしていません。Contains()呼び出しは、管理されていない文字列関数にヒットしますが、インデックス付けのメリットはありません。

5
Will

SQL Serverの[〜#〜] contains [〜#〜]のみを使用し、ワイルドカード列を使用しないように、プロトタイプを作成しました。それが達成することは、あなたが通常のLINQ関数のように[〜#〜] contains [〜#〜]を使用することです:

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

必要になるだろう:

1. [〜#〜] contains [〜#〜]キーワードをサポートするコードとEDMXの関数定義。

2. CONTAINSは関数ではなく、デフォルトで生成されたSQLは結果をbitとして扱うため、EFProviderWrapperToolkit/EFTracingProviderでEF SQLを書き換えます。

だが:

1.Containsは実際には関数ではなく、そこからブール結果を選択することはできません。条件でのみ使用できます。

2.クエリに特殊文字を含むパラメータ化されていない文字列が含まれている場合、以下のSQL書き換えコードは壊れる可能性があります。

私のプロトタイプのソース

関数定義:(EDMX)

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:文字の奇妙なケースは、異なるパラメータータイプ(varbinaryとnvarchar)で同じ関数を有効にするために使用されます

関数定義:(コード)

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"は、edmx:StorageModels/Schema/@ Namespaceの値と同じです

EF SQLの書き換え:(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;
    }
}

EFProviderWrapperToolkitを有効にします。

Nugetで取得した場合は、app.configまたはweb.configに次の行を追加する必要があります。

<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>
0
AqD