LINQ to SQL - Left Outer Join mit mehreren Join-Bedingungen

148

Ich habe die folgende SQL, die ich in LINQ übersetzen möchte:

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

Ich habe die typische Implementierung des linken äußeren Joins (dh into x from y in x.DefaultIfEmpty()usw.) gesehen, bin mir aber nicht sicher, wie ich die andere Join-Bedingung einführen soll ( AND f.otherid = 17)

BEARBEITEN

Warum ist die AND f.otherid = 17Bedingung Teil von JOIN anstelle der WHERE-Klausel? Da ffür einige Zeilen möglicherweise nicht vorhanden ist und ich dennoch möchte, dass diese Zeilen enthalten sind. Wenn die Bedingung in der WHERE-Klausel nach dem JOIN angewendet wird, erhalte ich nicht das gewünschte Verhalten.

Leider das:

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

scheint gleichbedeutend damit zu sein:

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

Das ist nicht ganz das, wonach ich suche.

Dan
quelle
Süss! Ich habe eine Weile danach gesucht, war mir aber nicht sicher, wie ich danach suchen sollte. Ich bin mir nicht sicher, wie ich dieser Antwort Tags hinzufügen soll. Hier sind die Suchkriterien, die ich verwendet habe: Linq zu SQL Filter in Join oder von Linq zu SQL wo Klausel in Join oder von
Solburn

Antworten:

243

Sie müssen Ihre Beitrittsbedingung einführen, bevor Sie anrufen DefaultIfEmpty(). Ich würde nur die Syntax der Erweiterungsmethode verwenden:

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

Oder Sie könnten eine Unterabfrage verwenden:

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
Dahlbyk
quelle
1
Vielen Dank, dass Sie das Qualifikationsmerkmal .Where in der Anweisung from .... defaultifempty freigegeben haben. Ich wusste nicht, dass du das kannst.
Frank Thomas
28

Dies funktioniert auch, ... wenn Sie mehrere Spaltenverknüpfungen haben

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
ZenXavier
quelle
12

Ich weiß, dass es " ein bisschen spät " ist, aber nur für den Fall, dass jemand dies in der LINQ-Methodensyntax tun muss ( weshalb ich diesen Beitrag ursprünglich gefunden habe ), wäre dies wie folgt :

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();
Prokuroren
quelle
2
Sehr nützlich, um die Lambda-Version zu sehen!
Lerner
2
.Select(fact.Value)sollte sein.Select(f => f.Value)
Petr Felzmann
5

Eine weitere gültige Option besteht darin, die Verknüpfungen wie folgt auf mehrere LINQ-Klauseln zu verteilen :

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

            // Add the specified content using UNION
            content = content.Union(addMoreContent);

            // Exclude the duplicates using DISTINCT
            content = content.Distinct();

            return content;
        }
    catch (MyLovelyException ex)
    {
        // Add your exception handling here
        throw ex;
    }
}
MAbraham1
quelle
Wäre es nicht langsamer als die gesamte Operation in einer einzigen Linq-Abfrage auszuführen?
Umar T.
@ umar-t, Ja höchstwahrscheinlich, wenn man bedenkt, dass dies vor mehr als acht Jahren war, als ich es schrieb. Persönlich mag ich die von Dahlbyk hier postulierte korrelierte Unterabfrage stackoverflow.com/a/1123051/212950
MAbraham1
1
Eine "Union" ist eine andere Operation als eine "Cross-Join". Es ist wie Addition gegen Multiplikation.
Suncat2000
1
@ Suncat2000, danke für die Korrektur. Frohes Thanksgiving! 👪🦃🙏
MAbraham1
0

Kann mit einem zusammengesetzten Join-Schlüssel geschrieben werden. Auch wenn Eigenschaften sowohl von links als auch von rechts ausgewählt werden müssen, kann der LINQ als geschrieben werden

var result = context.Periods
    .Where(p => p.companyid == 100)
    .GroupJoin(
        context.Facts,
        p => new {p.id, otherid = 17},
        f => new {id = f.periodid, f.otherid},
        (p, f) => new {p, f})
    .SelectMany(
        pf => pf.f.DefaultIfEmpty(),
        (pf, f) => new MyJoinEntity
        {
            Id = pf.p.id,
            Value = f.value,
            // and so on...
        });
Petr Felzmann
quelle
-1

Es scheint mir sinnvoll zu sein, einige Änderungen an Ihrem SQL-Code in Betracht zu ziehen, bevor Sie versuchen, ihn zu übersetzen.

Persönlich würde ich eine solche Abfrage als Gewerkschaft schreiben (obwohl ich Nullen vollständig vermeiden würde!):

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

Ich stimme also dem Geist der Antwort von @ MAbraham1 zu (obwohl ihr Code nichts mit der Frage zu tun zu haben scheint).

Es scheint jedoch, dass die Abfrage ausdrücklich darauf ausgelegt ist, ein Ergebnis mit einer einzelnen Spalte zu erzeugen, das doppelte Zeilen enthält - tatsächlich doppelte Nullen! Es ist schwer, nicht zu dem Schluss zu kommen, dass dieser Ansatz fehlerhaft ist.

eines Tages, wenn
quelle