LINQ og XML
Eksempel 1
Vi skal lese inn en XML-fil, bygge en liste av objekter og så skal vi plukke til ut av lista ved hjelp av LINQ.
Dataene tar vi fra en liste av vinbeskrivelser som beskrevet i modulen Noen datasett.
Vi etablerer et XmlDocument wineDoc fra dataene og vil legge alle vinene inn i en liste: List<wine> WineList. Hver vin er beskrevet med følgende klasse:
class wine : Object { String wname; String wcountry; String wdice; String wdescription; String wtype; public wine(String name,String country,String dice, String description, String type) { wname = name; wcountry = country; wdice = dice; wdescription = description; wtype = type; } public String name { get { return wname; } } public String country { get { return wcountry; } } public String dice { get { return wdice; } } public String type { get { return wtype; } } public String description { get { return wdescription; } } public override string ToString() { return wname; } }
Selve overføringen fra XML til objektliste gjøre ved hjelp av enkel xpath og vanlig DOM-programmering:
private void Form1_Load(object sender, EventArgs e) { // fill up the winelist wineDoc = new XmlDocument(); try { wineDoc.Load(WineSource); XmlNodeList wines = wineDoc.SelectNodes("//wine"); WineList = new List<wine>(wines.Count); foreach (XmlElement wine in wines) WineList.Add(new wine( wine.GetAttribute("name"), wine.GetElementsByTagName("country")[0].InnerText, wine.GetElementsByTagName("dice")[0].InnerText, wine.GetElementsByTagName("description")[0].InnerText, wine.GetElementsByTagName("type")[0].InnerText )); labelMessage.Text = Convert.ToString(wines.Count) +" viner lagt inn"; } catch (Exception ex) { labelMessage.Text = ex.Message; } }
Når vi nå har etablert objektlista kan vi formulere søk i lista. Eksempelet demonstrerer to slike søk ved hjelp av LINQ.
Portugisiske viner
Oppgaven vi skal løse er å velge ut alle vinder med property country lik "Portugal". Det vi gjør er altså å lage et sett, en liste med stringer. Når vi har utført selve søket, løper vi gjennom det vi fant og legger det i listeboksen.
private void buttonPortugal_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines var wines = from w in WineList where w.country == "Portugal" select new{N=w.name}; foreach (var wine in wines) listBox1.Items.Add(wine.N); }
De beste portugisiske vinene
Det er to forskjeller fra oppgaven ovenfor. For det første har vi et kombinert utvalgskriterium (både Portugal og sekser på terningen). For det andre velger vi ut hele objektet, vi konstruerer ikke en string-liste. Etter søket sitter vi altså igjen med en liste (med referanser til) vinobjekter. Så setter vi disse objektene inn i listeboksen. Dette betyr at når vi klikker i lista, vil vi få tilgang til selve objektet , og kan om ønskelig hente fra andre egenskaper ved vinen enn navnet. Hvert vinobjekt blir representert ved navnet i lista fordi vi har redefinert objectmetoden ToString() for vinobjekter til å levere vinens navn.
private void buttonBest_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines // with dice 6 var wines = from w in WineList where w.country == "Portugal" && w.dice == "6" select w; foreach (var wine in wines) listBox1.Items.Add(wine); }
Koden er i sin helhet slik:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Xml; namespace list1 { public partial class Form1 : Form { const string WineSource= "http://www.ia.hiof.no/~borres/commondata/vin/allwines.xml"; XmlDocument wineDoc; List<wine> WineList; public Form1() { InitializeComponent(); } #region makelist private void Form1_Load(object sender, EventArgs e) { // fill up the winelist wineDoc = new XmlDocument(); try { wineDoc.Load(WineSource); XmlNodeList wines = wineDoc.SelectNodes("//wine"); WineList = new List<wine>(wines.Count); foreach (XmlElement wine in wines) WineList.Add(new wine( wine.GetAttribute("name"), wine.GetElementsByTagName("country")[0].InnerText, wine.GetElementsByTagName("dice")[0].InnerText, wine.GetElementsByTagName("description")[0].InnerText, wine.GetElementsByTagName("type")[0].InnerText )); labelMessage.Text = Convert.ToString(wines.Count) +" viner lagt inn"; } catch (Exception ex) { labelMessage.Text = ex.Message; } } #endregion makelist #region button1 private void buttonPortugal_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines var wines = from w in WineList where w.country == "Portugal" select new{N=w.name}; foreach (var wine in wines) listBox1.Items.Add(wine.N); } #endregion button1 #region button2 private void buttonBest_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines // with dice 6 var wines = from w in WineList where w.country == "Portugal" && w.dice == "6" select w; foreach (var wine in wines) listBox1.Items.Add(wine); } #endregion button2 #region listselect private void listBox1_SelectedIndexChanged(object sender, EventArgs e) { try { // assume complete wine in list wine w = (wine)listBox1.SelectedItem; MessageBox.Show("type: "+w.type+"\r\n"+w.description); } catch (Exception ex) { // only winename in list MessageBox.Show((String)listBox1.SelectedItem); } } } #endregion listselect #region onewine class wine : Object { String wname; String wcountry; String wdice; String wdescription; String wtype; public wine(String name,String country,String dice, String description, String type) { wname = name; wcountry = country; wdice = dice; wdescription = description; wtype = type; } public String name { get { return wname; } } public String country { get { return wcountry; } } public String dice { get { return wdice; } } public String type { get { return wtype; } } public String description { get { return wdescription; } } public override string ToString() { return wname; } } #endregion onewine }
Eksempel 2
Vi bruker de samme dataene og skal gjøre bare en enkel endring fra eksempel 1. I eksempel 1 etablerte vi objektlista ved hjelp av xpath of dom-programmering. Nå skal vi forsøke å etablere lista ved hjelp av LINQ.
Det eneste vi endrer er koden for å laste inn XML-strukturen:
private void Form1_Load(object sender, EventArgs e) { // fill up the winelist XDocument xdoc = new XDocument(); try { xdoc = XDocument.Load(WineSource); WineList = new List<wine>(); var wines = from v in xdoc.Descendants("wine") select new { name = (string)v.Attribute("name"), country = (string)v.Element("country"), dice = (string)v.Element("dice"), description = (string)v.Element("description"), type = (string)v.Element("type") }; foreach (var w in wines) WineList.Add(new wine(w.name, w.country, w.dice, w.description, w.type)); labelMessage.Text = Convert.ToString(WineList.Count) + " viner lagt inn"; } catch (Exception ex) { labelMessage.Text = ex.Message; } }
Merk at her bruker vi ikke et XmlDocument, men et XDodument. XDocument er et format som gjør XML-filer søkbare med LINQ.
Eksempel 3
Vi bruker de samme dataene. Nå skal vi gjøre en kraftig forenkling. Vi kutter ut hele objektlista og bruker XDocumentet direkte som søkestruktur.
Etableringen av strukturen blir nå slik:
private void Form1_Load(object sender, EventArgs e) { // fill up the winelist xdoc = new XDocument(); try { xdoc = XDocument.Load(WineSource); labelMessage.Text = Convert.ToString(xdoc.Descendants("wine").Count()) + " viner lagt inn"; } catch (Exception ex) { labelMessage.Text = ex.Message; } }
De to søkerutinene blir slik
Portugisiske viner
private void buttonPortugal_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines var wines = from w in xdoc.Descendants("wine") where (string)w.Element("country") == "Portugal" select new { N = (string)w.Attribute("name") }; foreach (var w in wines) listBox1.Items.Add(w.N); }
Vi ser at vi søker direkte i XDocument xdoc.
De beste portugisiske vinene
Her har vi et lite problem. Vi ønsker at når brukeren klikker i lista skal han få opp flere egenskaper ved vinen enn selve navnet. Vi løser dette med å lagre (referanse til) et wine-objekt i lista. Vi lage altså slike vinlister bare ved behov.
private void buttonBest_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines // with dice 6 var wines = from w in xdoc.Descendants("wine") where (string)w.Element("country") == "Portugal" && (string)w.Element("dice") == "6" select w; foreach (var w in wines) listBox1.Items.Add(new wine(w)); foreach (var w in wines) listBox1.Items.Add(w); }
Listeklikket blir da som tidligere:
private void listBox1_SelectedIndexChanged(object sender, EventArgs e) { try { // assume complete wine in list XElement w = ((wine)listBox1.SelectedItem).myElement; MessageBox.Show("type: " + w.Element("type").Value + "\r\n" + "description: " + w.Element("description").Value); } catch (Exception ex) { // only winename in list MessageBox.Show((String)listBox1.SelectedItem); } } }
Koden er i sin helhet slik:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Xml.Linq; namespace linq2 { public partial class Form1 : Form { const string WineSource = "c:\\web\\commondata\\vin\\allwines.xml"; XDocument xdoc; public Form1() { InitializeComponent(); } #region load private void Form1_Load(object sender, EventArgs e) { // fill up the winelist xdoc = new XDocument(); try { xdoc = XDocument.Load(WineSource); labelMessage.Text = Convert.ToString(xdoc.Descendants("wine").Count()) + " viner lagt inn"; } catch (Exception ex) { labelMessage.Text = ex.Message; } } #endregion load #region button1 private void buttonPortugal_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines var wines = from w in xdoc.Descendants("wine") where (string)w.Element("country") == "Portugal" select new { N = (string)w.Attribute("name") }; foreach (var w in wines) listBox1.Items.Add(w.N); } #endregion button1 #region button2 private void buttonBest_Click(object sender, EventArgs e) { listBox1.Items.Clear(); // Collect and show names of Portugese wines // with dice 6 var wines = from w in xdoc.Descendants("wine") where (string)w.Element("country") == "Portugal" && (string)w.Element("dice") == "6" select w; foreach (var w in wines) listBox1.Items.Add(new wine(w)); foreach (var w in wines) listBox1.Items.Add(w); } #endregion button2 #region select private void listBox1_SelectedIndexChanged(object sender, EventArgs e) { try { // assume complete wine in list XElement w = ((wine)listBox1.SelectedItem).myElement; MessageBox.Show("type: " + w.Element("type").Value + "\r\n" + "description: " + w.Element("description").Value); } catch (Exception ex) { // only winename in list MessageBox.Show((String)listBox1.SelectedItem); } } } #endregion select #region wine class wine : Object { XElement theWine; public wine(XElement w) { theWine = w; } public XElement myElement { get { return theWine; } } public override string ToString() { return (String)theWine.Attribute("name"); } } #endregion wine }