LINQ : une nouvelle façon de concevoir les algorithmes
Il y a plusieurs façon d'optimiser le code :
- Faire le code le plus rapide à l'exécution
- Faire le code le plus rapide à l'écriture
- Faire le code le plus lisible
- etc.
Comme l'optimisation est donc une notion très relative, je ne vais pas m'en soucier dans les requêtes LINQ suivantes.
Imaginons le cas suivant :
private const string displayXml = @"<?xml version=""1.0"" encoding=""UTF-8""?><Displays><Display>Address1</Display><Display>TheatreName</Display><Display>Address2</Display><Display>City</Display></Displays>";
et un résultat :
string result = @"<NewDataSet>
<SearchResults>
<TheatreID>3528</TheatreID>
<TheatreName>AMC OAK PARK PLAZA 6</TheatreName>
<Address1>9747 QUIVIRA RD</Address1>
<City>SHAWNEE MISSION</City>
<StateAbbr>KS</StateAbbr>
<ZipPlus4>66215</ZipPlus4>
<InterceptMarket>SHAWNEE MISSION</InterceptMarket>
</SearchResults>
<SearchResults>
<TheatreID>5135</TheatreID>
<TheatreName>AMC OAK TREE 6</TheatreName>
<Address1>10006 AURORA AVE N</Address1>
<City>SEATTLE</City>
<StateAbbr>WA</StateAbbr>
<ZipPlus4>98133</ZipPlus4>
<InterceptMarket>SEATTLE</InterceptMarket>
</SearchResults>
<SearchResults>
<TheatreID>7821</TheatreID>
<TheatreName>AMC OAKVIEW 24</TheatreName>
<Address1>3555 S 140TH PLZ</Address1>
<City>OMAHA</City>
<StateAbbr>NE</StateAbbr>
<ZipPlus4>68144</ZipPlus4>
<InterceptMarket>OMAHA</InterceptMarket>
</SearchResults>
</NewDataSet>";
On veut récupérer le résultat en fonction de sa constante : il fallait que les éléments soient classés dans le même ordre que dans sa constante.
La requête LINQ To XML suivante fera très bien l'affaire :
var newXml = new XElement("NewDataSet",
from searchResult in XElement.Parse(result).Descendants("SearchResults")
select new XElement("SearchResults",
from display in XElement.Parse(displayXml).Descendants("Display")
select searchResult.Element(display.Value)));
Complexifions un peu ça. Après avoir récupérer ces éléments displayXml, il faudrait rajouter les éléments non présents dans la constante. Comment faire ceci ?
La façon la plus naturelle, à mon sens, (avec LINQ) est la suivante :
var newXml = new XElement("NewDataSet",
from searchResult in XElement.Parse(result).Descendants("SearchResults")
let displayXmlElement = XElement.Parse(displayXml)
select new XElement("SearchResults",
(from display in displayXmlElement.Descendants("Display")
select searchResult.Element(display.Value)).Union(
from searchResultElement in searchResult.Elements().Select(sre => sre.Name.LocalName)
where !displayXmlElement.Descendants("Display").Select(dxe => dxe.Value).Contains(searchResultElement)
select searchResult.Element(searchResultElement))));
Petite remarque au passage, avec le constructeur du XElement, on peut se passer de l'Union :
var newXml = new XElement("NewDataSet",
from searchResult in XElement.Parse(result).Descendants("SearchResults")
let displayXmlElement = XElement.Parse(displayXml)
select new XElement("SearchResults",
from display in displayXmlElement.Descendants("Display")
select searchResult.Element(display.Value),
from searchResultElement in searchResult.Elements().Select(sre => sre.Name.LocalName)
where !displayXmlElement.Descendants("Display").Select(dxe => dxe.Value).Contains(searchResultElement)
select searchResult.Element(searchResultElement)));
Cependant, on peut également pensé autrement : mettre tous les éléments dans la constante puis remettre tous les éléments et enlever les doublons. Ce qui donne :
var newXml = new XElement("NewDataSet",
from searchResult in XElement.Parse(result).Descendants("SearchResults")
select new XElement("SearchResults",
(from display in XElement.Parse(displayXml).Descendants("Display")
select searchResult.Element(display.Value)).Union(searchResult.Elements()).Distinct()));
Cette dernière requête est beaucoup plus lisible que la précédente.Cependant, on peut également
Le but de post n'est bien entendu pas de montrer ce que tout le monde sait : pour un même problème, il y a plusieurs solutions. Le but recherché ici est de montrer comment LINQ permet de gagner en lisibilité avec du code "moins naturelle".
Prenons un autre exemple : dans Northwind, imaginons qu'on veuille récupérer pour chaque produit le client en ayant acheté la plus grande quantité avec la quantité.
Si on utilise LINQ en "mode algorithme classique", on va écrire la requête suivante :
from p in context.Products
let info = (from c in context.Customers
let quantity =
(from o in c.Orders
select
(from od in o.Order_Details
where od.Product == p
select (int)od.Quantity).Sum()).Sum()
orderby quantity descending
select new { Customer = c, Quantity = quantity }).First()
select new { Product = p, info.Customer, info.Quantity };
Si on utilise LINQ en "mode requête", on va plutôt écrire la requête suivante :
from p in context.Products
let info = (from od in p.Order_Details
group od by od.Order.Customer into odByCust
let quantity = odByCust.Sum(od => (int)od.Quantity)
orderby quantity descending
select new { Customer = odByCust.Key, Quantity = quantity }).First()
select new { Product = p, info.Customer, info.Quantity };
Après c'est à vous de décider vers quel critère d'optimisation vous voulez vous orienter.
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :