Pregunta ¿Es posible utilizar la búsqueda de texto completo (FTS) con LINQ?


Me pregunto si es posible usar FTS con LINQ usando .NET Framework 3.5. Estoy buscando en la documentación que aún no encontré nada útil.

¿Alguien tiene alguna experiencia en esto?


74
2017-10-22 04:16


origen


Respuestas:


Sí. Sin embargo, primero tiene que crear la función del servidor SQL y llamar a eso porque, de manera predeterminada, LINQ usará un me gusta.

Esta entrada en el blog que explicará el detalle pero este es el extracto:

Para que funcione, debe crear una función de tabla con valores   nada más que una consulta CONTAINSTABLE basada en las palabras clave que pasa   en,

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

A continuación, agregue esta función a su modelo LINQ 2 SQL y le presto   ahora puede escribir consultas como.

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

75
2017-12-22 08:52



No. La búsqueda de texto completo no es compatible con LINQ To SQL.

Dicho eso, tú poder utilice un procedimiento almacenado que utilice FTS y haga que la consulta LINQ To SQL extraiga datos de eso.


12
2017-10-22 21:45



Yo no lo creo. Puede usar 'contiene' en un campo, pero solo genera un LIKE consulta. Si desea utilizar el texto completo, le recomendaría usar un proceso almacenado para hacer la consulta y luego pasarlo a LINQ


9
2017-10-22 04:22



si no desea crear uniones y desea simplificar su código C #, puede crear una función SQL y usarla en la cláusula "de":

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

Después de actualizar su DBML, úselo en linq:

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

Esto producirá SQL simple como este:

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

Se trata de trabajos en búsqueda por varias columnas, como puede ver en la implementación de la función ad_Search.


9
2017-11-19 06:54



No, la búsqueda de texto completo es algo muy específico del servidor sql (en el que el texto se indexa por palabras, y las consultas tocan este índice en lugar de atravesar una matriz de caracteres). Linq no es compatible con esto, cualquier llamada .Contains () golpeará las funciones de cadena no administradas, pero no se beneficiará de la indexación.


4
2017-10-22 21:42



Hice un prototipo funcional, para SQL Server CONTIENE solo y sin columnas de comodines. Lo que logra es que lo uses CONTIENE como las funciones LINQ normales:

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

Necesitará:

1. Definiciones de funciones en el código y EDMX para apoyar el CONTIENE palabra clave.

2. Reescriba EF SQL por EFProviderWrapperToolkit / EFTracingProvider, porque CONTAINS no es una función y, de forma predeterminada, el SQL generado trata su resultado como poco.

PERO:

1.Contains no es realmente una función y no puede seleccionar resultados booleanos de ella. Solo se puede usar en condiciones.

2. Es probable que el código de reescritura SQL a continuación se rompa si las consultas contienen cadenas no parametrizadas con caracteres especiales.

Fuente de mi prototipo

Definiciones de funciones: (EDMX)

En 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>

PD: los casos extraños de caracteres se utilizan para habilitar la misma función con diferentes tipos de parámetros (varbinary y nvarchar)

Definiciones de funciones: (código)

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

PD: "MyModel.Store" es igual que el valor en edmx: StorageModels / Schema / @ Namespace

Reescribe EF SQL: (por 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;
    }
}

Habilite EFProviderWrapperToolkit:

Si lo obtienes por nuget, debería agregar estas líneas en tu app.config o 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
2017-07-31 11:31