programing

LINQ에서 전체 텍스트 검색(FTS)을 사용할 수 있습니까?

goodsources 2023. 6. 25. 18:45
반응형

LINQ에서 전체 텍스트 검색(FTS)을 사용할 수 있습니까?

.NET Framework 3.5를 사용하여 LINQ와 함께 FTS를 사용할 수 있는지 궁금합니다.저는 아직 유용한 것을 찾지 못한 문서를 검색하고 있습니다.

이것에 대해 경험이 있는 사람이 있습니까?

예. 그러나 SQL 서버 함수를 먼저 만들고 기본적으로 LINQ에서 like를 사용하도록 호출해야 합니다.

자세한 내용을 설명하는 이 블로그 게시물은 다음과 같습니다.

그것을 작동시키려면 당신이 전달한 키워드를 기반으로 CONTENTSTABLE 쿼리 이상을 수행하지 않는 테이블 값 함수를 만들어야 합니다.

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;

아니요. 전체 텍스트 검색은 LINQ To SQL에서 지원되지 않습니다.

즉, FTS를 활용하는 저장 프로시저를 사용하여 LINQ To SQL 쿼리를 통해 데이터를 가져올 수 있습니다.

조인을 생성하지 않고 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 함수 구현에서 볼 수 있듯이 여러 열에서 검색할 수 있습니다.

그렇게 생각하지 않습니다.필드에서 '포함'을 사용할 수 있지만, 이는 다음 항목만 생성합니다.LIKE쿼리. 만약 당신이 전체 텍스트를 사용하고 싶다면, 나는 쿼리를 하기 위해 저장된 프로시저를 사용하는 것을 추천합니다. 그런 다음 그것을 LINQ로 다시 전달합니다.

아니요, 전체 텍스트 검색은 SQL 서버에 매우 특정한 기능입니다(텍스트는 단어로 인덱싱되고 쿼리는 문자 배열을 통과하는 것이 아니라 이 인덱스에 도달합니다).Linq는 이것을 지원하지 않습니다.Contains() 호출은 관리되지 않는 문자열 함수에 영향을 주지만 인덱싱의 이점은 없습니다.

저는 SQL Server의 Contains 전용이고 와일드카드 열이 없는 작동 프로토타입을 만들었습니다.일반적인 LINQ 함수처럼 CONTINES를 사용할 수 있습니다.

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

다음이 필요합니다.

1. CONTENS 키워드를 지원하는 코드 및 EDMX의 함수 정의.

2.EFProviderWrapper의 EF SQL 다시 쓰기툴킷/EContains는 함수가 아니므로 기본적으로 생성된 SQL은 결과를 비트로 처리합니다.

그러나:

1.포함은 실제 함수가 아니므로 해당 함수에서 부울 결과를 선택할 수 없습니다.조건에서만 사용할 수 있습니다.

2. 쿼리에 특수 문자가 포함된 비모수 문자열이 포함된 경우 아래 SQL 다시 쓰기 코드가 손상될 수 있습니다.

내 프로토타입의 출처

기능 정의: (EDMX)

edmx 아래:스토리지 모델/스키마

<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)으로 동일한 기능을 활성화하기 위해 이상한 경우의 char가 사용됩니다.

함수 정의: (코드)

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: "내 모델.Store"는 edmx의 값과 동일합니다.스토리지 모델/Schema/@Namespace

EF SQL 다시 쓰기: (EFProviderWrapper 사용)툴킷)

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

EF 제공자 래퍼 사용툴킷:

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>

언급URL : https://stackoverflow.com/questions/224475/is-it-possible-to-use-full-text-search-fts-with-linq

반응형