LINQ to SQL: combinación externa izquierda con múltiples condiciones de combinación

148

Tengo el siguiente SQL, que estoy tratando de traducir a 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

He visto la implementación típica de la unión externa izquierda (es decir, into x from y in x.DefaultIfEmpty()etc.) pero no estoy seguro de cómo introducir la otra condición de unión ( AND f.otherid = 17)

EDITAR

¿Por qué la AND f.otherid = 17condición es parte de JOIN en lugar de en la cláusula WHERE? Porque fpuede no existir para algunas filas y todavía quiero que se incluyan estas filas. Si la condición se aplica en la cláusula WHERE, después de JOIN, no obtengo el comportamiento que quiero.

Lamentablemente esto:

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

parece ser equivalente a esto:

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

que no es exactamente lo que busco.

dan
fuente
¡Dulce! He estado buscando esto por un tiempo, pero no estaba seguro de cómo buscarlo. No estoy seguro de cómo agregar etiquetas a esta respuesta. Aquí están los criterios de búsqueda que utilicé: linq a sql filter en join o de linq a sql where cláusula en join o from
Solburn

Respuestas:

243

Debe introducir su condición de unión antes de llamar DefaultIfEmpty(). Simplemente usaría la sintaxis del método de extensión:

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

O podría usar una subconsulta:

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
fuente
1
Gracias por compartir el calificador .Where en la declaración de defaultifempty de ... No sabía que podías hacer eso.
Frank Thomas
28

esto también funciona ... si tiene múltiples uniones de columna

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
fuente
12

Sé que es " un poco tarde ", pero en caso de que alguien necesite hacer esto en la sintaxis del método LINQ ( es por eso que encontré esta publicación inicialmente ), esta sería la forma de hacerlo:

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
fuente
2
Muy útil para ver la versión lambda!
Estudiante el
2
.Select(fact.Value)debería ser.Select(f => f.Value)
Petr Felzmann
5

Otra opción válida es distribuir las uniones a través de múltiples cláusulas LINQ , de la siguiente manera:

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
fuente
¿No sería más lento que hacer toda la operación en una sola consulta linq?
Umar T.
@ umar-t, sí, lo más probable, considerando que esto fue hace más de ocho años cuando lo escribí. Personalmente, me gusta la subconsulta correlacionada postulada por Dahlbyk aquí stackoverflow.com/a/1123051/212950
MAbraham1
1
Una "unión" es una operación diferente a una "unión cruzada". Es como la suma frente a la multiplicación.
Suncat2000
1
@ Suncat2000, gracias por la corrección. ¡Feliz día de acción de gracias! 👪🦃🙏
MAbraham1
0

Se puede escribir usando la clave de combinación compuesta. Además, si es necesario seleccionar propiedades de los lados izquierdo y derecho, LINQ se puede escribir como

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
fuente
-1

Me parece que tiene valor considerar algunas reescrituras en su código SQL antes de intentar traducirlo.

Personalmente, escribiría una consulta como una unión (¡aunque evitaría los nulos por completo!):

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

Así que supongo que estoy de acuerdo con el espíritu de la respuesta de @ MAbraham1 (aunque su código parece no estar relacionado con la pregunta).

Sin embargo, parece que la consulta está diseñada expresamente para producir un resultado de columna única que comprende filas duplicadas, ¡de hecho nulos duplicados! Es difícil no llegar a la conclusión de que este enfoque es erróneo.

un día cuando
fuente