Requêtes Linq conditionnelles

92

Nous travaillons sur une visionneuse de journaux. L'utilisation aura la possibilité de filtrer par utilisateur, gravité, etc. Dans les jours Sql j'ajouterais à la chaîne de requête, mais je veux le faire avec Linq. Comment puis-je ajouter conditionnellement des clauses where?

sgwill
la source

Réponses:

156

si vous voulez filtrer uniquement si certains critères sont satisfaits, faites quelque chose comme ceci

var logs = from log in context.Logs
           select log;

if (filterBySeverity)
    logs = logs.Where(p => p.Severity == severity);

if (filterByUser)
    logs = logs.Where(p => p.User == user);

Cela permettra à votre arbre d'expression d'être exactement ce que vous voulez. De cette façon, le SQL créé sera exactement ce dont vous avez besoin et rien de moins.

Darren Kopp
la source
2
Salut Avez-vous des suggestions pour faire des clauses WHERE OU au lieu de AND ..?
Jon H
1
Ouais ... c'est un peu difficile à faire. Le mieux que j'ai vu est le modèle de spécification et l'extraction du prédicat dans la spécification, puis l'appel de la spécification.Ou (someOtherSpecification). En gros, vous devez écrire un peu votre propre arbre d'expression. Exemple de code et explication ici: codeinsanity.com/archive/2008/08/13/…
Darren Kopp
J'ai une question stupide, si ces journaux sont acquis à partir de la base de données, obtenons-nous tous les journaux et les filtrons-nous en mémoire? Si oui, comment puis-je transmettre les conditions à la base de données
Ali Umair
ce n'est pas les filtrer en mémoire. il construit une requête et envoie toutes les conditions de la base de données (du moins pour la plupart des fournisseurs linq-to-x)
Darren Kopp
obtenir cette erreurLINQ to Entities does not recognize the method 'System.String get_Item(System.String)' method, and this method cannot be translated into a store expression.
Ali Umair
22

Si vous avez besoin de filtrer la base sur une liste / un tableau, utilisez ce qui suit:

    public List<Data> GetData(List<string> Numbers, List<string> Letters)
    {
        if (Numbers == null)
            Numbers = new List<string>();

        if (Letters == null)
            Letters = new List<string>();

        var q = from d in database.table
                where (Numbers.Count == 0 || Numbers.Contains(d.Number))
                where (Letters.Count == 0 || Letters.Contains(d.Letter))
                select new Data
                {
                    Number = d.Number,
                    Letter = d.Letter,
                };
        return q.ToList();

    }
Carlos
la source
3
C'est de loin la meilleure et la plus correcte réponse. Le conditionnel || ne compare la première partie et saute la seconde que si la première partie est vraie ... bien fait!
Serj Sagan
1
Cette construction inclut la partie «ou» de l'expression dans la requête SQL générée. La réponse acceptée générera des déclarations plus efficaces. En fonction des optimisations du fournisseur de données, bien sûr. LINQ-to-SQL peut avoir une meilleure optimisation, mais pas LINQ-to-Entities.
Suncat2000
20

J'ai fini par utiliser une réponse similaire à celle de Daren, mais avec une interface IQueryable:

IQueryable<Log> matches = m_Locator.Logs;

// Users filter
if (usersFilter)
    matches = matches.Where(l => l.UserName == comboBoxUsers.Text);

 // Severity filter
 if (severityFilter)
     matches = matches.Where(l => l.Severity == comboBoxSeverity.Text);

 Logs = (from log in matches
         orderby log.EventTime descending
         select log).ToList();

Cela construit la requête avant d'accéder à la base de données. La commande ne s'exécutera pas avant .ToList () à la fin.

sgwill
la source
14

En ce qui concerne le linq conditionnel, j'aime beaucoup le motif des filtres et des tuyaux.
http://blog.wekeroad.com/mvc-storefront/mvcstore-part-3/

Fondamentalement, vous créez une méthode d'extension pour chaque cas de filtre qui prend dans IQueryable et un paramètre.

public static IQueryable<Type> HasID(this IQueryable<Type> query, long? id)
{
    return id.HasValue ? query.Where(o => i.ID.Equals(id.Value)) : query;
}
Lars Mæhlum
la source
8

J'ai résolu cela avec une méthode d'extension pour permettre à LINQ d'être activé conditionnellement au milieu d'une expression fluide. Cela supprime le besoin de fractionner l'expression avec des ifinstructions.

.If() méthode d'extension:

public static IQueryable<TSource> If<TSource>(
        this IQueryable<TSource> source,
        bool condition,
        Func<IQueryable<TSource>, IQueryable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }

Cela vous permet de faire ceci:

return context.Logs
     .If(filterBySeverity, q => q.Where(p => p.Severity == severity))
     .If(filterByUser, q => q.Where(p => p.User == user))
     .ToList();

Voici également une IEnumerable<T>version qui gérera la plupart des autres expressions LINQ:

public static IEnumerable<TSource> If<TSource>(
    this IEnumerable<TSource> source,
    bool condition,
    Func<IEnumerable<TSource>, IEnumerable<TSource>> branch)
    {
        return condition ? branch(source) : source;
    }
Ryan
la source
4

Une autre option serait d'utiliser quelque chose comme le PredicateBuilder discuté ici . Il vous permet d'écrire du code comme celui-ci:

var newKids  = Product.ContainsInDescription ("BlackBerry", "iPhone");

var classics = Product.ContainsInDescription ("Nokia", "Ericsson")
                  .And (Product.IsSelling());

var query = from p in Data.Products.Where (newKids.Or (classics))
            select p;

Notez que cela ne fonctionne qu'avec Linq 2 SQL. EntityFramework n'implémente pas Expression.Invoke, qui est nécessaire pour que cette méthode fonctionne. J'ai une question à ce sujet ici .

Brad Leach
la source
C'est une excellente méthode pour ceux qui utilisent une couche de logique métier au-dessus de leur référentiel avec un outil comme AutoMapper pour mapper entre les objets de transfert de données et les modèles d'entité. L'utilisation du constructeur de prédicat vous permettra de modifier dynamiquement votre IQueryable avant de l'envoyer à AutoMapper pour l'aplatir, c'est-à-dire mettre la liste en mémoire. Notez qu'il prend également en charge Entity Framework.
chrisjsherm
3

Ce faisant:

bool lastNameSearch = true/false; // depending if they want to search by last name,

avoir ceci dans la wheredéclaration:

where (lastNameSearch && name.LastNameSearch == "smith")

signifie que lorsque la requête finale est créée, si lastNameSearch est falsela requête SQL omettre complètement toute la dernière recherche de nom.

James Livingston
la source
Dépend du fournisseur de données. LINQ-to-Entities ne l'optimise pas très bien.
Suncat2000
1

Ce n'est pas la plus jolie chose mais vous pouvez utiliser une expression lambda et passer vos conditions facultativement. Dans TSQL, je fais beaucoup de choses suivantes pour rendre les paramètres facultatifs:

WHERE Field = @FieldVar OU @FieldVar EST NULL

Vous pouvez dupliquer le même style avec un lambda suivant (un exemple de vérification de l'authentification):

MyDataContext db = nouveau MyDataContext ();

void RunQuery (chaîne param1, chaîne param2, int? param3) {

Func checkUser = utilisateur =>

((param1.Length> 0)? user.Param1 == param1: 1 == 1) &&

((param2.Length> 0)? user.Param2 == param2: 1 == 1) &&

((param3! = null)? user.Param3 == param3: 1 == 1);

Utilisateur foundUser = db.Users.SingleOrDefault (checkUser);

}

t3rse
la source
1

J'ai eu une exigence similaire récemment et j'ai finalement trouvé cela dans le MSDN. Exemples CSharp pour Visual Studio 2008

Les classes incluses dans l'exemple DynamicQuery du téléchargement vous permettent de créer des requêtes dynamiques au moment de l'exécution au format suivant:

var query =
db.Customers.
Where("City = @0 and Orders.Count >= @1", "London", 10).
OrderBy("CompanyName").
Select("new(CompanyName as Name, Phone)");

En utilisant cela, vous pouvez créer une chaîne de requête de manière dynamique au moment de l'exécution et la transmettre à la méthode Where ():

string dynamicQueryString = "City = \"London\" and Order.Count >= 10"; 
var q = from c in db.Customers.Where(queryString, null)
        orderby c.CompanyName
        select c;
Andy Rose
la source
1

Vous pouvez créer et utiliser cette méthode d'extension

public static IQueryable<TSource> WhereIf<TSource>(this IQueryable<TSource> source, bool isToExecute, Expression<Func<TSource, bool>> predicate)
{
    return isToExecute ? source.Where(predicate) : source;
}
Gustavo
la source
0

Utilisez simplement l'opérateur && de C #:

var items = dc.Users.Where(l => l.Date == DateTime.Today && l.Severity == "Critical")

Edit: Ah, besoin de lire plus attentivement. Vous vouliez savoir comment ajouter conditionnellement des clauses supplémentaires. Dans ce cas, je n'en ai aucune idée. :) Ce que je ferais probablement, c'est simplement préparer plusieurs requêtes et exécuter la bonne, en fonction de ce dont j'ai fini par avoir besoin.

Le Schtroumpf
la source
0

Vous pouvez utiliser une méthode externe:

var results =
    from rec in GetSomeRecs()
    where ConditionalCheck(rec)
    select rec;

...

bool ConditionalCheck( typeofRec input ) {
    ...
}

Cela fonctionnerait, mais ne peut pas être décomposé en arborescences d'expressions, ce qui signifie que Linq to SQL exécuterait le code de vérification sur chaque enregistrement.

Alternativement:

var results =
    from rec in GetSomeRecs()
    where 
        (!filterBySeverity || rec.Severity == severity) &&
        (!filterByUser|| rec.User == user)
    select rec;

Cela pourrait fonctionner dans les arbres d'expression, ce qui signifie que Linq to SQL serait optimisé.

Keith
la source
0

Eh bien, je pensais que vous pouviez mettre les conditions de filtre dans une liste générique de prédicats:

    var list = new List<string> { "me", "you", "meyou", "mow" };

    var predicates = new List<Predicate<string>>();

    predicates.Add(i => i.Contains("me"));
    predicates.Add(i => i.EndsWith("w"));

    var results = new List<string>();

    foreach (var p in predicates)
        results.AddRange(from i in list where p.Invoke(i) select i);               

Il en résulte une liste contenant "moi", "meyou" et "tondre".

Vous pouvez optimiser cela en faisant le foreach avec les prédicats dans une fonction totalement différente qui ORs tous les prédicats.

Jon Limjap
la source