União externa esquerda LINQ to SQL

140

Essa consulta é equivalente a uma LEFT OUTERassociação?

//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
fonte

Respostas:

167

Não é bem assim - já que cada linha "esquerda" em uma junção externa esquerda corresponderá a 0-n linhas "direita" (na segunda tabela), enquanto que a sua corresponde apenas a 0-1. Para fazer uma junção externa esquerda, você precisa SelectManye DefaultIfEmpty, por exemplo:

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

( ou através dos métodos de extensão )

Marc Gravell
fonte
19
Alguém pode explicar como essa sintaxe maluca funciona? Não consigo ver como qualquer uma dessas palavras-chave magicamente a torna uma junção esquerda. O que o "into sr" faz? Linq me frustra, por vezes :)
Joe Phillips
2
@JoePhillips Tenho muita experiência em SQL, mas tentar aprender o LINQ é como percorrer a lama. Eu concordo que é absolutamente louco.
Nick.McDermaid
@ Marc-Gravell: Você poderia me ajudar a resolver a minha consulta sql para conversão linq: stackoverflow.com/questions/28367941/...
Vishal I Patil
@VishalIPatil Por que você deseja converter de SQL para LINQ? SQL funciona muito bem e é muito mais previsível e eficiente ...
Marc Gravell
1
@VishalIPatil então ... por que fazer isso? Quase todas as ferramentas do LINQ incluem a capacidade de executar SQL manuscrito. Por que não fazer isso?
Marc Gravell
216

Você não precisa das instruções 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

E sim, a consulta acima realmente cria uma junção ESQUERDA OUTER.

Link para uma pergunta semelhante que lida com várias associações à esquerda: Linq to Sql: várias associações externas à esquerda

Amir
fonte
14
Embora eu saiba que a resposta de @Marc Gravvel funciona, eu realmente prefiro esse método porque, na IMO, ele se parece mais com o que deve ser uma junção esquerda.
Llaughlin 6/08
1
Excelente resposta. Procurando por mais de 5 horas de pesquisa no google. Esta é a única maneira que o SQL resultante terá deixado entrar nele.
Faisal Mq
1
MUITO OBRIGADO .... Eu estava procurando uma solução para isso a tarde toda e seu código acertou em cheio (e parece natural de inicializar). Gostaria de poder votar isso várias vezes.
Jim Jim
2
@ Jim obrigado :-) Estou feliz que os desenvolvedores ainda estão recebendo milhas desta resposta. Concordo plenamente que o DefaultIfEmpty () parece muito mais natural do que usar as instruções into.
Amir
7
Apenas uma observação para quem encontrar isso como eu acabei de encontrar, isso resulta em uma junção externa esquerda dentro de um aplicativo cruzado, o que significa que você receberá duplicatas se houver várias correspondências no lado direito da associação. A solução de Marc Gravell, embora não tão "bonita", me deu a saída e o conjunto de resultados SQL que eu estava procurando.
você
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
fonte
Boa tentativa, mas parece que o OP está usando c #. A sintaxe do VB é estranhamente diferente.
Levitikon 6/09/12
5

Encontrei 1 solução. se quiser traduzir esse tipo de SQL (junção esquerda) para a entidade 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
fonte
Veja este comentário , as entidades Linq-SQL não suportam DefaultIfEmpty.
TJ Crowder
2

Eu gostaria de adicionar mais uma coisa. No LINQ to SQL, se seu banco de dados estiver construído corretamente e suas tabelas estiverem relacionadas por restrições de chave estrangeira, você não precisará fazer nenhuma associação.

Usando o LINQPad, criei a seguinte 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 foi traduzido para a consulta (ligeiramente truncada) abaixo

 -- 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 o LEFT OUTER JOINacima.

Brian Kraemer
fonte
1

Cuide do desempenho:

Eu experimentei que pelo menos com o EF Core as diferentes respostas fornecidas aqui podem resultar em desempenho diferente. Estou ciente de que o OP perguntou sobre o Linq to SQL, mas parece-me que as mesmas perguntas ocorrem também com o EF Core.

Em um caso específico que eu tive que lidar, a sugestão (sintaticamente mais agradável) de Marc Gravell resultou em junções esquerdas dentro de uma aplicação cruzada - semelhante ao que Mike U descreveu - que resultou em que os custos estimados para essa consulta específica eram dois vezes mais alto em comparação com uma consulta sem junções cruzadas . Os tempos de execução do servidor diferiram por um fator de 3 . [1]

A solução de Marc Gravell resultou em uma consulta sem junções cruzadas.

Contexto: eu basicamente precisava realizar duas junções esquerdas em duas tabelas, cada uma das quais novamente exigia uma junção a outra tabela. Além disso, lá eu tive que especificar outras condições onde nas tabelas nas quais eu precisava aplicar a junção esquerda. Além disso, eu tinha duas junções internas na mesa principal.

Custos estimados do operador:

  • com cruz aplicar: 0,2534
  • sem aplicação cruzada: 0,0991.

Tempos de execução do servidor em ms (consultas executadas 10 vezes; medidas usando SET STATISTICS TIME ON):

  • com cruz aplicar: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • sem aplicação cruzada: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(A primeira execução foi mais lenta nas duas consultas; parece que algo está armazenado em cache.)

Tamanhos da tabela:

  • tabela principal: 87 linhas,
  • primeira tabela para junção esquerda: 179 linhas;
  • segunda tabela para junção esquerda: 7 linhas.

Versão EF Core: 2.2.1.

Versão do SQL Server: MS SQL Server 2017 - 14 ... (no Windows 10).

Todas as tabelas relevantes tinham índices apenas nas chaves primárias.

Minha conclusão: é sempre recomendável olhar para o SQL gerado, pois ele pode realmente diferir.


[1] Curiosamente, ao definir as 'Estatísticas do cliente' no MS SQL Server Management Studio, pude ver uma tendência oposta; ou seja, a última execução da solução sem aplicação cruzada levou mais de 1s. Suponho que algo estava errado aqui - talvez com a minha configuração.

Andreas Schütz
fonte