Pregunta

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

¿Alguien tiene alguna experiencia en esto?

¿Fue útil?

Solución

Sí. Sin embargo, primero tiene que crear la función del servidor SQL y llamar a eso, ya que, de forma predeterminada, LINQ utilizará un similar.

Esto publicación de blog que explicará los detalles pero este es el extracto:

  

Para que funcione, necesitas crear una función con valores de tabla que lo haga.   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, agrega esta función a su modelo LINQ 2 SQL y él está listo.   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;

Otros consejos

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

Dicho esto, usted puede usar un procedimiento almacenado que utiliza FTS y tiene la consulta LINQ To SQL extrayendo datos de eso.

No lo creo. Puede usar 'contiene' en un campo, pero solo genera una consulta LIKE . Si desea utilizar texto completo, le recomendaría que utilice un procedimiento almacenado para realizar la consulta y luego devuélvalo a LINQ

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

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, utilícelo en linq:

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

Esto producirá un SQL simple como este:

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

Esto funciona en la búsqueda por varias columnas, como se puede ver en la implementación de la función ad_Search.

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

Hice un prototipo funcional, solo para CONTAINS de SQL Server y sin columnas comodín. Lo que logra es que uses CONTAINS como las funciones LINQ comunes:

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

Necesitarás:

1.Funciona las definiciones en el código y EDMX para admitir la palabra clave CONTAINS .

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

PERO:

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

2.El código de reescritura de SQL a continuación se romperá si las consultas contienen cadenas no parametrizadas con caracteres especiales.

Fuente de mi prototipo

Definiciones de funciones: (EDMX)

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

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

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

Habilitar EFProviderWrapperToolkit:

Si lo obtiene por nuget, debería agregar estas líneas en su 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>
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top