sql server - test - ¿Es posible utilizar la búsqueda de texto completo(FTS) con LINQ?
sql server to linq (6)
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?
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 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 código y EDMX para soportar la palabra clave CONTAINS .
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 un bit .
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>
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.
No. La búsqueda de texto completo no es compatible con LINQ To SQL.
Dicho esto, puede usar un procedimiento almacenado que utiliza FTS y hacer que la consulta LINQ To SQL extraiga datos de eso.
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 publicación de blog que explicará los detalles, pero este es el extracto:
Para que funcione, debe crear una función con valores de tabla que no haga más que una consulta CONTAINSTABLE basada en las palabras clave que ingresa,
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 de SQL de LINQ 2 y 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;
Yo no lo creo. Puede usar ''contiene'' en un campo, pero solo genera una consulta LIKE
. Si desea utilizar el texto completo, le recomendaría usar un proceso almacenado para hacer la consulta y luego pasarlo a LINQ
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.