web-dev-qa-db-fra.com

Recherche générique pour LINQ

Je voudrais savoir s'il est possible d'effectuer une recherche générique à l'aide de LINQ.

Je vois que LINQ a Contains, StartsWith, EndsWith, etc.

Et si je veux quelque chose comme% Test si% ça marche%, comment faire?

Cordialement

35
PlayKid

J'utiliserais des expressions régulières, car vous n'utilisez peut-être pas toujours Linq to SQL.

Comme cet exemple de Linq to Objects

List<string> list = new List<string>();
list.Add("This is a sentence.");
list.Add("This is another one.");
list.Add("C# is fun.");
list.Add("Linq is also fun.");

System.Text.RegularExpressions.Regex regEx = new System.Text.RegularExpressions.Regex("This");

var qry = list
    .Where<string>(item => regEx.IsMatch(item))
    .ToList<string>();

// Print results
foreach (var item in qry)
{
    Console.WriteLine(item);
}
34
David Basarab

Vous pouvez utiliser SqlMethods.Like () .

Un exemple d'utilisation:

var results =
        from u in users
        where SqlMethods.Like(u.FirstName, "%John%")
        select u;
77
Ryan Versaw

ajoutez System.Data.Linq.SqlClient à votre liste d'utilisation ou d'importation, puis essayez:

var results= from x in data
             where SqlMethods.Like(x.SearchField, “%something%like%this%”)
             select x;
14
Joe Davis

Pour Entity Framework Core 2.0 il y a l'opérateur LIKE ( annoncé en août 2017 ):

var query = from e in _context.Employees
                    where EF.Functions.Like(e.Title, "%developer%")
                    select e;
8
Dmitry Pavlov

En regardant la question

Et si je veux quelque chose comme% Test si% ça marche%, comment faire?

alors j'attends quelque chose de

LIKE '%Test if%it work%'

ce qui signifie que la chaîne doit contenir "Test if" et "it work", dans cet ordre.

Cela ne fonctionnera pas:

context.SomeTable.Where(s => s.Name.Contains("Test if%it work")).ToList();

Et si j'utilise:

context.SomeTable.Where(s => s.Name.Contains("Test if") && s.Name.Contains("it work")).ToList();

alors je trouverai tous les enregistrements qui contiennent à la fois "Test si" et "ça marche", mais pas spécifiquement dans cet ordre .

Donc avec Contient ce n'est pas possible. Mais avec IndexOf c'est le cas.

IndexOf localisera la chaîne de recherche ET renverra sa position dans la chaîne. Permet de retrouver les mots dans le bon ordre.

-- Mise à jour --

Avec ma réponse originale, ce n'était pas mon objectif de fournir une solution générique, mais plutôt un exemple d'une autre approche qui ne dépend pas de SQL. Il est donc exact que l'exemple original ne répond qu'à la question littérale. Mais comme la réponse peut être plus utile si elle est générique, j'ai écrit une extension IQuerable qui permet d'ajouter une instruction similaire à la requête aussi simple qu'une instruction where. L'extension fonctionne pour Linq comme Linq-Sql.

Cela trouvera tous les enregistrements avec à la fois "Test si" et "ça marche", dans cet ordre.

context.SomeTable.Like("test if%it work", "Name").ToList();

listOfString.Like("test if%it work").ToList();

Extension, permet un nombre illimité de caractères génériques:

/// <summary>
/// Allow to search the string with wildcards.
/// </summary>
/// <typeparam name="T">String or an object with a string member.</typeparam>
/// <param name="q">Original query</param>
/// <param name="searchstring">The searchstring</param>
/// <param name="memberName">The name of the field or null if not a field.</param>
/// <returns>Query filtered by 'LIKE'.</returns>
public static IQueryable<T> Like<T>(this IQueryable<T> q, string searchstring, string memberName = null)
{
    // %a%b%c% --> IndexOf(a) > -1 && IndexOf(b) > IndexOf(a) && IndexOf(c) > IndexOf(b)

    var eParam = Expression.Parameter(typeof(T), "e");

    MethodInfo methodInfo;

    // Linq (C#) is case sensitive, but sql isn't. Use StringComparison ignorecase for Linq.
    // Sql however doesn't know StringComparison, so try to determine the provider.
    var isLinq = (q.Provider.GetType().IsGenericType && q.Provider.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>));
    if (isLinq)
        methodInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) });
    else
        methodInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string) });

    Expression expr;
    if (string.IsNullOrEmpty(memberName))
        expr = eParam;
    else
        expr = Expression.Property(eParam, memberName);

    // Split the searchstring by the wildcard symbol:
    var likeParts = searchstring.Split(new char[] { '%' }, StringSplitOptions.RemoveEmptyEntries);

    for (int i = 0; i < likeParts.Length; i++)
    {
        MethodCallExpression e;
        if (isLinq)
            e = Expression.Call(expr, methodInfo, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) });
        else
            e = Expression.Call(expr, methodInfo, Expression.Constant(likeParts[i], typeof(string)));

        if (i == 0)
        {
            // e.IndexOf("likePart") > -1
            q = q.Where(Expression.Lambda<Func<T, bool>>(Expression.GreaterThan(e, Expression.Constant(-1, typeof(int))), eParam));
        }
        else
        {
            // e.IndexOf("likePart_previous")
            MethodCallExpression ePrevious;
            if (isLinq)
                ePrevious = Expression.Call(expr, methodInfo, new Expression[] { Expression.Constant(likeParts[i - 1], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) });
            else
                ePrevious = Expression.Call(expr, methodInfo, Expression.Constant(likeParts[i - 1], typeof(string)));

            // e.IndexOf("likePart_previous") < e.IndexOf("likePart")
            q = q.Where(Expression.Lambda<Func<T, bool>>(Expression.LessThan(ePrevious, e), eParam));
        }
    }
    return q;
}

Puisqu'il n'a pas besoin de SqlMethods, je suppose que vous pouvez l'utiliser pour n'importe quelle base de données, comme MySql ou Postgresql. Mais je n'en suis pas sûr. J'ai testé cela avec Sql Server en utilisant Entity Framework 6. L'instruction ci-dessus génère le code suivant dans Sql Server.

SELECT [Extent1].* FROM SomeTable AS [Extent1]
WHERE ((( CAST(CHARINDEX(N'test if', [Extent1].[Name]) AS int)) - 1) > -1)
AND ((( CAST(CHARINDEX(N'test if', [Extent1].[Name]) AS int)) - 1) < 
     (( CAST(CHARINDEX(N'it work', [Extent1].[Name]) AS int)) - 1))

Concernant les performances, il semble y avoir une discussion sur ce qui est "mieux": LIKE ou CHARINDEX. Et d'après ce que j'ai lu, CHARINDEX semble être le favori.

4
Ruard van Elburg
.Where( column LIKE "Pattern")
2
Rony
var result = (from x in db.Members
              where x.IDNumber.Contains(idnumber)
              && x.InstitutionIdentifier == institution.Identifier
              select x).ToList();
return result;

Fonctionne à la fois pour Linq vers SQL et Linq en mémoire.

2
Gerhard

Je sais que c'est un vieux sujet, mais voici ma solution très simple:

string s=Regex.Escape("pattern - escaped for sanity").Replace("%", ".*").Replace("_", ".?");
user => Regex.IsMatch(user.FullName, s, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);

Dans ce code, j'utilise des caractères d'échappement communs pour le langage SQL. Si vous souhaitez utiliser par exemple * Et ?, La chaîne d'échappement contiendra \* Et \? En conséquence, assurez-vous d'inclure le barre oblique inverse caractère dans les instructions .Replace(...). Bien sûr, si vous souhaitez donner à votre utilisateur la possibilité de rechercher RexEx, n'échappez pas à la chaîne de modèle.

Recherchez dans le didacticiel Regex d'autres options.

Je crois que normalement % Correspondra à au moins un caractère, tandis que le RegEx .* Correspondra à zéro ou plus caractères. Donc en réalité, le caractère générique % Ressemble plus à .+ (Gourmand) plutôt qu'à .* (Paresseux).

J'espère que cela t'aides.

2
nurchi

je ne sais pas si vous parlez LinqToSql ou simplement linq ... mais vous pourriez des expressions régulières comme ceci:

.Where(dto => System.Text.RegularExpressions.Regex.IsMatch(dto.CustomerName, @"Ad"));
1
bytebender

Dans le code .Net, y compris LINQ to Objects, j'utilise l'implémentation de la fonction IsSqlLikeMatch à partir du thread tilisation de Regex pour créer un SQL "like" comme fonction. .

Exemple d'utilisation

bool ret = message.IsSqlLikeMatch(pattern);

Plus de détails dans mon article modèles "similaires" de SQL à comparer dans .Net

1

Vous pouvez également utiliser "contient"

var myresult = db.MyItems.Where(x=>x.MyField.Contains(mysearchstring));
1
spadelives

Parlez-vous LINQ to objects ou LINQ to SQL?

Pour LINQ aux objets, vous devrez recourir à expressions régulières me pense.

0
fretje

Je l'utilise pour prendre en charge un filtre générique de "*" dans la recherche d'un utilisateur. (l'ordre n'a pas d'importance):

 if (!string.IsNullOrEmpty(SearchString))
    {
     List<String> containValues = new List<String>();
     if (SearchString.Contains("*"))
        {

        String[] pieces = SearchString.Split("*");

        foreach (String piece in pieces)
                {
                if (piece != "")
                   {
                   containValues.Add(piece);
                   }
                 }
           }

       if (containValues.Count > 0)
          {
          foreach(String thisValue in containValues)
             {
             Items = Items.Where(s => s.Description.Contains(thisValue));
             }
           }
           else
           {
           Items = Items.Where(s => s.Description.Contains(SearchString));
           }
       }
0
pcalkins