none
Linq: Bestimmte XElements in DataGridView zur Bearbeitung anzeigen RRS feed

  • Frage

  • Hallo,

    ich habe eine XML Datei, erstelle eine XDocument und mache darauf eine Linq Abfrage. Aus den Elementen, die ich aus der Abfrage bekomme, möchte ich bestimmte ChildElemente auf dem DataGridView zum Bearbeiten anzeigen. Die geänderten Daten sollen danach in der XML Datei wieder abgespeichert werden. Mein Vorgehen ist deshalb, dass ich in der Linq Query keinen neuen Typen erstelle mit SELECT NEW ....{ } sondern das gesamte item zurückgebe mit SELECT item.Elements()

    Von diesem item im Beispiel Element2, das ich komplett bekomme, möchte ich  nun ChildElement2_1 und ChildElement2_3 im DataGridView anzeigen. Kann man dem DataGridView vorgeben, dass es alle Elements("ChildElement2_1") und Elements("ChildElements2_3") anzeigen soll? Bei SQL macht man dies ja mit DataMember.

     

    Bsp:

    Element1

    Element2

       ChildElement2_1

       ChildElement2_2

       ChildElement2_3

     

    Gruß Jens

     

    Dienstag, 25. Mai 2010 09:37

Antworten

  • Hallo Jens,

    Wenn Du alle Daten der XML-Datenquelle bearbeiten möchtest, lohnt sich der Weg über XDocument nicht. Da würde ich DataSet.ReadXml(), bzw. DataSet.WriteXml() verwenden. Der Umweg über XDocument zahlt sich nur dann aus, wenn Du die Daten filtern oder umwandeln möchtest. Hier ein mögliches Szenario, in dem die Daten zunächst aus einem String eingelesen, und nach erfolgter Bearbeitung im DataGridView dann in eine Datei gespeichert werden:

    using System;
    using System.Data;
    using System.Linq;
    using System.Windows.Forms;
    using System.Xml.Linq;
    using System.IO;
    
    namespace BestOfTwoWorlds
    {
     public partial class Form1 : Form
     {
      XDocument m_Doc;
      DataSet m_DataSet = new DataSet();
      DataTable m_Table = new DataTable();
    
      public Form1()
      {
       InitializeComponent();
      }
    
      private void Form1_Load(object sender, EventArgs e)
      {
       string input = @"<?xml version='1.0' encoding='utf-8'?><Bibliothek><Eintrag><Vorname>Johann Wolfgang</Vorname><Nachname>von Goethe</Nachname><Titel>Wilhelm Meisters Lehrjahre</Titel><ISBN>ISBN 3-89350-448-6</ISBN></Eintrag><Eintrag><Vorname>Johann Wolfgang</Vorname><Nachname>von Goethe</Nachname><Titel>Faust</Titel><ISBN>ISBN 2-267-00735-0</ISBN></Eintrag></Bibliothek>";
    
       // Variante1: Alles in die Tabelle einlesen
       // m_DataSet.ReadXml(new StringReader(input));
    
       // Variante2: Über LINQ eingelesene Daten filtern
       m_Doc = XDocument.Load(new StringReader(input));
    
       m_DataSet.Tables.Add(m_Table);
    
       foreach (XElement x in m_Doc.Descendants("Eintrag").First().Descendants()) 
        m_Table.Columns.Add(new DataColumn(x.Name.ToString())); 
    
       var rows = from x in m_Doc.Descendants("Eintrag")
          where x.Element("ISBN").Value.StartsWith("ISBN 2")
          select new object[] {
    			 x.Element("Vorname").Value, 
    			 x.Element("Nachname").Value, 
    			 x.Element("Titel").Value,
    			 x.Element("ISBN").Value };
    
       foreach (var rowData in rows)
       {
        DataRow newRow = m_Table.NewRow();
        newRow.ItemArray = rowData;
        m_Table.Rows.Add(newRow);
       }
    
       dataGridView1.DataSource = m_Table;
       dataGridView1.Columns["ISBN"].Visible = false;
    
      }
    
      private void buttonSave_Click(object sender, EventArgs e)
      {
       m_DataSet.WriteXml("output.xml");
      }
     }
    }
    

     

    Gruß
    Marcel

    Donnerstag, 27. Mai 2010 15:00
  • Hallo Marcel,

    eine kleine Ergänzung zu Deinem empfehlenswerten Code.
    Für die Konvertierung besser einen Cast verwenden:

     select new object[] {
    	 (string)x.Element("Vorname"), 
    	 (string)x.Element("Nachname"), 
    	 (string)x.Element("Titel"),
    	 (string)x.Element("ISBN") };
    

    sonst gibt es bei null Ärger, wegen dem Value.
    Für ein Cast wiederum hat XElement für die Standardtypen einen expliziten Operator.

    Und weil es mir Justamente bei realem Input gerade heute ein, zwei graue Haare beschert hat (nicht das es noch auffiele ;-)

    Denn wenn das Element fehlt (und ein DataSet.WriteXml lässt leere Elemente aus),
    hagelt es eine NullReferenceException und hinzukommt zudem
    Improving LINQ Code Smell with Explicit and Implicit Conversion Operators

    für die Fälle wo man kein Nullable zuweisen will/kann.

    Wobei meine Lösungvariante (stellvertretend für Int):

        public static int AsInt32(this XContainer value, XName name)
        {
          if (value != null)
          {
            XElement element = value.Element(name);
            if (element != null)
              return (int)element;
          }
          return 0;
        }
    

    (mag noch schöner gehen, aber für 3 fehlende Elemente pro Megabyte reichte es).
    Ein x.AsInt32("Name") ist kürzer (vor allem wenn mal 50) und IMHO so besser zu lesen.

    Gruß Elmar

    Donnerstag, 27. Mai 2010 15:44
    Beantworter

Alle Antworten

  • Hallo Jens,

    um eine Bearbeitung (und nicht nur Anzeige) hinzubekommen, sind XDocument und Co. nicht geeignet.
    Die Datenbindung funktioniert volllständig nur, wenn eine Klasse, die IBindingList
    und auf Eigenschaftsebene  IEditableObject implementiert, verwendet wird.
    XDocument ist aber dafür nicht ausgelegt, vielmehr auf Ressourcen sparen

    Sinnvoller wäre, die Daten für die Bearbeitung in eine DataTable zu überführen,
    und am Ende wieder in ein XDocument.  Ein auf die Schnelle gefundener Codeschnippsel:
    http://stackoverflow.com/questions/2633548/xml-string-into-a-datagridview-c

    Gruß Elmar

    Dienstag, 25. Mai 2010 16:39
    Beantworter
  • Hallo Jens,

    Wenn Du alle Daten der XML-Datenquelle bearbeiten möchtest, lohnt sich der Weg über XDocument nicht. Da würde ich DataSet.ReadXml(), bzw. DataSet.WriteXml() verwenden. Der Umweg über XDocument zahlt sich nur dann aus, wenn Du die Daten filtern oder umwandeln möchtest. Hier ein mögliches Szenario, in dem die Daten zunächst aus einem String eingelesen, und nach erfolgter Bearbeitung im DataGridView dann in eine Datei gespeichert werden:

    using System;
    using System.Data;
    using System.Linq;
    using System.Windows.Forms;
    using System.Xml.Linq;
    using System.IO;
    
    namespace BestOfTwoWorlds
    {
     public partial class Form1 : Form
     {
      XDocument m_Doc;
      DataSet m_DataSet = new DataSet();
      DataTable m_Table = new DataTable();
    
      public Form1()
      {
       InitializeComponent();
      }
    
      private void Form1_Load(object sender, EventArgs e)
      {
       string input = @"<?xml version='1.0' encoding='utf-8'?><Bibliothek><Eintrag><Vorname>Johann Wolfgang</Vorname><Nachname>von Goethe</Nachname><Titel>Wilhelm Meisters Lehrjahre</Titel><ISBN>ISBN 3-89350-448-6</ISBN></Eintrag><Eintrag><Vorname>Johann Wolfgang</Vorname><Nachname>von Goethe</Nachname><Titel>Faust</Titel><ISBN>ISBN 2-267-00735-0</ISBN></Eintrag></Bibliothek>";
    
       // Variante1: Alles in die Tabelle einlesen
       // m_DataSet.ReadXml(new StringReader(input));
    
       // Variante2: Über LINQ eingelesene Daten filtern
       m_Doc = XDocument.Load(new StringReader(input));
    
       m_DataSet.Tables.Add(m_Table);
    
       foreach (XElement x in m_Doc.Descendants("Eintrag").First().Descendants()) 
        m_Table.Columns.Add(new DataColumn(x.Name.ToString())); 
    
       var rows = from x in m_Doc.Descendants("Eintrag")
          where x.Element("ISBN").Value.StartsWith("ISBN 2")
          select new object[] {
    			 x.Element("Vorname").Value, 
    			 x.Element("Nachname").Value, 
    			 x.Element("Titel").Value,
    			 x.Element("ISBN").Value };
    
       foreach (var rowData in rows)
       {
        DataRow newRow = m_Table.NewRow();
        newRow.ItemArray = rowData;
        m_Table.Rows.Add(newRow);
       }
    
       dataGridView1.DataSource = m_Table;
       dataGridView1.Columns["ISBN"].Visible = false;
    
      }
    
      private void buttonSave_Click(object sender, EventArgs e)
      {
       m_DataSet.WriteXml("output.xml");
      }
     }
    }
    

     

    Gruß
    Marcel

    Donnerstag, 27. Mai 2010 15:00
  • Hallo Marcel,

    eine kleine Ergänzung zu Deinem empfehlenswerten Code.
    Für die Konvertierung besser einen Cast verwenden:

     select new object[] {
    	 (string)x.Element("Vorname"), 
    	 (string)x.Element("Nachname"), 
    	 (string)x.Element("Titel"),
    	 (string)x.Element("ISBN") };
    

    sonst gibt es bei null Ärger, wegen dem Value.
    Für ein Cast wiederum hat XElement für die Standardtypen einen expliziten Operator.

    Und weil es mir Justamente bei realem Input gerade heute ein, zwei graue Haare beschert hat (nicht das es noch auffiele ;-)

    Denn wenn das Element fehlt (und ein DataSet.WriteXml lässt leere Elemente aus),
    hagelt es eine NullReferenceException und hinzukommt zudem
    Improving LINQ Code Smell with Explicit and Implicit Conversion Operators

    für die Fälle wo man kein Nullable zuweisen will/kann.

    Wobei meine Lösungvariante (stellvertretend für Int):

        public static int AsInt32(this XContainer value, XName name)
        {
          if (value != null)
          {
            XElement element = value.Element(name);
            if (element != null)
              return (int)element;
          }
          return 0;
        }
    

    (mag noch schöner gehen, aber für 3 fehlende Elemente pro Megabyte reichte es).
    Ein x.AsInt32("Name") ist kürzer (vor allem wenn mal 50) und IMHO so besser zu lesen.

    Gruß Elmar

    Donnerstag, 27. Mai 2010 15:44
    Beantworter
  • Hallo Elmar,

    Bin ganz Deiner Meinung. Vorsicht ist die Mutter der Porzellankiste. Und die Erweiterungsmethode sieht zudem noch elegant und wiederverwendbar aus.

    Gruß
    Marcel

    Donnerstag, 27. Mai 2010 16:49