2015-12-27 1 views
0

J'ai ce simple LINQ, et je n'arrive pas à comprendre pourquoi le SQL qui en résulte est si étrange.La requête SQL générée par LINQ contient deux mêmes SELECT

C'est le LINQ:

var query = 
(
    from fileRecord in _context.FileRecords 
    join linkAttr in _context.LinkAttributes on fileRecord.Id equals linkAttr.FileRecordId 
    where linkAttr.ValidTo == null 
    join titleAttr in _context.TitleAttributes on fileRecord.Id equals titleAttr.FileRecordId into titleGJ 
    join textAttr in _context.TextAttributes on fileRecord.Id equals textAttr.FileRecordId into textGJ 
    join publishedAttr in _context.PublishedAttributes on fileRecord.Id equals publishedAttr.FileRecordId into publishedGJ 
    select new Set 
    { 
     Id = fileRecord.Id, 
     ParentId = fileRecord.Type == FileRecordType.Photobank ? null : linkAttr.ParentId, 
     Title = 
     (
      from titleAttr in titleGJ 
      where titleAttr.ValidTo == null && titleAttr.CultureId == cultureId 
      select titleAttr.Title 
     ).FirstOrDefault(), 
     Text = 
     (
      from textAttr in textGJ 
      where textAttr.ValidTo == null && textAttr.CultureId == cultureId 
      select textAttr.Text 
     ).FirstOrDefault(), 
     Published = 
     (
      from publishedAttr in publishedGJ 
      where publishedAttr.ValidTo == null && publishedAttr.CultureId == cultureId 
      select publishedAttr.Published 
     ).FirstOrDefault() 
    } 
); 

Les trois entités - dans ce cas TitleAttribute, TextAttribute et PublishedAttribute sont presque les mêmes classes, PublishedAttribute contient encore une propriété, mais dans le LINQ n'est pas utilisé.

Mais la requête générée est bizarre. La sélection de la table PublishedAttributes est là deux fois (marqué par les commentaires):

SELECT 
    [Project12].[Id] AS [Id], 
    CASE WHEN (5 = [Project12].[Type]) THEN CAST(NULL AS int) ELSE [Project12].[ParentId] END AS [C1], 
    [Project12].[C1] AS [C2], 
    [Project12].[C2] AS [C3], 
    CASE WHEN ([Project12].[C3] IS NULL) THEN cast(0 as bit) ELSE [Project12].[C4] END AS [C4] 
    FROM (SELECT 
     [Project10].[Id] AS [Id], 
     [Project10].[Type] AS [Type], 
     [Project10].[ParentId] AS [ParentId], 
     [Project10].[C1] AS [C1], 
     [Project10].[C2] AS [C2], 
     [Project10].[C3] AS [C3], 
     -- THIS ONE 
     (SELECT TOP (1) 
      [Extent6].[Published] AS [Published] 
      FROM [fs].[PublishedAttributes] AS [Extent6] 
      WHERE ([Project10].[Id] = [Extent6].[FileRecordId]) AND ([Extent6].[ValidTo] IS NULL) AND ([Extent6].[CultureId] = @p__linq__3)) AS [C4] 
     FROM (SELECT 
      [Project9].[Id] AS [Id], 
      [Project9].[Type] AS [Type], 
      [Project9].[ParentId] AS [ParentId], 
      [Project9].[C1] AS [C1], 
      [Project9].[C2] AS [C2], 
      [Project9].[C3] AS [C3] 
      FROM (SELECT 
       [Project7].[Id] AS [Id], 
       [Project7].[Type] AS [Type], 
       [Project7].[ParentId] AS [ParentId], 
       [Project7].[C1] AS [C1], 
       [Project7].[C2] AS [C2], 
       -- AND THIS ONE 
       (SELECT TOP (1) 
        [Extent5].[Published] AS [Published] 
        FROM [fs].[PublishedAttributes] AS [Extent5] 
        WHERE ([Project7].[Id] = [Extent5].[FileRecordId]) AND ([Extent5].[ValidTo] IS NULL) AND ([Extent5].[CultureId] = @p__linq__3)) AS [C3] 
       FROM (SELECT 
        [Project6].[Id] AS [Id], 
        [Project6].[Type] AS [Type], 
        [Project6].[ParentId] AS [ParentId], 
        [Project6].[C1] AS [C1], 
        [Project6].[C2] AS [C2] 
        FROM (SELECT 
         [Project4].[Id] AS [Id], 
         [Project4].[Type] AS [Type], 
         [Project4].[ParentId] AS [ParentId], 
         [Project4].[C1] AS [C1], 
         (SELECT TOP (1) 
          [Extent4].[Text] AS [Text] 
          FROM [fs].[TextAttributes] AS [Extent4] 
          WHERE ([Project4].[Id] = [Extent4].[FileRecordId]) AND ([Extent4].[ValidTo] IS NULL) AND ([Extent4].[CultureId] = @p__linq__2)) AS [C2] 
         FROM (SELECT 
          [Project3].[Id] AS [Id], 
          [Project3].[Type] AS [Type], 
          [Project3].[ParentId] AS [ParentId], 
          [Project3].[C1] AS [C1] 
          FROM (SELECT 
           [Project1].[Id] AS [Id], 
           [Project1].[Type] AS [Type], 
           [Project1].[ParentId] AS [ParentId], 
           (SELECT TOP (1) 
            [Extent3].[Title] AS [Title] 
            FROM [fs].[FileTitleAttributes] AS [Extent3] 
            WHERE ([Project1].[Id] = [Extent3].[FileRecordId]) AND ([Extent3].[ValidTo] IS NULL) AND ([Extent3].[CultureId] = @p__linq__1)) AS [C1] 
           FROM (SELECT 
            [Extent1].[Id] AS [Id], 
            [Extent1].[Type] AS [Type], 
            [Extent2].[ParentId] AS [ParentId] 
            FROM [fs].[Files] AS [Extent1] 
            INNER JOIN [fs].[LinkAttributes] AS [Extent2] ON [Extent1].[Id] = [Extent2].[FileRecordId] 
            WHERE ((([Extent1].[BrandId] = @p__linq__0) AND (2 = [Extent1].[Type])) OR (5 = [Extent1].[Type])) AND ([Extent2].[ValidTo] IS NULL) 
           ) AS [Project1] 
          ) AS [Project3] 
         ) AS [Project4] 
        ) AS [Project6] 
       ) AS [Project7] 
      ) AS [Project9] 
     ) AS [Project10] 
    ) AS [Project12] 

Dans les trois tableaux (TitleAttributes, TextAttributes et PublishedAttributes), toutes les touches sont les mêmes, tous les index sont les mêmes, la cartographie CF EF, tout est pareil - mais je ne comprends pas pourquoi la requête générée contient deux fois le même SELECT. Je suis embrouillé et après plusieurs heures, sans idée.

EF6.1, .NET 4.6.1

EDIT: Je trouve que si la valeur que je suis sélection est une chaîne, la SELECT appropriée est là correctement une fois. Sinon, si c'est int ou bool, il est sélectionné deux fois. titleAttr.Title et textAttr.Text sont des chaînes; publishedAttr.Published est booléen - il est donc sélectionné deux fois pour une raison quelconque. Bizarre.

+0

Je ne l'ai pas retracé (trop complexe à faire rapidement) mais je parie que c'est à cause des trois méthodes FirstOrDefault() lors de la construction de l'ensemble de retour. Ceux-ci le forcent à exécuter la requête plusieurs fois pour s'assurer qu'un résultat revient quel que soit le résultat qui manque. –

+0

C'était aussi ma première pensée, donc dans ce LINQ j'ai essayé de mettre cette publication = ... au début, au milieu ... et ces deux sélections étaient toujours là sur la même table. Dans le plan d'exécution, la sous-sélection inutile coûte environ 20%, ce qui me dérange. Mais de plus, j'aimerais savoir ce qui ne va pas avec ma compréhension de LINQ to SQL. –

Répondre

0

J'ai finalement découvert/réalisé que le problème se produit lorsque la valeur sélectionnée est annulable, la réponse est ici:

CASE WHEN ([Project12].[C3] IS NULL) THEN cast(0 as bit) ELSE [Project12].[C4] END AS [C4] 

Il est nécessaire de savoir si la valeur est nulle (première sélection), le cas échéant , retourne 0, si ce n'est pas le cas, renvoie la valeur (seconde sélection).