2009-07-14 5 views
126

Ho il seguente SQL, che sto cercando di tradurre in LINQ:LINQ to SQL - sinistra outer join con più condizioni di join

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 

ho visto il tipico attuazione del join esterno sinistro (es. into x from y in x.DefaultIfEmpty() ecc), ma sono sicuro di come introdurre l'altra condizione di join (AND f.otherid = 17)

EDIT

Perché è la parte AND f.otherid = 17 condizioni di il JOIN invece che nella clausola WHERE? Perché f potrebbe non esistere per alcune righe e voglio comunque includere queste righe. Se la condizione è applicata nella clausola WHERE, dopo il JOIN - allora non ho il comportamento che voglio.

Purtroppo questo:

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 

sembra essere equivalente a questo:

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 

che non è proprio quello che sto cercando.

+0

Dolce!Sono stato alla ricerca di questo per un po 'ma non ero sicuro di come cercare questo. Non sei sicuro di come aggiungere tag a questa risposta. Ecco i criteri di ricerca che ho usato: da linq a sql filter in join o da da linq a sql dove clausola in join o da – Solburn

risposta

205

È necessario introdurre la condizione di join prima di chiamare DefaultIfEmpty(). Vorrei solo usare metodo di estensione sintassi:

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 

Oppure si potrebbe usare una sottoquery:

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

Un'altra opzione valida è quella di diffondere i join tra più clausole LINQ, come segue:

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

non sarebbe più lento di fare l'intera operazione in una singola query di linq? –

+0

@ umar-t, sì molto probabilmente, considerando che questo era più di otto anni fa quando l'ho scritto. Personalmente mi piace la sottoquery correlata postulata da Dahlbyk qui https://stackoverflow.com/a/1123051/212950 – MAbraham1

23

questo funziona anche, ... se si dispone di più colonne unisce

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

Mi sembra che valga la pena di considerare alcune riscritture del codice SQL prima di tentare di tradurlo.

Personalmente, mi piacerebbe scrivere una query come unione (anche se eviterei nulli del tutto!):

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 
        ); 

quindi credo che sono d'accordo con lo spirito di @ di MAbraham1 risposta (anche se il loro codice sembra essere estraneo alla domanda).

Tuttavia, sembra che la query sia espressamente progettata per produrre un risultato a colonna singola comprendente righe duplicate - anzi duplicare valori nulli! È difficile non giungere alla conclusione che questo approccio sia difettoso.

7

so che è "un po 'tardi", ma solo nel caso in cui se qualcuno ha bisogno di fare questo in LINQ metodo sintassi (che è il motivo per cui ho trovato inizialmente questo post), questo sarebbe come fare :

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

Molto utile per vedere la versione lambda! – Learner