Je me demande s'il est possible d'utiliser FTS avec LINQ en utilisant .NET Framework 3.5. Je cherche dans la documentation que je n'ai encore rien trouvé d'utile.
Quelqu'un at-il une expérience à ce sujet?
Oui. Cependant, vous devez d'abord créer la fonction de serveur SQL et l'appeler car, par défaut, LINQ utilisera une fonction similaire.
Ce article de blog qui expliquera les détails mais voici l'extrait:
Pour le faire fonctionner, vous devez créer une fonction de valeur de table qui ne fait rien de plus qu'une requête CONTAINSTABLE basée sur les mots clés que vous transmettez,
create function udf_sessionSearch (@keywords nvarchar(4000)) returns table as return (select [SessionId],[rank] from containstable(Session,(description,title),@keywords))
Vous ajoutez ensuite cette fonction à votre modèle SQL LINQ 2 et il vous permet désormais d'écrire des requêtes comme.
var sessList = from s in DB.Sessions join fts in DB.udf_sessionSearch(SearchText) on s.sessionId equals fts.SessionId select s;
Non. La recherche en texte intégral n'est pas prise en charge par LINQ To SQL.
Cela dit, vous pouvez utilisez une procédure stockée qui utilise FTS et demandez à la requête LINQ To SQL d'extraire des données de cela.
si vous ne souhaitez pas créer de jointures et que vous souhaitez simplifier votre code C #, vous pouvez créer une fonction SQL et l'utiliser dans la clause "from":
CREATE FUNCTION ad_Search
(
@keyword nvarchar(4000)
)
RETURNS TABLE
AS
RETURN
(
select * from Ad where
(CONTAINS(Description, @keyword) OR CONTAINS(Title, @keyword))
)
Après avoir mis à jour votre DBML, utilisez-le dans linq:
string searchKeyword = "Word and subword";
var result = from ad in context.ad_Search(searchKeyword)
select ad;
Cela produira du SQL simple comme ceci:
SELECT [t0].ID, [t0].Title, [t0].Description
FROM [dbo].[ad_Search](@p0) AS [t0]
Cela fonctionne dans la recherche par plusieurs colonnes comme vous pouvez le voir dans l'implémentation de la fonction ad_Search.
Je ne le crois pas. Vous pouvez utiliser "contient" sur un champ, mais il ne génère qu'une requête LIKE
. Si vous souhaitez utiliser le texte intégral, je vous recommande d'utiliser un proc stocké pour effectuer la requête, puis de le renvoyer à LINQ
Non, la recherche en texte intégral est quelque chose de très spécifique au serveur SQL (dans lequel le texte est indexé par des mots et les requêtes atteignent cet index par rapport à la traversée d'un tableau de caractères). Linq ne prend pas en charge cela, tous les appels .Contains () atteindront les fonctions de chaîne non gérées mais ne bénéficieront pas de l'indexation.
J'ai fait un prototype fonctionnel, pour SQL Server CONTIENT uniquement et aucune colonne générique. Ce qu'il permet de faire est d'utiliser CONTIENT comme les fonctions LINQ ordinaires:
var query = context.CreateObjectSet<MyFile>()
.Where(file => file.FileName.Contains("pdf")
&& FullTextFunctions.ContainsBinary(file.FileTable_Ref.file_stream, "Hello"));
1. Définitions des fonctions dans le code et EDMX pour prendre en charge le mot clé CONTIENT .
2.Réécrivez EF SQL par EFProviderWrapperToolkit/EFTracingProvider, car CONTAINS n'est pas une fonction et par défaut le SQL généré traite son résultat comme bit.
1.Contains n'est pas vraiment une fonction et vous ne pouvez pas en sélectionner de résultats booléens. Il ne peut être utilisé que dans des conditions.
Le code de réécriture SQL ci-dessous est susceptible de se casser si les requêtes contiennent des chaînes non paramétrées avec des caractères spéciaux.
Sous 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: les cas étranges de caractères sont utilisés pour activer la même fonction avec différents types de paramètres (varbinary et 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" est identique à la valeur dans edmx: StorageModels/Schema/@ Namespace
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;
}
}
Si vous l'obtenez par nuget, il devrait ajouter ces lignes dans votre app.config ou 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>