2009-07-14 6 views
126

mam następujące SQL, który próbuję tłumaczyć do LINQ:LINQ to SQL - Left Outer Join z wieloma dołączyć warunki

SELECT f.value 
FROM period as p 
LEFT OUTER JOIN facts AS f ON p.id = f.periodid AND f.otherid = 17 
WHERE p.companyid = 100 

Widziałem typowa realizacja lewe sprzężenie zewnętrzne (tj. into x from y in x.DefaultIfEmpty() itd.), ale jestem pewien, w jaki sposób wprowadzić drugi dołączyć warunek (AND f.otherid = 17)

EDIT

Dlaczego część AND f.otherid = 17 stan JOIN zamiast w klauzuli WHERE? Ponieważ f może nie istnieć dla niektórych wierszy i nadal chcę, aby te wiersze zostały uwzględnione. Jeśli warunek zostanie zastosowany w klauzuli WHERE, po JOIN - wtedy nie otrzymam pożądanego zachowania.

Niestety to:

from p in context.Periods 
join f in context.Facts on p.id equals f.periodid into fg 
from fgi in fg.DefaultIfEmpty() 
where p.companyid == 100 && fgi.otherid == 17 
select f.value 

wydaje się być równoważne do tego:

SELECT f.value 
FROM period as p 
LEFT OUTER JOIN facts AS f ON p.id = f.periodid 
WHERE p.companyid = 100 AND f.otherid = 17 

który nie jest całkiem co jestem po.

+0

Słodkie!Szukałem tego przez chwilę, ale nie byłem pewien, jak tego szukać. Nie wiesz, jak dodać tagi do tej odpowiedzi. Oto kryteria wyszukiwania, których użyłem: linq do filtra sql w połączeniu lub od linq do sql, gdzie klauzula w połączeniu lub od – Solburn

Odpowiedz

205

Musisz wprowadzić warunek łączenia przed wywołaniem DefaultIfEmpty(). Chciałbym po prostu użyć metody rozszerzenie składni:

from p in context.Periods 
join f in context.Facts on p.id equals f.periodid into fg 
from fgi in fg.Where(f => f.otherid == 17).DefaultIfEmpty() 
where p.companyid == 100 
select f.value 

Albo można użyć podzapytania:

from p in context.Periods 
join f in context.Facts on p.id equals f.periodid into fg 
from fgi in (from f in fg 
      where f.otherid == 17 
      select f).DefaultIfEmpty() 
where p.companyid == 100 
select f.value 
5

Kolejna ważna opcja jest szerzenie dołącza całej wielokrotne klauzule LINQ, w następujący sposób:

public static IEnumerable<Announcementboard> GetSiteContent(string pageName, DateTime date) 
{ 
    IEnumerable<Announcementboard> content = null; 
    IEnumerable<Announcementboard> addMoreContent = null; 
     try 
     { 
      content = from c in DB.Announcementboards 
       //Can be displayed beginning on this date 
       where c.Displayondate > date.AddDays(-1) 
       //Doesn't Expire or Expires at future date 
       && (c.Displaythrudate == null || c.Displaythrudate > date) 
       //Content is NOT draft, and IS published 
       && c.Isdraft == "N" && c.Publishedon != null 
       orderby c.Sortorder ascending, c.Heading ascending 
       select c; 

      //Get the content specific to page names 
      if (!string.IsNullOrEmpty(pageName)) 
      { 
       addMoreContent = from c in content 
        join p in DB.Announceonpages on c.Announcementid equals p.Announcementid 
        join s in DB.Apppagenames on p.Apppagenameid equals s.Apppagenameid 
        where s.Apppageref.ToLower() == pageName.ToLower() 
        select c; 
      } 

      //CROSS-JOIN this content 
      content = content.Union(addMoreContent); 

      //Exclude dupes - effectively OUTER JOIN 
      content = content.Distinct(); 

      return content; 
     } 
    catch (MyLovelyException ex) 
    { 
     throw ex; 
    } 
} 
+0

nie byłaby wolniejsza niż wykonanie całej operacji w jednym zapytaniu linq? –

+0

@ umar-t, Tak, najprawdopodobniej, biorąc pod uwagę to było ponad osiem lat temu, kiedy to napisałem. Osobiście podoba mi się skorelowane pod-zapytanie postulowane przez Dahlbyka tutaj https://stackoverflow.com/a/1123051/212950 – MAbraham1

23

to też działa, ... jeśli masz wiele połączeń kolumnowych

from p in context.Periods 
join f in context.Facts 
on new { 
    id = p.periodid, 
    p.otherid 
} equals new { 
    f.id, 
    f.otherid 
} into fg 
from fgi in fg.DefaultIfEmpty() 
where p.companyid == 100 
select f.value 
-1

Wydaje mi się, że warto wziąć pod uwagę niektóre poprawki do kodu SQL przed próbą jego przetłumaczenia.

Osobiście bym napisać takiej kwerendy jako związek (chociaż chciałbym uniknąć null całkowicie!):

SELECT f.value 
    FROM period as p JOIN facts AS f ON p.id = f.periodid 
WHERE p.companyid = 100 
     AND f.otherid = 17 
UNION 
SELECT NULL AS value 
    FROM period as p 
WHERE p.companyid = 100 
     AND NOT EXISTS ( 
         SELECT * 
         FROM facts AS f 
         WHERE p.id = f.periodid 
          AND f.otherid = 17 
        ); 

Więc myślę, że zgadza się z duchem użytkownika @ MAbraham1 odpowiedź (choć ich kodu wydaje się być niezwiązanym z pytaniem).

Jednak wygląda na to, że zapytanie zostało zaprojektowane specjalnie w celu utworzenia wyniku pojedynczej kolumny zawierającej duplikaty wierszy - a nawet zduplikowane wartości zerowe! Trudno nie dojść do wniosku, że takie podejście jest błędne.

7

Wiem, że to „nieco późno”, ale tylko w przypadku, jeśli ktoś musi to zrobić w LINQ Składnia metody (dlatego znalazłem ten post początkowo), byłoby to jak to zrobić :

var results = context.Periods 
    .GroupJoin(
     context.Facts, 
     period => period.id, 
     fk => fk.periodid, 
     (period, fact) => fact.Where(f => f.otherid == 17) 
           .Select(fact.Value) 
           .DefaultIfEmpty() 
    ) 
    .Where(period.companyid==100) 
    .SelectMany(fact=>fact).ToList(); 
+0

Bardzo przydatne, aby zobaczyć wersję lambda! – Learner