Laden...

Hilfe bei XMLSerializer von 2 Klassen unter einem Object

Letzter Beitrag vor 24 Tagen 14 Posts 462 Views
Hilfe bei XMLSerializer von 2 Klassen unter einem Object

Hallo liebe Community,

ich weiß nicht ob ich hier in dem richtigen Abteilung bin. Falls nicht, dann bitte in den passenden Bereich verschieben. Danke.

So nun zu meinem Thema. Ich bin in C# betreffend noch ein Anfänger und wollte mir einen kleinen Data-Updater machen, denn ich gerne privat nutzen würde. Seit knapp einem Monat jedoch komme ich nicht weiter. Ich hoffe Ihr könnt mir da weiterhelfen.

So Sieht mein Projekt gerade aus. Ich habe zwei Formen. Von der AddForm werden die Daten von 3 Textboxen in eine ListView eingepflegt. Diese will ich dann zusammen mit dem Inhalt einer Textbox(Version) in XML-Datei speichern.

using System;
using System.Collections.Generic;
using System.Windows.Forms;
using System.IO;
using System.Globalization;
using System.Xml.Serialization;

namespace M.T.UpdateMaker
{
    public partial class UpdateMaker : Form
    {

        public UpdateMaker()
        {
            InitializeComponent();
            currentCulture = CultureInfo.CurrentCulture;
        }

        String ListViewItemsXML = "UpdateInfo.xml";

        public List<myObject> ListViewItems = new List<myObject>();

        CultureInfo currentCulture;

        private void setVisibility()
        {
            if (File.Exists(ListViewItemsXML))
            {
                ListViewItems = Utilities.XMLSaver.DeserializeFromXML(ListViewItemsXML);

                foreach (myObject mo in ListViewItems)
                {
                    ListViewItem item = new ListViewItem(mo.Name);
                    item.SubItems.Add(mo.Description);
                    listView1.Items.Add(item);
                }
            }
        }


        private void btnAdd_Click(object sender, EventArgs e)
        {
            AddForm Af = new AddForm(this);
            Af.ShowDialog();
        }

        private void btnGenerate_Click(object sender, EventArgs e)
        {
            if (!File.Exists(ListViewItemsXML))
            {
                Utilities.XMLSaver.SerializeToXML(ListViewItems, ListViewItemsXML);
            }
        }

        private void btnLoadXML_Click(object sender, EventArgs e)
        {
            setVisibility();
        }
        private void btnClear_Click(object sender, EventArgs e)
        {
            listView1.Items.Clear();
        }
    }


    // Serialisierungsversuch
    [Serializable]
    [XmlType("Data-Source")]
    public class myObject
    {
        public string Name { get; set; }
        public string Description { get; set; }
        public string Status { get; set; }

        public string BuildVersion { get; set; }
    }
}
using System;
using System.Windows.Forms;

namespace M.T.UpdateMaker
{
    public partial class AddForm : Form
    {
        private readonly UpdateMaker um;
        public AddForm(UpdateMaker updateMaker)
        {
            InitializeComponent();
            um = updateMaker;
        }

        private void btnOk_Click(object sender, EventArgs e)
        {
            string[] row1 = {txtDesc.Text, txtStatus.Text};
            um.listView1.Items.Add(txtName.Text).SubItems.AddRange(row1);
            myObject myObject = new myObject();
            myObject.Name = txtName.Text;
            myObject.Description = txtDesc.Text;
            myObject.Status = txtStatus.Text;
            um.ListViewItems.Add(myObject);
            this.Close();
        }
    }
}
using System.Collections.Generic;
using System.IO;
using System.Xml.Serialization;

namespace M.T.UpdateMaker.Utilities
{
    public static class XMLSaver
    {

        //Instead of creating xml documents from scratch you can serailize them into xml and save them all in one step

        public static void SerializeToXML(List<myObject> ListViewItems, string path)
        {
            XmlSerializer serializer = new XmlSerializer(typeof(List<myObject>));
            TextWriter textWriter = new StreamWriter(path);
            serializer.Serialize(textWriter, ListViewItems);
            textWriter.Close();
        }

        public static List<myObject> DeserializeFromXML(string path)
        {
            XmlSerializer deserializer = new XmlSerializer(typeof(List<myObject>));
            TextReader textReader = new StreamReader(path);
            List<myObject> ListviewItems;
            ListviewItems = (List<myObject>)deserializer.Deserialize(textReader);
            textReader.Close();

            return ListviewItems;
        }
    }
}

Das wird als XML-Datei erzeugt:

<ArrayOfDataSource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <Data-Source>
  <Name>fsdfa</Name>
  <Description>xyvyxv</Description>
  <Status>Ready ...</Status>
 </Data-Source>
 <Data-Source>
  <BuildVersion>1.0.0.1</BuildVersion>
 </Data-Source>
</ArrayOfDataSource>

Ich würde aber gerne dass die BuildVersion eine eigene Bezeichnung hat und nicht die Data-Source.
Ich serializiere eine Listview als Data-Source und die BuildVersion von einer TextBox.

Eigentlich würde ich das gerne in diesem Format haben:

<ArrayOfDataSource xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
 <BuildVersion>
  <Version>1.0.0.1</Version>
 </BuildVersion>
 <DataList>
  <Name>Eintrag1</Name>
  <Description>Mein erster Text</Description>
  <Status>Ready ...</Status>
 </DataList>
 <DataList>
  <Name>Eintrag2</Name>
  <Description>Mein zweiter Text</Description>
  <Status>Ready ...</Status>
 </DataList>
</ArrayOfDataSource>

Für alle, die sich die Zeit nehmen das hier zu lesen bzw. zu beantworten, vielen lieben Dank schon mal im Voraus

Gruß

ThaKilla

Du solltest Dir für die Serialisierung von Daten immer eine eigene Klasse (Complex Object) bauen, die Deine Serialisierung (also XML, Json, etc..) repräsentiert. Aktuell schmeisst Du nur eine Liste rein und sagst dem Serializer "mach einfach mal". Daher kommt als Root auch sowas raus wie ArrayOfDataSource 
So ein Vorgehen hat den enormen Nachteil, dass Deine Stuktur völlig anders sein kann, wenn Du den Serializer austauschst.

.NET Doc: Examples of XML Serialization

Ich würde aber gerne dass die BuildVersion eine eigene Bezeichnung hat und nicht die Data-Source.

Deine XML ist genau das, was Du da als Objekt myObject etc darstellst.
Soll Deine XML anders aussehen, dann brauchst Du dafür extra Objekte oder Du serialisierst die XML manuell (quasi Nodes von Hand definieren).
Es ist ohnehin empfohlen, dass Deine XML Struktur als eigene Objekte definiert werden, sodass sich die Struktur der Dateien nicht ändert, wenn sich Deine UI/Logik-Objekte ändern. Sowas nennt man im Allgemeinen Data Transfer Objects.

Ja, das bedeutet, dass Du manches doppelt hast und von Hand das Mapping machen musst; aber im Endeffekt ist genau das Versionierung.

Willst Du eine Multi-Versionierung Deiner XML Dateien, dann kannst Du ohnehin in den meisten Fällen nicht über diese Wege serialisieren.
Dann musst Du erstmal die Version-Node manuell laden und dann den richtigen Serializer/Parser zur richtigen Version verwenden - was dann in der Folge auch heisst, dass Du mehrere XML-Objekte brauchst um die verschiedenen Versionen darstellen zu können.

Hallo "Abt",

kannst du mir das Anhand eines Praxisbeispieles das zeigen? Das würde bei meiner Sache schneller helfen, mit Kindern zu Hause würde ich wieder monatelang dran sitzen. So könnte ich das evtl. besser verstehen und einige kleine Projekte machen und testen ob ich das gelernte auch in die Tat umsetzen kann.

Wäre dir und weiteren Helfern sehr verbunden.

Gruß

ThaKilla

Beispiel ist im Link, den ich Dir gegeben hab. Hab auch nen Privatleben und nich wirklich Zeit für jeden seinen Code zu schreiben 😉

Hallo nochmals zusammen,

ich habe in den letzten Tagen viel im Internet nach einer Lösung gesucht um mein Problem zu lösen.
Was ich meistens gelesen habe ist, da die meisten Lösungen auf eine DataGridView verweisen.
Leider verwenden die meisten nur eine DGV und serialisieren diese zu einer XML Datei.
Da ich aber mehrere und unterschiedliche Daten speichern will gehen diese Vorschläge nicht.
Ich habe ein bisschen an meinen Code gefeilt, aber habe ein Problem, dass ich wahrscheinlich nicht auf die schnelle Weise lösen kann.

Und zwar da erhalte ich ein ERROR : "table"-Argument darf nicht null sein. Parametername: table

private void btnXML_Click(object sender, EventArgs e)
{
    DataSet dataSet1 = new DataSet();
    dataSet1.AcceptChanges();

    DataSet dataSet2 = new DataSet();
    dataSet2.AcceptChanges();

    DataTable data = (DataTable)(ConfigData.DataSource);
    DataTable data2 = (DataTable)(ConfigVersion.DataSource);

    dataSet1.Tables.Add(data);
    dataSet2.Tables.Add(data2);
    try
    {
        dataSet1.Merge(dataSet2);
        dataSet1.WriteXml(savefile);
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

Der ERROR wird in der Zeile angezeigt

DataTable data = (DataTable)(ConfigData.DataSource);

Mit freundlichen Grüßen

ThaKilla

Hallo,

erfolgt denn irgendwo eine Zuweisung an ConfigData.DataSource .

glandorf

Hallo, wie könnte ich das am besten machen?

Über eine DataSource Datei mit zwei Tabellen oder in der Code-Ansicht?

Ich habe es über Einbindung einer Klasse versucht, wo ich meine Daten klassifiziert habe. Leider ohne Erfolg 😔

Leider habe ich das im ersten Post nicht erwähnt, aber ich bin ein kompletter Neuling auf diesem Gebiet und versuche mich gleich an sowas kompliziertem.

Das sollte eigentlich ein kleiner Updater + Update-Maker sein mit ich ich eine Fotogalerie für Familienfotos verwalte.

Leider komme ich nur langsam voran, aber jede kleine Unterstützung bringt mich einen kleinen Schritt voran.

Danke euch allen

Mit freundlichen Grüßen

ThaKilla

Hallo ThaKilla

Du kannst ein DTO (data transfer object) verwenden um deine Daten zu serialisieren. Das ist einfach eine Klasse mit den entsprechenden Properties. Das DTO füllst du mit den Daten aus dem DataGridView/DataSet und serialisierst es dann. Wenn du mehrere Dateien haben willst, serialisierst du einfach mehrere DTOs. Ist wirklich einfach.

Gruss
Alf

Hallo ThaKilla,

mit dem Code aus deinem Eingangsbeitrag bist du schon auf dem richtigen Weg (besser als die DataSet/ DataTable-Serialisierung), nur daß du eben dein Datenmodell (so wie Abt schon geschrieben hat) anpassen mußt:

[XmlType("Data-Source")]
public class DataSource
{
    public BuildVersion BuildVersion { get; set; } = new();
    
    public List<DataItem> DataList { get; set; } = new();
}

public class BuildVersion
{
    public string Version { get; set; }
}

public class DataItem
{
    public string Name { get; set; }
    public string Description { get; set; }
    public string Status { get; set; }
}

Anstatt einer List<...> erzeugst du dann ein einzelnes DataSource-Objekt (welches ein BuildVersion-Objekt und eine Liste der Daten enthält) und serialisierst dieses dann.


Außerdem solltest du das Erzeugen des DataItem-Objekts (welches bei dir noch myObjectheißt) nicht in dem Code des AddForm-Dialogs durchführen, sondern nach dem Aufruf in der Hauptform, s. mein Artikel Kommunikation von 2 Forms  unter "1. Hauptform ruft Unterform mittels ShowDialog() auf".

Dazu dann einfach (nur lesende) Eigenschaften in AddForm hinzufügen, z.B.

public string ItemName => txtName.Text;

Dann benötigst du auch keine Referenz mehr auf die Hauptform UpdateMaker.

Hallo Th69,

aber wenn ich die Referenz entferne, dann kann ich nicht mehr auf die Listview in Form1 zu greifgen.
Dann erhalte ich die Fehlermeldung "listview1 ist im Kontext nicht vorhanden."

PS:
Kann man evtl. mein kleines Projekt auch nur auf eine Form begrenzen, dann müsste ich nicht so viel googln.
Ich möchte das Projekt, wenn es machbar ist, zeitnah erledigen und so schlicht wie einfach halten.

Ich habe auch deinen Beitrag mit den zwei Formen angeschaut, werde daraus leider nicht schlauer, wie ich die Daten dann von den
Textboxen in die Listview einpflegen kann und diese Liste dann zusammen mit der Textbox für die Version danach serialisiere.

Trotzdem Danke für die tollen Vorschläge, habe die letzten Tage auf Nacht mal ein stündchenweise mir das im INet rausgesucht und gelesen.
Leider habe ich keine so richtig passende Infos finden können, die auch einen Praxisteil enthalten.

Mit freundlichen Grüßen

ThaKilla

Du brauchst ja dann auch nicht mehr von dort aus darauf zugreifen, da du den gesamten Code aus btnOk_Click (bis auf Close()) in die andere Form-Klasse verschiebst:

private void btnAdd_Click(object sender, EventArgs e)
{
   AddForm addForm = new AddForm(this);
   if (addForm.ShowDialog() == DialogResult.OK)
   {
       DataItem dataItem = new()
       {
           Name = addForm.ItemName,
           Description = addForm.Description,
           Status = addForm.Status
       };
       AddDataItem(dataItem);
   }
}

private void AddDataItem(DataItem dataItem)
{
     string[] subItems = { dataItem.Description, dataItem.Status };
     listView1.Items.Add(dataItem.ItemName).SubItems.AddRange(subItems);
     DataSource.DataList.Add(dataItem);
}

private DataSource DataSource { get; private set; } = new();

Und dann nur noch die passenden Eigenschaften ItemName,Description und Status in der AddForm-Klasse (wie in meinem letzten Beitrag gezeigt) anlegen (Name habe ich in ItemName umbenannt, da es diese Eigenschaft ja schon bei Form/ Control gibt).

Hallo Th69,

nach mehreren Anläufen habe ich deine Tipps endlich auch in die Tat umsetzen können.
Leider habe ich nun das Problem, dass ich die BuildVersion nicht in die Serialisierung bekomme.

Habe schon einiges versucht, auch eine Objekt zu erstellen.

 private void btnGenerate_Click(object sender, EventArgs e)
 {
     if (!File.Exists(ListViewItemsXML))
     {
         XmlSerializer serializer = new XmlSerializer(typeof(DataSource));
         TextWriter textWriter = new StreamWriter(ListViewItemsXML);
         serializer.Serialize(textWriter, DataSource);
         textWriter.Close();
     }
 }
 private void AddBuildVersion(string buildVersion)
{
    DataSource.BuildVersion.Version = txtVersion.Text;
}
 

Daraus erhalte ich dann folgende XML file

This XML file does not appear to have any style information associated with it. The document tree is shown below.
<Data-Source xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<BuildVersion/>
<DataList>
<DataItem>
<ItemName>1</ItemName>
<Description>1</Description>
<Status>Ready ...</Status>
</DataItem>
<DataItem>
<ItemName>2</ItemName>
<Description>2</Description>
<Status>Ready ...</Status>
</DataItem>
</DataList>
</Data-Source>

PS:

Und vielen lieben Dank für die ganzen Tipps. Hat mich zwar einige Tage an Forschung gekostet, aber wenn man gleich aufgibt, schafft man nie was 😉

Musste feststellen, dass ich die Kurzschreibweise new() nicht nutzen kann, da ich in die Build-Eigenschaften gesehen habe, dass ich C# V4 nutze.

Mit freundlichen Grüßen

ThaKilla

N'Abend,

wird denn AddBuildVersion auch vor dem Serialisieren aufgerufen? Ansonsten kannst du mal mit dem Debugger überprüfen, was in DataSource.BuildVersion drin steht, s. [Artikel] Debugger: Wie verwende ich den von Visual Studio?

PS:

C# 4 ist aber schon ziemlich alt (von 2010 für .NET Framework 4.0), s.a. C#: Versionen.

Kannst du nicht auf ein neueres Framework (+ C#-Version) wechseln? Oder benutzt du noch VS 2010?

Hallo Th69,

ich verwende VS 2022. Ich habe einfach ein WinForms-Projekt angelegt und da wurde .Net-Framework 4.8.1 angegeben
und das habe ich so gelassen. Man kann doch das Projekt nachträglich auf eine neuere Version von C# umschreiben bzw.
neues Projekt anlegen und denn Code dorthin kopieren.

PS:
Der Code geht jetzt, danke für den Tipp. Den habe ich gleich in die Tat umgesetzt.

private void AddBuildVersion(string build)
{
    BuildVersion version = new BuildVersion()
    {
        Version = build,
    };
    DataSource.BuildVersion.Version = build;
}

Und vor dem Serialisieren rufe ich die Funktion auf und setze einen Wert.
AddBuildVersion(txtVersion.Text);

<Data-Source xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<BuildVersion>
<Version>1.0.0.1</Version>
</BuildVersion>
<DataList>
<DataItem>
<ItemName>1</ItemName>
<Description>1</Description>
<Status>Ready ...</Status>
</DataItem>
<DataItem>
<ItemName>2</ItemName>
<Description>2</Description>
<Status>Ready ...</Status>
</DataItem>
</DataList>
</Data-Source>

Danke für die vielen nützlichen Tipps und kleine Schubser in die richtige Richtung.
Großen Applaus an alle und an die Betreiber.

Mit freundlichen grüßen

ThaKilla