1
votes

Comment interroger des groupes de prédicat dans EF Core

Je souhaite appliquer un certain nombre de filtres composites à un groupe de données comme celui-ci:

SELECT 
[Person].[Id]
FROM [Person] 
WHERE 
   ((([Person].[Name] = "Bob") AND ([Person].[Gender] = "Male")) 
   OR (([Person].[Name] = "Alice") AND ([Person].[Gender] = "Female")))

Je suis intéressé par les Ids de Homme Bobs et Female Alices . Désolé Female Bobs . Je ne veux pas de toi.

C'est la bonne façon de résoudre ce problème en mémoire Linq, mais il ya un problème. Voici à quoi ressemble l'EF SQL (je vérifie cela dans mon profileur de serveur SQL)

SELECT [p].[Name], [p].[Gender], [p].[Id] FROM [People] AS [p]

C'est terrible. Il déterre tout et effectue ensuite le travail réel en mémoire. Il n'y a aucun moyen que cela fonctionne avec beaucoup de gens, cela s'arrêtera.

Y a-t-il un moyen de faire en sorte que le SQL généré ressemble plus à ça?

Filter[] filters = new[] { new Filter { Name = "Bob", Gender = "Male"  },
new Filter { Name = "Alice", Height = "Female" } };

_dbContext.People.Where(p => filter.Any(f => f.Name == p.Name && f.Gender == p.Gender)).Select(p => p.Id);


2 commentaires

Vous pouvez l'essayer avec un générateur de prédicat


Hé, n'est-ce pas une erreur, ça devrait être _dbContext.People.Where (p => filter.Any (f => f.Name == p.Name && f.Gender == p.Gender)). Sélectionnez ( p => p.Id);


3 Réponses :


2
votes

La structure de votre requête suggère que le nombre de combinaisons nom / sexe pourrait être supérieur à deux. Dans ce cas, il peut être plus judicieux d'écrire votre propre procédure stockée au lieu de laisser EF créer la requête.

Dans ce cas, j'utiliserais un paramètre table pour transmettre un ensemble de lignes en tant que paramètres à la procédure stockée. Chaque ligne contient à la fois un nom et un sexe. La requête joint ce paramètre de table à votre table Person, renvoyant des lignes où les noms et les sexes correspondent.


0 commentaires

1
votes

Dans cette situation particulière (si vous n'avez qu'un petit nombre de filtres), je suggérerais de diviser la requête en une expression explicite, comme

var maleNameFilter = "Bob";
var femaleNameFilter = "Alice";
_dbContext.People.Where(p => 
    (p.Name == maleNameFilter && p.Gender == "Male") 
    || (p.Name == femaleNameFilter && p.Gender == "Female")
).Select(p => p.Id);

Cependant, je soupçonne que vous voudriez utiliser un grand nombre de filtres, auquel cas cela devient plus difficile avec LINQ. Comme déjà suggéré dans certains commentaires, vous pouvez utiliser Predicate Builder pour cela (voir l ' exemple ).

Enfin, si vous voulez des performances maximales à un coût un peu plus complexe, vous pouvez envisager de placer vos valeurs de filtre dans une table séparée de la base de données et de réécrire votre requête en utilisant Join () .


0 commentaires

2
votes

Voici ce que j'ai fait à la fin, comme @stuartd l'a suggéré:

var predicate = PredicateBuilder.New<Person>();
foreach (var filter in filters)
{
    predicate = predicate.Or(p =>
       p.Gender == filter.Gender &&  filter.Name == p.Name));
}

people = _dbContext.People.Where(predicate).Select(r => r.Id).Distinct().ToArrayAsync();

Fonctionne comme un charme. Merci.


0 commentaires