2017-07-19 7 views
0

Ma tâche est d'optimiser certaines méthodes et en regardant le code que je trouve ce goulot d'étranglement (Pardonnez-moi si le formatage semble désactivé):résultat IQueryable différent de IEnumerable

// ... 
    IEnumerable<Card> storageCards = 
     Db.StorageCards 
      .Where(x => x.Active && x.DocumentType == (int)DocumentType.Import); 

    // excludeLastDate is bool 
    storageCards = storageCards.Where(x => (excludeLastDate && x.Date < toD) 
             || (!excludeLastDate && x.Date <= toD)); 

    return LocationPriceData(storageCards, locationId.Value); 
} 

private Dictionary<int, decimal> LocationPriceData(IEnumerable<Card> storageCards 
                , int locationId) 
{ 
    // sc.LocationId, sc.ProductId are nullable int 
    // sc.Price is nullable decimal 
    // sc.Date is datetime, not null 
    var result = from sc in storageCards 
        where sc.LocationId == locationId 
        group sc by sc.ProductId 
         into g 
         let price = g.OrderByDescending(t => t.Date) 
            .ThenByDescending(t => t.Id) 
            .FirstOrDefault(t => t.Price.HasValue) 
         where price != null 
         select new 
         { 
          ProductId = g.Key.Value, 
          Price = price.LevelInputPrice.Value 
         }; 

    return result.ToDictionary(x => x.ProductId, x => x.Price); 
} 

Pour l'améliorer, je changé sous forme de type storageCardIEnumerable<Card> à IQueryable<Card> (en LocationPriceData signature ainsi) qui a fait une énorme différence, mais maintenant les résultats sont différents aussi! Je comprends que l'amélioration des performances s'est produite en raison de la différence d'implémentation IEnumerable et IQueryable et que différentes données sont extraites de la base de données, mais pourquoi le résultat final est-il différent?

Je suspecte la partie group mais comme il compare un int nullable je ne peux pas voir une raison pour le résultat différent? Db est le serveur MS SQL.

Oh, et dans les deux cas, la matérialisation arrive à la ligne return result.ToDictionary(x => x.ProductId, x => x.Price);

EDIT

Lors de la modification de la requête IQueryable généré est pas comme ce que je pensais. Le Cross Application n'a aucune commande dedans!

La logique derrière le code suivant est jamais exécuté sur DB:

    let price = g.OrderByDescending(t => t.Date) 
           .ThenByDescending(t => t.Id) 
           .FirstOrDefault(t => t.Price.HasValue) 

Voici le SQL généré:

exec sp_executesql N'SELECT 
[Element1].[Id] AS [Id], 
[Project2].[ProductId] AS [ProductId], 
[Element1].[LevelInputPrice] AS [LevelInputPrice] 
FROM (SELECT 
    [Distinct1].[ProductId] AS [ProductId] 
    FROM (SELECT DISTINCT 
     [Extent1].[ProductId] AS [ProductId] 
     FROM [dbo].[StorageCard] AS [Extent1] 
     WHERE ((([Extent1].[Active] = 1) AND (10 = [Extent1].[DocumentType])) OR (40 = [Extent1].[DocumentType]) OR ((70 = [Extent1].[DocumentType]) AND ([Extent1].[InputQuantity] > cast(0 as decimal(18))))) AND (((@p__linq__0 = 1) AND ([Extent1].[Date] < @p__linq__1)) OR ((@p__linq__2 <> cast(1 as bit)) AND ([Extent1].[Date] <= @p__linq__3))) AND ([Extent1].[LocationId] = @p__linq__4) 
    ) AS [Distinct1]) AS [Project2] 
CROSS APPLY (SELECT TOP (1) 
    [Extent2].[Id] AS [Id], 
    [Extent2].[Date] AS [Date], 
    [Extent2].[DocumentType] AS [DocumentType], 
    [Extent2].[InputQuantity] AS [InputQuantity], 
    [Extent2].[LevelInputPrice] AS [LevelInputPrice], 
    [Extent2].[Active] AS [Active], 
    [Extent2].[LocationId] AS [LocationId], 
    [Extent2].[ProductId] AS [ProductId] 
    FROM [dbo].[StorageCard] AS [Extent2] 
    WHERE ((([Extent2].[Active] = 1) AND (10 = [Extent2].[DocumentType])) OR (40 = [Extent2].[DocumentType]) OR ((70 = [Extent2].[DocumentType]) AND ([Extent2].[InputQuantity] > cast(0 as decimal(18))))) AND (((@p__linq__0 = 1) AND ([Extent2].[Date] < @p__linq__1)) OR ((@p__linq__2 <> cast(1 as bit)) AND ([Extent2].[Date] <= @p__linq__3))) AND ([Extent2].[LocationId] = @p__linq__4) AND (([Project2].[ProductId] = [Extent2].[ProductId]) OR (([Project2].[ProductId] IS NULL) AND ([Extent2].[ProductId] IS NULL))) AND ([Extent2].[LevelInputPrice] IS NOT NULL)) AS [Element1] 
WHERE [Element1].[Id] IS NOT NULL',N'@p__linq__0 bit,@p__linq__1 datetime2(7),@p__linq__2 bit,@p__linq__3 datetime2(7),@p__linq__4 int',@p__linq__0=0,@p__linq__1='2017-07-19 08:43:52.6901840',@p__linq__2=0,@p__linq__3='2017-07-19 08:43:52.6901840',@p__linq__4=11 
+0

Prenez votre question https://codereview.stackexchange.com/ –

+1

En regardant le profileur, une requête est reçue à la ligne 'de ToDictionary'? –

+0

@ TadijaBagarić ..Quel .. semble être ce que vous disiez de toute façon; donc ne pas tenir compte de mes commentaires :) – Rob

Répondre

1

Je ne suis pas du tout familier avec la syntaxe de requête dans LINQ. J'ai le sentiment qu'une instruction let peut ne pas être prise en charge par l'ORM en raison de sa complexité. Il est étrange, cependant, qu'il soit complètement omis du SQL, plutôt que de générer une erreur.

Essayez de donner un coup de feu ce lieu:

var result = storageCards 
    .Where(sc => sc.LocationId == locationId) 
    .GroupBy(sc => sc.ProductId) 
    .Select(g => new { 
     ProductId = g.Key.Value, 
     Price = g.OrderByDescending(t => t.Date) 
       .ThenByDescending(t => t.Id) 
       .Where(t => t.Price.HasValue) 
       .Select(t => t.LevelInputPrice.Value) 
       .FirstOrDefault() 
    }) 
    .Where(a => a.Price != null); 
+1

J'ai essayé une instruction let similaire et Linq2Sql s'en occupe très bien. – sgmoore

+0

Tnx, pas la réponse dont j'avais besoin, mais ça m'a amené à la réponse. –