Có thể sử dụng Tìm kiếm Toàn văn (FTS) với LINQ không?


76

Tôi tự hỏi liệu có thể sử dụng FTS với LINQ bằng .NET Framework 3.5 không. Tôi đang tìm kiếm tài liệu mà tôi chưa tìm thấy bất kỳ điều gì hữu ích.

Có ai có bất kỳ kinh nghiệm về điều này?

Câu trả lời:


77

Đúng. Tuy nhiên, bạn phải tạo hàm máy chủ SQL trước và gọi hàm đó theo mặc định, LINQ sẽ sử dụng hàm like.

Bài đăng trên blog này sẽ giải thích chi tiết nhưng đây là phần trích dẫn:

Để làm cho nó hoạt động, bạn cần tạo một hàm có giá trị bảng không làm gì khác hơn là một truy vấn CÓ THỂ CHỨA dựa trên các từ khóa bạn chuyển vào,

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

Sau đó, bạn thêm chức năng này vào mô hình SQL LINQ 2 của mình và trước tiên bạn có thể viết các truy vấn như thế nào.

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

12

Không. Tìm kiếm toàn văn không được hỗ trợ bởi LINQ To SQL.

Điều đó nói rằng, bạn có thể sử dụng một thủ tục được lưu trữ sử dụng FTS và truy vấn LINQ To SQL lấy dữ liệu từ đó.


10

nếu bạn không muốn tạo nối và muốn đơn giản hóa mã C # của mình, bạn có thể tạo hàm SQL và sử dụng nó trong mệnh đề "from":

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

Sau khi cập nhật DBML của bạn, hãy sử dụng nó trong linq:

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

Điều này sẽ tạo ra SQL đơn giản như sau:

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

Điều này hoạt động trong tìm kiếm theo một số cột như bạn có thể thấy từ việc triển khai hàm ad_Search.


9

Tôi không tin như vậy. Bạn có thể sử dụng 'chứa' trên một trường, nhưng nó chỉ tạo ra một LIKEtruy vấn. Nếu bạn muốn sử dụng toàn văn, tôi khuyên bạn nên sử dụng một proc được lưu trữ để thực hiện truy vấn rồi chuyển lại cho LINQ


5

Không, tìm kiếm toàn văn là một cái gì đó rất cụ thể đối với máy chủ sql (trong đó văn bản được lập chỉ mục bởi các từ và các truy vấn đánh vào chỉ mục này so với duyệt qua một mảng ký tự). Linq không hỗ trợ điều này, bất kỳ lệnh gọi .Contains () nào sẽ đánh vào các hàm chuỗi không được quản lý nhưng sẽ không có lợi từ việc lập chỉ mục.


0

Tôi đã tạo một nguyên mẫu đang hoạt động, chỉ dành cho CONTAINS của SQL Server và không có cột ký tự đại diện. Những gì nó đạt được là bạn có thể sử dụng CONTAINS như các hàm LINQ thông thường:

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

Bạn sẽ cần:

1. Định nghĩa chức năng trong mã và EDMX để hỗ trợ CONTAINS từ khóa .

2. Viết lại EF SQL bằng EFProviderWrapperToolkit / EFTracingProvider, vì CONTAINS không phải là một hàm và theo mặc định, SQL được tạo xử lý kết quả của nó dưới dạng bit .

NHƯNG:

1.Contains không thực sự là một hàm và bạn không thể chọn kết quả boolean từ nó. Nó chỉ có thể được sử dụng trong điều kiện.

2. Mã viết lại SQL bên dưới có thể bị hỏng nếu các truy vấn chứa các chuỗi không được tham số hóa với các ký tự đặc biệt.

Nguồn nguyên mẫu của tôi

Định nghĩa hàm: (EDMX)

Theo 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: các trường hợp ký tự kỳ lạ được sử dụng để kích hoạt cùng một chức năng với các kiểu tham số khác nhau (varbinary và nvarchar)

Định nghĩa hàm: (mã)

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

Tái bút : "MyModel.Store" giống như giá trị trong edmx: StorageModels / Schema / @ Namespace

Viết lại EF SQL: (bởi 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;
    }
}

Bật EFProviderWrapperToolkit:

Nếu bạn nhận được nó bằng nuget, nó sẽ thêm những dòng này vào app.config hoặc web.config của bạn:

<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>
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.