Jointure externe gauche LINQ vers SQL

140

Cette requête est-elle équivalente à une LEFT OUTERjointure?

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

Réponses:

167

Pas tout à fait - puisque chaque ligne «gauche» dans une jointure externe gauche correspondra à 0-n lignes «droites» (dans la deuxième table), où comme la vôtre ne correspondra qu'à 0-1. Pour faire une jointure externe gauche, vous avez besoin de SelectManyet DefaultIfEmpty, par exemple:

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 via les méthodes d'extension )

Marc Gravell
la source
19
Quelqu'un peut-il expliquer comment fonctionne cette syntaxe folle? Je ne vois pas comment l'un de ces mots-clés en fait comme par magie une jointure à gauche. Que fait le "into sr"? Linq me frustre parfois :)
Joe Phillips
2
@JoePhillips J'ai beaucoup d'expérience SQL, mais essayer d'apprendre LINQ, c'est comme patauger dans la boue. Je suis d'accord que c'est absolument fou.
Nick.McDermaid
@ marc-gravell: Pourriez-vous m'aider à résoudre ma requête SQL en conversion linq
Vishal I Patil
@VishalIPatil pourquoi voulez-vous convertir SQL en LINQ? SQL fonctionne très bien et est beaucoup plus prévisible et efficace ...
Marc Gravell
1
@VishalIPatil alors ... pourquoi faire ça? Presque tous les outils LINQ incluent la possibilité d'exécuter du SQL manuscrit. Pourquoi ne pas faire ça?
Marc Gravell
216

Vous n'avez pas besoin des instructions 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

Et oui, la requête ci-dessus crée en effet une jointure LEFT OUTER.

Lien vers une question similaire qui gère plusieurs jointures à gauche: Linq à Sql: plusieurs jointures externes à gauche

Amir
la source
14
Bien que je sache que la réponse de @Marc Gravvel fonctionne, je préfère vraiment cette méthode parce que l'OMI se sent plus conforme à ce à quoi une jointure gauche devrait ressembler.
llaughlin
1
Excellente réponse. Vous recherchez plus de 5 heures de recherche Google. C'est la seule façon dont le SQL résultant aura laissé la jointure.
Faisal Mq
1
MERCI tellement .... Je cherchais une solution pour cela tout l'après-midi et votre code l'a cloué (et semble naturel pour démarrer). J'aimerais pouvoir voter plusieurs fois.
Jim
2
@Jim merci :-) Je suis content que les développeurs tirent encore parti de cette réponse. Je suis entièrement d'accord que DefaultIfEmpty () semble beaucoup plus naturel que d'utiliser les instructions into.
Amir
7
Juste une note pour toute autre personne qui trouve cela comme je viens de le faire, cela se traduit par une JOINTURE EXTÉRIEURE GAUCHE dans une APPLICATION CROISÉE, ce qui signifie que vous obtiendrez des doublons s'il y a plusieurs correspondances sur le côté droit de la jointure. La solution de Marc Gravell, bien que pas aussi "jolie", m'a donné la bonne sortie SQL et l'ensemble de résultats que je recherchais.
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

Vérifiez http://msdn.microsoft.com/en-us/vbasic/bb737929.aspx

Krishnaraj Barvathaya
la source
Bien essayé mais il semble que l'OP utilise c #. La syntaxe VB est étrangement différente.
Levitikon
5

J'ai trouvé 1 solution. si vous voulez traduire ce type de SQL (jointure gauche) en entité 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
la source
Voir ce commentaire , les entités Linq-to-SQL ne prennent pas en charge DefaultIfEmpty.
TJ Crowder
2

J'aimerais ajouter une dernière chose. Dans LINQ to SQL, si votre base de données est correctement construite et que vos tables sont liées via des contraintes de clé étrangère, vous n'avez pas du tout besoin de faire une jointure.

À l'aide de LINQPad, j'ai créé la requête LINQ suivante:

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

Ce qui a été traduit en requête (légèrement tronquée) ci-dessous

 -- 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]

Notez ce qui LEFT OUTER JOINprécède.

Brian Kraemer
la source
1

Prenez soin de la performance:

J'ai constaté qu'au moins avec EF Core, les différentes réponses données ici pouvaient entraîner des performances différentes. Je suis conscient que l'OP a posé des questions sur Linq to SQL, mais il me semble que les mêmes questions se posent également avec EF Core.

Dans un cas spécifique que j'ai dû gérer, la suggestion (syntaxiquement plus agréable) de Marc Gravell a entraîné des jointures à gauche à l'intérieur d'une application croisée - de la même manière que Mike U a décrit - ce qui a eu pour résultat que les coûts estimés pour cette requête spécifique étaient de deux fois plus élevé par rapport à une requête sans jointures croisées . Les temps d'exécution du serveur différaient d'un facteur 3 . [1]

La solution de Marc Gravell a abouti à une requête sans jointures croisées.

Contexte: J'avais essentiellement besoin d'effectuer deux jointures à gauche sur deux tables dont chacune nécessitait à nouveau une jointure à une autre table. De plus, là, j'ai dû spécifier d'autres conditions where sur les tables sur lesquelles je devais appliquer la jointure gauche. De plus, j'avais deux jointures internes sur la table principale.

Estimation des coûts d'opérateur:

  • avec croix s'appliquent: 0,2534
  • sans croix s'appliquent: 0,0991.

Temps d'exécution du serveur en ms (requêtes exécutées 10 fois; mesurées à l'aide de SET STATISTICS TIME ON):

  • avec croix appliquer: 5, 6, 6, 6, 6, 6, 6, 6, 6, 6
  • sans croix s'appliquent: 2, 2, 2, 2, 2, 2, 2, 2, 2, 2

(La toute première exécution était plus lente pour les deux requêtes; il semble que quelque chose soit mis en cache.)

Tailles de table:

  • table principale: 87 lignes,
  • première table pour jointure gauche: 179 lignes;
  • deuxième table pour jointure gauche: 7 lignes.

Version EF Core: 2.2.1.

Version de SQL Server: MS SQL Server 2017 - 14 ... (sous Windows 10).

Toutes les tables pertinentes avaient des index sur les clés primaires uniquement.

Ma conclusion: il est toujours recommandé de regarder le SQL généré car il peut vraiment différer.


[1] Il est intéressant de noter que lors de la configuration des «Statistiques client» dans MS SQL Server Management Studio, j'ai pu voir une tendance opposée; à savoir que la dernière exécution de la solution sans application croisée a pris plus de 1 s. Je suppose que quelque chose n'allait pas ici - peut-être avec ma configuration.

Andreas Schütz
la source