2014-10-15 29 views
7

Struktura danych wygląda następująco:Użytkownicy zapytania, którzy mają wszystkie wymagane rekordy w dwóch tabelach

Użytkownika (id)

UserApp (user_id, APP_ID)

UserSkill (user_id, skill_id)

Korzystając z linq-sql lub EF, jak skonstruować zapytanie, by elegancko zwracać tylko tych użytkowników, którzy posiadają każdą żądaną aplikację i umiejętności?

Ponadto, w jaki sposób dostosować zapytanie, aby zwrócić użytkownika posiadającego przynajmniej jedną z żądanych aplikacji lub umiejętności? Zasadniczo OR vs AND (powyżej).

UPDATE 1:

Więc myślę, że jesteśmy blisko. Zasadniczo chcę tylko zwrócić użytkowników, którzy mają WSZYSTKIE żądane aplikacje i umiejętności. Jeśli mamy dwie tablice wymaganych identyfikatorów dla umiejętności i aplikacje:

int[] requestedAppIDs // [1, 2, 3] 
int[] requestedSkillIDs // [4, 5, 6] 

chciałbym tylko zwrócić użytkownikowi jeśli mają aplikacje 1,2,3 i umiejętności 4,5,6.

var usersWithAllSelectedAppsAndSkills = 
    context.Users 
    .GroupJoin(context.UserApp, 
     k => k.id, 
     k => k.user_id, 
     (o, i) => new { User = o, UserApps = i }) 
    .GroupJoin(context.UserSkill, 
     k => k.User.id, 
     k => k.user_id, 
     (o, i) => new { User = o.User, o.UserApps, UserSkills = i }) 
    .Where(w => !requestedAppIDs.Except(w.UserApps.Select(x => x.app_id).ToArray()).Any() && !requestedSkillIDs.Except(w.UserSkills.Select(x => x.skill_id).ToArray()).Any()) 
    .Select(s => s.User) 
    .ToList(); 

Oczywiście, LINQ nie wiem jak przetłumaczyć UserSkills.Select(). ToArray() jest w moim przypadku() SQL. Jak mogę to zrobić?

I, po drugie, również rozwiązanie OR (użytkownik ma jedną z żądanych aplikacji lub umiejętności).

+0

co z moją odpowiedź To nie działa? – MajkeloDev

+0

@bmherold można podać strukturę DB i przykład? – Mairaj

+0

@bmherold Co jest niejasne lub niezbyt zwięzłe w moim rozwiązaniu? Możesz bardzo łatwo zmienić (mając wszystkie/dowolne umiejętności) (i/lub) (mając wszystko/dowolną aplikację). Moje rozwiązanie jest na tyle małe, że można je ćwierkać;) Myślę więc, że tak naprawdę to nie martwi. Proszę, pomyśl, co cię martwi i wyjaśnij. Być może nie wyjaśniłeś w dobry sposób tego, czego szukasz ... lub nawet nie wiesz tego. – JotaBe

Odpowiedz

2

Oto jeden ze sposobów, aby to zrobić, mam nadzieję, mam pełne prawo składni :)

using (var context = new YourContext()) 
      { 
       var usersWithAllSkills = context.User 
           .Where(w => w.id == yourId) 
           .Join(context.UserApp, 
             k => k.id, 
             k => k.user_id, 
             (o,i) => o) 
           .Join(context.UserSkill, 
             k => k.id, 
             k => k.user_id, 
             (o,i) => o) 
           .ToList(); 

     var usersWithAnySkill = context.User 
          .Where(w => w.id == yourId) 
          .GroupJoin(context.UserSkill, 
             k => k.id, 
             k => k.user_id, 
             (o,i) => new { User = o, UserSkills = i }) 
          .GroupJoin(context.UserApp, 
             k => k.User.id, 
             k => k.user_id, 
             (o,i) => new { User = o.User, o.UserSkills ,UserApps = i }) 
          .Where(w => w.UserSkills != null || w.UserApps != null) 
          .Select(s => s.User) 
          .ToList(); 

     } 
+0

Dodano aktualizację powyżej opartą na rozwiązaniu. Było blisko, ale chcę wybrać tylko tych użytkowników, którzy zawierają WSZYSTKIE żądane aplikacje i umiejętności. – bmherold

2

W pierwszym przypadku (I) Po prostu trzeba zrobić sprzężenie wewnętrzne jak poniżej:

from t1 in db.UserApp 
join t2 in db.UserSkill on t1.user_id equals t2.user_id 
where t1.app_id == "someID" && t2.skill_id == "someID" 
select new { t1.user_id,t1.user_app_id, t2.user_skill} 

W drugim przypadku zamień po prostu & & (AND) z || (LUB).

3

Spowoduje to wykonanie zadania, o ile tylko wartości user_id - identyfikator_aplikacji i identyfikator_użytkownika - skill_id w tabelach UserApp i UserSkill są unikalne.

var requestedSkillIDs = new[] { 4, 5, 6 }; 
var skillCount = requestedSkillIDs.Length; 
var requestedAppIDs = new[] { 1, 2, 3 }; 
var appCount = requestedAppIDs.Length; 

using (var context = new TestContext()) { 
    context.Database.CreateIfNotExists(); 

    var appQuery = context.UserApp.Where(p => requestedAppIDs.Contains(p.AppId)) 
         .GroupBy(p => p.UserId) 
         .Where(p => p.Count() == appCount); 

    var skillQuery = context.UserSkill.Where(p => requestedSkillIDs.Contains(p.SkillId)) 
         .GroupBy(p => p.UserId) 
         .Where(p => p.Count() == skillCount); 

    var result = from a in appQuery 
        join s in skillQuery on a.Key equals s.Key 
        join u in context.Users on s.Key equals u.Id 
        select u; 


    var users = result.ToList(); 
} 
+0

Tak, ten będzie działał. bmherold - pamiętaj tylko, że zawsze musisz zrobić to tak jak w powyższym przykładzie: Collection.Contains (atrybut LinQ). Nie możesz tego odwrócić. – MajkeloDev

2

Istnieje bardziej bezpośredni sposób zapisywania wymaganych zapytań za pomocą L2E. Aby napisać te zapytania, musisz zapomnieć o myśleniu w SQL i zacząć myśleć w LINQ.

W pierwszym przypadku, poszukaj użytkowników posiadających wszelkie umiejętności:

var usersWithAll = ctx.Users2.Where(u => 
    appIds.All(aid => u.Apps.Any(a => a.AppId == aid)) 
    && skillIds.All(sid => u.Skills.Any(s => s.SkillId == sid)) 
    ); 

przetłumaczyć jako: uzyskać użytkowników, gdzie dla wszystkich appIds użytkownik al leat aplikację z tego identyfikatora aplikacji i dla wszystkich skillIds użytkownik ma co najmniej jedną umiejętność o takim identyfikatorze

, a dla drugiego przypadku użytkownicy które z dowolnych aplikacji i każda z umiejętności:

var usersWithAny = ctx.Users2.Where(u => 
    appIds.Any(aid => u.Apps.Any(a => a.AppId == aid)) 
    && skillIds.Any(sid => u.Skills.Any(s => s.SkillId == sid)) 
    ).ToList(); 

przetłumaczyć jako: uzyskać użytkowników, gdzie co najmniej jeden AppID użytkownik ma aplikację o takim identyfikatorze aplikacji oraz za wszelkie skillIds użytkownik ma co najmniej jedną umiejętność o takim identyfikatorze

Po uruchomieniu tej klasy testowej zobaczysz także wykonane zapytanie (pamiętaj, że w tym celu korzystam z właściwości Log z Database. Myślę, że jest dostępny tylko z EF6).

namespace Tests 
{ 
    [TestClass] 
    public class CheckSeveralRelationsAtOnce 
    { 
     [TestMethod] 
     public void HasAllAppsAndSkills() 
     { 
      int[] appIds = {1, 2, 3}; 
      int[] skillIds = {6, 7, 8}; 
      using (var ctx = new MyDbContext()) 
      { 
       ctx.Database.Log = Console.Write; 

       var usersWithAll = ctx.Users2.Where(u => 
        appIds.All(aid => u.Apps.Any(a => a.AppId == aid)) 
        && skillIds.All(sid => u.Skills.Any(s => s.SkillId == sid)) 
        ).ToList(); 
       Assert.IsNotNull(usersWithAll); 
      } 
     } 

     [TestMethod] 
     public void HasAnyAppsOrSkill() 
     { 
      int[] appIds = { 1, 2, 3 }; 
      int[] skillIds = { 6, 7, 8 }; 
      using (var ctx = new MyDbContext()) 
      { 
       ctx.Database.Log = Console.Write; 

       var usersWithAny = ctx.Users2.Where(u => 
        appIds.Any(aid => u.Apps.Any(a => a.AppId == aid)) 
        && skillIds.Any(sid => u.Skills.Any(s => s.SkillId == sid)) 
        ).ToList(); 
       Assert.IsNotNull(usersWithAny); 
      } 
     } 
    } 
} 
1

wierzę answer by codeworx jest poprawna dla coraz użytkownikom wszystkie umiejętności/apps

Tak na marginesie - odparłem prawie to samo pytanie niedawno z czystym roztworze SQL (SQL Server) - które mogłyby zostać zamienionym na procedurę przechowywaną (z parametrem wycenionym w tabeli) - patrz: here, jeśli jest zainteresowany. Będzie to działać lepiej dla dużej liczby wartości. Struktura obiektu zmieni każdą umiejętność/aplikację na liście w własny parametr SQL, który jest znacznie wolniejszy.

Niestety Entity Framework naprawdę nie obsługują tabeli cenione parametry jeszcze - choć można użyć połączenia Entity Framework, aby bezpośrednio wywołać procedurę przechowywaną z ceniony parametru tabeli (zgodnie this article

Powrót na pytanie pod ręką ... dodam się (łatwiejszy) zapytanie do wybierz użytkownika z żadnymi umiejętności & którejkolwiek z aplikacji:

var result = from u in context.Users 
      join _a in (
       from a in context.UserApp 
       where requestedAppIDs.Contains(a.AppId) 
       select a.UserId; 
      ) on u.Id equals _a 
      into aGrp 
      join _s in (
       from s in context.UserSkill 
       where requestedSkillIDs.Contains(s.SkillId) 
       select s.UserId; 
      ) on u.Id equals _s 
      into sGrp 
      where aGrp.Any() 
       && sGrp.Any() 
      select u; 

i tylko dla kompletności - Rozwiązanie wszystko jeszcze raz:

var skillCount = requestedSkillIDs.Length; 
var appCount = requestedAppIDs.Length; 
var result = from u in context.Users 
      join _a in (
       from a in context.UserApp 
       where requestedAppIDs.Contains(a.AppId) 
       select a.UserId; 
      ) on u.Id equals _a 
      into aGrp 
      join _s in (
       from s in context.UserSkill 
       where requestedSkillIDs.Contains(s.SkillId) 
       select s.UserId; 
      ) on u.Id equals _s 
      into sGrp 
      where aGrp.Count() == appCount 
       && sGrp.Count() == skillCount 
      select u; 

i wreszcie - przykład gdzie główny korpus kwerenda jest ustalona, ​​ale można dodać różniące WHERE w zależności od I/LUB wymaganie

bool onlyReturnWhereAllAreMatched = false; 
var skillCount = requestedSkillIDs.Length; 
var appCount = requestedAppIDs.Length; 
IQueryable<User> result; 
var query = from u in context.Users 
      join _a in (
       from a in context.UserApp 
       where requestedAppIDs.Contains(a.AppId) 
       select a.UserId; 
      ) on u.Id equals _a 
      into aGrp 
      join _s in (
       from s in context.UserSkill 
       where requestedSkillIDs.Contains(s.SkillId) 
       select s.UserId; 
      ) on u.Id equals _s 
      into sGrp 
      select new {u, aCount = aGrp.Count(), sCount = sGrp.Count()}; 
if (onlyReturnWhereAllAreMatched) 
{ 
    result = from x in query 
      where x.aCount == appCount 
       && x.sCount == skillCount 
      select x.u; 
} else { 
    result = from x in query 
      where x.aCount > 0 
       && x.sCount > 0 
      select x.u; 
}