¿Es posible utilizar la búsqueda de texto completo (FTS) con LINQ?
-
03-07-2019 - |
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?
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>