2 votes

Y a-t-il une meilleure façon d'écrire cette requête LINQ frankenstein qui recherche des valeurs dans une table enfant et les classe par ordre de pertinence ?

J'ai un tableau d'utilisateurs et un tableau de compétences d'un à plusieurs utilisateurs. J'ai besoin de pouvoir rechercher des utilisateurs sur la base de leurs compétences. Cette requête prend une liste de compétences souhaitées et recherche les utilisateurs qui ont ces compétences. Je souhaite trier les utilisateurs en fonction du nombre de compétences souhaitées qu'ils possèdent. Ainsi, si un utilisateur ne possède que 1 des 3 compétences souhaitées, il sera plus loin dans la liste que l'utilisateur qui possède 3 des 3 compétences souhaitées.

Je commence par une liste séparée par des virgules des identifiants de compétences recherchés :

List<short> searchedSkillsRaw = skills.Value.Split(',').Select(i => short.Parse(i)).ToList();

Je filtre ensuite uniquement les types d'utilisateurs qui peuvent faire l'objet d'une recherche :

List<User> users = (from u in db.Users
                    where
                        u.Verified == true &&
                        u.Level > 0 &&
                        u.Type == 1 &&
                        (u.UserDetail.City == city.SelectedValue || u.UserDetail.City == null)
                    select u).ToList();

puis vient la partie la plus folle :

var fUsers = from u in users
             select new
             {
                 u.Id,
                 u.FirstName,
                 u.LastName,
                 u.UserName,
                 UserPhone = u.UserDetail.Phone,
                 UserSkills = (from uskills in u.UserSkills
                               join skillsJoin in configSkills on uskills.SkillId equals skillsJoin.ValueIdInt into tempSkills
                               from skillsJoin in tempSkills.DefaultIfEmpty()
                               where uskills.UserId == u.Id
                               select new
                               {
                                   SkillId = uskills.SkillId,
                                   SkillName = skillsJoin.Name,
                                   SkillNameFound = searchedSkillsRaw.Contains(uskills.SkillId)
                               }),
                 UserSkillsFound = (from uskills in u.UserSkills
                                    where uskills.UserId == u.Id && searchedSkillsRaw.Contains(uskills.SkillId)
                                    select uskills.UserId).Count()
             } into userResults
             where userResults.UserSkillsFound > 0
             orderby userResults.UserSkillsFound descending
             select userResults;

et ça marche ! Mais cela me semble très lourd et inefficace. Surtout la partie secondaire qui compte le nombre de compétences trouvées.

Merci pour tous les conseils que vous pourrez me donner.

--r

3voto

Michael Ulmann Points 745

Je pense que cela devrait suffire :

(from u in users
where u.UserSkills.Any(skill => searchedSkillsRaw.Contains(skill.SkillId))
select new
{
    u.Id,
    u.FirstName,
    u.LastName,
    u.UserName,
    UserPhone = u.UserDetail.Phone,
    UserSkills = u.UserSkills,
    UserSkillsFound = u.UserSkills.Where(skill => searchedSkillsRaw.Contains(skill.SkillId)).Count()
} into userResults
orderby userResults.UserSkillsFound descending
select userResult).ToList();

Cependant, comme il s'agit d'une requête qui est exécutée sur le serveur SQL, je recommande vivement de supprimer l'appel "ToList()" de la première requête. En effet, LINQ exécute alors deux requêtes distinctes sur le serveur SQL. Vous devriez le remplacer par IQueryable. La puissance de LINQ est de construire des requêtes en plusieurs étapes sans avoir à les exécuter entre elles. Ainsi, "ToList" ne doit être appelé qu'à la fin, lorsque la requête entière a été construite. En fait, ce que vous faites actuellement, c'est exécuter la deuxième requête en mémoire plutôt que sur le serveur de la base de données.

En ce qui concerne votre relation UserSkills one-to-many, vous n'avez pas besoin de faire une jointure explicite dans LINQ. Vous pouvez simplement accéder à la propriété de la collection.

Faites-moi savoir si vous avez besoin de plus d'explications.

Michael

0voto

Will Points 76760

Pourquoi ne pas laisser les gens faire, dire, fUsers.UserSkills.Count() ? Cela réduirait la quantité de données extraites du serveur en premier lieu.

Vous pouvez également créer une vue contenant un champ calculé et l'associer à un type. Cela pousserait la requête pour le décompte dans la base de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X