是否可以将全文搜索 (FTS) 与 LINQ 一起使用?

Posted

技术标签:

【中文标题】是否可以将全文搜索 (FTS) 与 LINQ 一起使用?【英文标题】:Is it possible to use Full Text Search (FTS) with LINQ? 【发布时间】:2010-09-18 11:27:10 【问题描述】:

我想知道是否可以使用 .NET Framework 3.5 将 FTS 与 LINQ 一起使用。我正在搜索尚未发现任何有用的文档。

有人有这方面的经验吗?

【问题讨论】:

【参考方案1】:

我不相信。您可以在字段上使用“包含”,但它只会生成 LIKE 查询。如果您想使用全文,我建议您使用存储过程进行查询,然后将其传递回 LINQ

【讨论】:

【参考方案2】:

不,全文搜索是 sql server 特有的(其中文本由单词索引,查询命中该索引而不是遍历字符数组)。 Linq 不支持这一点,任何 .Contains() 调用都会命中非托管字符串函数,但不会从索引中受益。

【讨论】:

【参考方案3】:

没有。 LINQ To SQL 不支持全文搜索。

也就是说,您可以使用利用 FTS 的存储过程,并让 LINQ To SQL 查询从中提取数据。

【讨论】:

【参考方案4】:

是的。但是,您必须先创建 SQL 服务器函数并调用它,因为默认情况下 LINQ 将使用 like。

这个blog post会解释细节,但这是摘录:

要让它工作,你需要创建一个表值函数 只不过是基于您传递的关键字的 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;

【讨论】:

【参考方案5】:

如果您不想创建连接并想简化您的 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 函数实现中可以看出,这适用于按多列搜索。

【讨论】:

【参考方案6】:

我为 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.代码和EDMX中的函数定义支持CONTAINS关键字。

2.EFProviderWrapperToolkit/EFTracingProvider重写EF SQL,因为CONTAINS不是函数,默认生成的SQL将其结果视为bit

但是:

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:chars 的奇怪情况用于启用具有不同参数类型(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>

【讨论】:

以上是关于是否可以将全文搜索 (FTS) 与 LINQ 一起使用?的主要内容,如果未能解决你的问题,请参考以下文章

Peewee 可以使用 SQLite 的 FTS5(全文搜索)辅助函数 highlight() 吗?

SQlite 全文搜索(FTS)?

使用 LLBLGen 时如何在 Linq 查询中进行全文搜索

PhoneGap、SQLite 和全文搜索

使用全文本搜索 (FTS) 在多个列中搜索多个标记,使用 OR 运算符

SQLite第三课 源码编译错误以及解决