Unión externa izquierda de LINQ to SQL

140

¿Es esta consulta equivalente a una LEFT OUTERunión?

//assuming that I have a parameter named 'invoiceId' of type int
from c in SupportCases
let invoice = c.Invoices.FirstOrDefault(i=> i.Id == invoiceId)
where (invoiceId == 0 || invoice != null)    
select new 
{
      Id = c.Id
      , InvoiceId = invoice == null ? 0 : invoice.Id
}
Ali Kazmi
fuente

Respuestas:

167

No del todo, ya que cada fila "izquierda" en una combinación externa izquierda coincidirá con 0-n filas "derecha" (en la segunda tabla), donde-como la suya solo coincide con 0-1. Para hacer una unión externa izquierda, necesita SelectManyy DefaultIfEmpty, por ejemplo:

var query = from c in db.Customers
            join o in db.Orders
               on c.CustomerID equals o.CustomerID into sr
            from x in sr.DefaultIfEmpty()
            select new {
               CustomerID = c.CustomerID, ContactName = c.ContactName,
               OrderID = x == null ? -1 : x.OrderID };   

( o mediante los métodos de extensión )

Marc Gravell
fuente
19
¿Alguien puede explicar cómo funciona esta sintaxis loca? No veo cómo ninguna de esas palabras clave la convierte mágicamente en una combinación izquierda. ¿Qué hace el "into sr"? Linq me frustra a veces :)
Joe Phillips
2
@JoePhillips Tengo mucha experiencia en SQL, pero tratar de aprender LINQ es como meterse en el barro. Estoy de acuerdo, es absolutamente loco.
Nick.McDermaid
@ marc-gravell: ¿Podría ayudarme a resolver mi consulta sql a la conversión de linq: stackoverflow.com/questions/28367941/…
Vishal I Patil
@VishalIPatil ¿por qué quieres convertir de SQL a LINQ? SQL funciona bien y es mucho más predecible y eficiente ...
Marc Gravell
1
@VishalIPatil entonces ... ¿por qué hacer eso? Casi todas las herramientas LINQ incluyen la capacidad de ejecutar SQL escrito a mano. ¿Por qué no solo hacer eso?
Marc Gravell
216

No necesita las declaraciones into:

var query = 
    from customer in dc.Customers
    from order in dc.Orders
         .Where(o => customer.CustomerId == o.CustomerId)
         .DefaultIfEmpty()
    select new { Customer = customer, Order = order } 
    //Order will be null if the left join is null

Y sí, la consulta anterior crea una unión EXTERIOR IZQUIERDA.

Enlace a una pregunta similar que maneja múltiples combinaciones izquierdas: Linq to Sql: Varias combinaciones externas izquierdas

Amir
fuente
14
Si bien sé que la respuesta de @Marc Gravvel funciona, realmente prefiero este método porque, en mi opinión, se siente más en línea con lo que debería ser una unión izquierda.
llaughlin
1
Excelente respuesta Buscando más de 5 horas de búsqueda en google. Esta es la única forma en que el SQL resultante habrá dejado unirse en él.
Faisal Mq
1
MUCHAS GRACIAS ... Estaba buscando una solución para esto toda la tarde y su código lo clavó (y se siente natural para arrancar). Ojalá pudiera votar esto varias veces.
Jim
2
@ Jim, gracias :-) Me alegro de que los desarrolladores sigan obteniendo millas de esta respuesta. Estoy completamente de acuerdo en que DefaultIfEmpty () se siente mucho más natural que usar las declaraciones into.
Amir
77
Solo una nota para cualquier persona que encuentre esto como acabo de hacer, esto da como resultado una IZQUIERDA EXTERIOR IZQUIERDA dentro de una APLICACIÓN CRUZADA, lo que significa que obtendrá duplicados si hay varias coincidencias en el lado derecho de la unión. La solución de Marc Gravell, aunque no tan "bonita", me dio la salida SQL adecuada y el conjunto de resultados que estaba buscando.
Mike U
13
Public Sub LinqToSqlJoin07()
Dim q = From e In db.Employees _
        Group Join o In db.Orders On e Equals o.Employee Into ords = Group _
        From o In ords.DefaultIfEmpty _
        Select New With {e.FirstName, e.LastName, .Order = o}

ObjectDumper.Write(q) End Sub

Verifique http://msdn.microsoft.com/en-us/vbasic/bb737929.aspx

Krishnaraj Barvathaya
fuente
Buen intento pero parece que el OP está usando c #. La sintaxis de VB es extrañamente diferente.
Levitikon
5

Encontré 1 solución. si desea traducir este tipo de SQL (combinación izquierda) en la entidad Linq ...

SQL:

SELECT * FROM [JOBBOOKING] AS [t0]
LEFT OUTER JOIN [REFTABLE] AS [t1] ON ([t0].[trxtype] = [t1].[code])
                                  AND ([t1]. [reftype] = "TRX")

LINQ:

from job in JOBBOOKINGs
join r in (from r1 in REFTABLEs where r1.Reftype=="TRX" select r1) 
          on job.Trxtype equals r.Code into join1
from j in join1.DefaultIfEmpty()
select new
{
   //cols...
}
mokth
fuente
Vea este comentario , las entidades de Linq a SQL no son compatibles DefaultIfEmpty.
TJ Crowder
2

Me gustaría agregar una cosa más. En LINQ to SQL, si su base de datos está construida correctamente y sus tablas están relacionadas a través de restricciones de clave externa, entonces no necesita hacer una unión.

Usando LINQPad creé la siguiente consulta LINQ:

//Querying from both the CustomerInfo table and OrderInfo table
from cust in CustomerInfo
where cust.CustomerID == 123456
select new {cust, cust.OrderInfo}

Que se tradujo a la consulta (ligeramente truncada) a continuación

 -- Region Parameters
 DECLARE @p0 Int = 123456
-- EndRegion
SELECT [t0].[CustomerID], [t0].[AlternateCustomerID],  [t1].[OrderID], [t1].[OnlineOrderID], (
    SELECT COUNT(*)
    FROM [OrderInfo] AS [t2]
    WHERE [t2].[CustomerID] = [t0].[CustomerID]
    ) AS [value]
FROM [CustomerInfo] AS [t0]
LEFT OUTER JOIN [OrderInfo] AS [t1] ON [t1].[CustomerID] = [t0].[CustomerID]
WHERE [t0].[CustomerID] = @p0
ORDER BY [t0].[CustomerID], [t1].[OrderID]

Observe lo LEFT OUTER JOINanterior.

Brian Kraemer
fuente
1

Cuida el rendimiento:

Experimenté que al menos con EF Core, las diferentes respuestas dadas aquí podrían dar como resultado un rendimiento diferente. Soy consciente de que el OP preguntó sobre Linq to SQL, pero me parece que las mismas preguntas también ocurren con EF Core.

En un caso específico que tuve que manejar, la sugerencia (sintácticamente más agradable) de Marc Gravell resultó en uniones izquierdas dentro de una aplicación cruzada, de manera similar a lo que describió Mike U, lo que resultó en que los costos estimados para esta consulta específica eran dos veces más alto en comparación con una consulta sin uniones cruzadas . Los tiempos de ejecución del servidor diferían en un factor de 3 . [1]

La solución de Marc Gravell resultó en una consulta sin combinaciones cruzadas.

Contexto: esencialmente necesitaba realizar dos uniones izquierdas en dos tablas, cada una de las cuales nuevamente requería una unión a otra tabla. Además, allí tuve que especificar otras condiciones where en las tablas en las que necesitaba aplicar la combinación izquierda. Además, tenía dos uniones internas en la mesa principal.

Costos estimados del operador:

  • con aplicación cruzada: 0.2534
  • sin aplicación cruzada: 0.0991.

Tiempos de ejecución del servidor en ms (consultas ejecutadas 10 veces; medido usando SET STATISTICS TIME ON):

  • con aplicación cruzada: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • sin aplicación cruzada: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(La primera ejecución fue más lenta para ambas consultas; parece que algo está en caché).

Tamaños de mesa:

  • tabla principal: 87 filas,
  • primera tabla para la unión izquierda: 179 filas;
  • segunda tabla para la unión izquierda: 7 filas.

Versión EF Core: 2.2.1.

Versión de SQL Server: MS SQL Server 2017-14 ... (en Windows 10).

Todas las tablas relevantes tenían índices solo en las claves primarias.

Mi conclusión: siempre se recomienda mirar el SQL generado, ya que realmente puede diferir.


[1] Curiosamente, al configurar las 'Estadísticas del cliente' en MS SQL Server Management Studio, pude ver una tendencia opuesta; a saber, que la última ejecución de la solución sin aplicación cruzada tomó más de 1 segundo. Supongo que algo iba mal aquí, tal vez con mi configuración.

Andreas Schütz
fuente