novembre
2007
Le défaut de LINQ c’est qu’il va devenir beaucoup plus facile de faire du code moins performant.
Prenons un exemple :
{
private List<Facture> _factures;
public string Nom { get; set; }
public string Prenom { get; set; }
public List<Facture> Factures
{
get
{
if (_factures == null)
_factures = new List<Facture>();
return _factures;
}
}
}
public class Facture
{
private Client _client;
public Client Client
{
get { return _client; }
set
{
if (_client != null)
_client.Factures.Remove(this);
_client = value;
_client.Factures.Add(this);
}
}
public decimal Prix { get; set; }
}
Imaginons que l’on veuille récupérer les clients ayant dépensé au moins 50 euros en les triant par ordre décroissant de dépense, puis alphabétiquement.
Le code en C# 2.0 pourrait être le suivant :
copyClients.AddRange(clients);
copyClients.Sort(delegate(Client x, Client y)
{
decimal xFacturesSomme = 0, yFacturesSomme = 0;
foreach (Facture facture in x.Factures)
xFacturesSomme += facture.Prix;
foreach (Facture facture in y.Factures)
yFacturesSomme += facture.Prix;
int compareFactureSomme = xFacturesSomme.CompareTo(yFacturesSomme);
if (compareFactureSomme != 0)
return -compareFactureSomme;
int compareNom = x.Nom.CompareTo(y.Nom);
if (compareNom != 0)
return compareNom;
return x.Prenom.CompareTo(y.Prenom);
});
foreach (Client c in copyClients)
{
decimal facturesSomme = 0;
foreach (Facture facture in c.Factures)
facturesSomme += facture.Prix;
if (facturesSomme > 50)
Console.WriteLine("{0} {1} a dépensé {2} euros", c.Nom, c.Prenom, facturesSomme);
}
Avec LINQ, ce code peut être grandement simplifié. Mais attention, on pourrait écrire ceci :
(from client in clients
select new
{
client.Nom,
client.Prenom,
TotalDepense = (from f in client.Factures
select f.Prix).Sum()
})
where c.TotalDepense > 50
orderby c.TotalDepense descending, c.Nom, c.Prenom
select c)
Console.WriteLine("{0} {1} a dépensé {2} euros", statClient.Nom, statClient.Prenom, statClient.TotalDepense);
Cependant, analysons de plus près ce code :
Pour calculer la somme totale dépensée par le client, nous parcourons toutes les factures pour récupérer un IEnumerable sur le prix de celles-ci, puis nous reparcourons cet IEnumerable pour faire la somme.
L’idée serait donc de regrouper ces deux boucles en une seule. Pour cela, utilisons une lambda expression et une autre signature de la méthode Sum :
(from client in clients
select new
{
client.Nom,
client.Prenom,
TotalDepense = client.Factures.Sum(f => f.Prix)
})
where c.TotalDepense > 50
orderby c.TotalDepense descending, c.Nom, c.Prenom
select c)
Console.WriteLine("{0} {1} a dépensé {2} euros", statClient.Nom, statClient.Prenom, statClient.TotalDepense);
L’autre problème que l’on peut identifier, c’est le fait que nous parcourons une première boucle sur les clients pour récupérer un IEnumerable de type anonyme avec le nom, le prénom et la somme dépensée et qu’ensuite, nous reparcourons cet IEnumerable pour les trier. Effectivement, pour pouvoir trier par la somme dépensée, il faut au préalable l’avoir calculée.
Là encore, une meilleure utilisation de LINQ pourrait nous permettre d’optimiser cela :
let totalDepense = c.Factures.Sum(f => f.Prix)
where totalDepense > 50
orderby totalDepense descending, c.Nom, c.Prenom
select new
{
c.Nom,
c.Prenom,
TotalDepense = totalDepense
})
Console.WriteLine("{0} {1} a dépensé {2} euros", statClient.Nom, statClient.Prenom, statClient.TotalDepense);
Via ce post, je cherche à démontrer deux choses :
la requête LINQ est beaucoup plus simple et claire que le code C# 2.0.
une mauvaise connaissance de LINQ peut engendrer des pertes de performances importantes.
En revanche, avec LINQ To SQL, même si je ne vous conseille bien entendu pas d’écrire de « mauvaises » requêtes, la requête SQL générée est optimisée. Dans les trois cas précédents, la requête sera la suivante :
FROM (
SELECT [t0].[Nom], [t0].[Prenom], (
SELECT SUM([t1].[Prix])
FROM [dbo].[Facture] AS [t1]
WHERE [t1].[Client] = [t0].[Id]
) AS [value]
FROM [dbo].[Client] AS [t0]
) AS [t2]
WHERE [t2].[value] > @p0
ORDER BY [t2].[value] DESC, [t2].[Nom], [t2].[Prenom]
Le risque lié à une mauvaise connaissance de LINQ peut cependant être fortement réduit avec une formation (http://www.winwise.fr/formation/Presentation-de-LINQ-W095.aspx par exemple).