LINQ to SQL - Jointure externe gauche avec plusieurs conditions de jointure

148

J'ai le SQL suivant, que j'essaie de traduire en LINQ:

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

J'ai vu l'implémentation typique de la jointure externe gauche (c'est-à-dire, into x from y in x.DefaultIfEmpty()etc.) mais je ne sais pas comment introduire l'autre condition de jointure ( AND f.otherid = 17)

ÉDITER

Pourquoi la AND f.otherid = 17condition fait-elle partie de JOIN plutôt que de la clause WHERE? Parce qu'il fpeut ne pas exister pour certaines lignes et je veux toujours que ces lignes soient incluses. Si la condition est appliquée dans la clause WHERE, après le JOIN - alors je n'obtiens pas le comportement souhaité.

Malheureusement ceci:

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

semble être équivalent à ceci:

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

ce n'est pas tout à fait ce que je recherche.

dan
la source
Doux! Je cherchais ceci depuis un certain temps, mais je ne savais pas comment le rechercher. Je ne sais pas comment ajouter des balises à cette réponse. Voici les critères de recherche que j'ai utilisés: filtre linq vers sql dans la jointure ou de linq vers sql clause where dans la jointure ou à partir de
Solburn

Réponses:

243

Vous devez introduire votre condition de jointure avant d'appeler DefaultIfEmpty(). J'utiliserais simplement la syntaxe de la méthode d'extension:

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

Ou vous pouvez utiliser une sous-requête:

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
la source
1
Merci d'avoir partagé le qualificatif .Where sur l'instruction from .... defaultifempty. Je ne savais pas que tu pouvais faire ça.
Frank Thomas le
28

cela fonctionne aussi, ... si vous avez plusieurs jointures de colonnes

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
la source
12

Je sais que c'est " un peu tard ", mais juste au cas où quelqu'un aurait besoin de le faire dans la syntaxe de la méthode LINQ ( c'est pourquoi j'ai trouvé cet article au départ ), voici comment procéder:

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();
Prokurors
la source
2
Très utile pour voir la version lambda!
Apprenant
2
.Select(fact.Value)devrait être.Select(f => f.Value)
Petr Felzmann
5

Une autre option valide consiste à répartir les jointures sur plusieurs clauses LINQ , comme suit:

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
la source
ne serait-ce pas plus lent que de faire toute l'opération en une seule requête linq?
Umar T.
@ umar-t, Oui très probablement, étant donné que c'était il y a plus de huit ans quand je l'ai écrit. Personnellement, j'aime la sous-requête corrélée postulée par Dahlbyk ici stackoverflow.com/a/1123051/212950
MAbraham1
1
Une "union" est une opération différente d'une "jointure croisée". C'est comme l'addition contre la multiplication.
Suncat2000
1
@ Suncat2000, merci pour la correction. Joyeux Action de Graces! 👪🦃🙏
MAbraham1
0

Peut être écrit à l'aide de la clé de jointure composite. De plus, s'il est nécessaire de sélectionner des propriétés des côtés gauche et droit, LINQ peut être écrit comme

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
la source
-1

Il me semble qu'il est utile d'envisager de réécrire votre code SQL avant d'essayer de le traduire.

Personnellement, j'écrirais une telle requête comme une union (même si j'éviterais complètement les valeurs nulles!):

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

Je suppose donc que je suis d'accord avec l'esprit de la réponse de @ MAbraham1 (bien que leur code ne semble pas lié à la question).

Cependant, il semble que la requête soit expressément conçue pour produire un résultat de colonne unique comprenant des lignes en double - en fait des valeurs nulles en double! Il est difficile de ne pas conclure que cette approche est imparfaite.

un jour quand
la source