Entwickler-Ecke

IO, XML und Registry - Xml mit Linq lesen: Null Exception :(


Schafschaf - Di 31.03.15 11:50
Titel: Xml mit Linq lesen: Null Exception :(
Hallo Leute,

ich hab mal wieder ein Problem, wo ich nicht weiterkomme :(
Ich möchte ein paar Daten aus einer Xml Datei lesen und sie in ein Objekt schreiben um sie zur späteren Verwendung in ein anderes Format zu schreiben.
So sieht die Xml-Datei aus. Wobei es viele <Order> gibt und die Elemente voneinander abweichen (z.B. bei Firmenbestellungen gibt es noch <Company>, die tauchen meistens gar nicht auf)



XML-Daten
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
<?xml version='1.0' encoding='utf-8'?>
<Orders>
  <Shop>
    <GUID>datenschutz</GUID>
  </Shop>
  <Order>
    <OrderNumber>datenschutz</OrderNumber>
    <CustomerNumber>datenschutz</CustomerNumber>
    <Currency>datenschutz</Currency>
    <Language>de</Language>
    <Locale>de_DE</Locale>
    <TaxArea>EU</TaxArea>
    <TaxModel>gross</TaxModel>
    <GrandTotal>datenschutz</GrandTotal>
    <TotalBeforeTax>datenschutz</TotalBeforeTax>
    <TotalTax>datenschutz</TotalTax>
    <Addresses>
      <BillingAddress>
        <FirstName>datenschutz</FirstName>
        <LastName>datenschutz</LastName>
        <Street>datenschutz</Street>
        <Zipcode>datenschutz</Zipcode>
        <State>datenschutz</State>
        <City>datenschutz</City>
        <EMail>datenschutz</EMail>
        <Phone>datenschutz</Phone>
        <Country>DE</Country>
      </BillingAddress>
      <ShippingAddress>
        <FirstName>datenschutz</FirstName>
        <LastName>datenschutz</LastName>
        <Street>datenschutz</Street>
        <Zipcode>datenschutz</Zipcode>
        <State>datenschutz</State>
        <City>datenschutz</City>
        <Phone>datenschutz</Phone>
        <Country>DE</Country>
      </ShippingAddress>
    </Addresses>
    ... ... ... 
  </Order>
  <Order>
     ... ... ...
  </Order>


Das Lesen der Daten sollte mit einer Linq-Abfrage erfolgen.
Vorher hatte ich First(), da kam immer "Die Sequenz enthält keine Elemente". Ist ja klar, First erzwingt ja quasi dass was kommt.
Deshalb habe ich jetzt überall FirstOrDefault(). Jetzt kommt "Der Objektverweis wurde nicht auf eine Objektinstatz festgelegt".
Nicht bei der Abfrage selbst, immer nur wenn ich versuche mit dem Objekt "Orders" zu arbeiten, nämlich "Orders" zu durchlaufen und dessen Werte in die Liste ins "Shipment" Objekt einzutragen.


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
XDocument xdoc = XDocument.Load("datei.xml");
                       
            List<Shipment> shipments = new List<Shipment>();

            var Orders = from Order in xdoc.Descendants("Order")
                         select new
                         {
                             //das sind nur ein paar als Beispiel
                             OrderNumber = Order.Descendants("OrderNumber").FirstOrDefault().Value,
                             stomerNumber = Order.Descendants("CustomerNumber").FirstOrDefault().Value, 
                             BillingAddressPhone = Order.Descendants("Addresses").Descendants("BillingAddress").FirstOrDefault().Descendants("Phone").FirstOrDefault().Value,
                             BillingAddressCountry = Order.Descendants("Addresses").Descendants("BillingAddress").FirstOrDefault().Descendants("Country").FirstOrDefault().Value,
                             ShippingAddressFirstName = Order.Descendants("Addresses").FirstOrDefault().Descendants("ShippingAddress").FirstOrDefault().Descendants("FirstName").FirstOrDefault().Value,
                         }


Selbst eine Abfrage ob es Null ist, bringt nichts:


C#-Quelltext
1:
BillingAddressPhone = Order.Descendants("Addresses").Descendants("BillingAddress").FirstOrDefault().Descendants("Phone").FirstOrDefault() != null ? BillingAddressPhone = Order.Descendants("Addresses").Descendants("BillingAddress").FirstOrDefault().Descendants("Phone").FirstOrDefault().Value : "",                    


Ich denke es ist mal wieder was ganz Doofes, Einfaches..

Freue mich um jeden Rat :)

LG Schafschaf


Th69 - Di 31.03.15 15:51

Hallo,

dein Code ist ein bißchen zu komplex (besonders bzgl. der häufigen Aufrufe von Descendants).

Außerdem scheint dir die Descendants-Methode nicht ganz klar zu sein (oder?): Descendants vs. Elements in Linq to XML [http://chodounsky.net/2013/10/23/descendants-vs-elements-in-linq-to-xml/]

Und bzgl. FirstOrDefault: im Gegensatz zu First liefert diese Methode keine Exception, sondern null, wenn kein passendes Element gefunden wurde. Dies bedeutet aber nicht, daß du dann einfach auf untergeordnete Eigenschaften (wie Value) zugreifen darfst (ohne explizit auf null zu testen).

Statt der einen langen Zeile mit x Methodenaufrufen solltest du also jede Methode einzeln aufrufen und die Rückgabewerte überprüfen!

Außerdem solltest du mittels des Debuggers die Fehler schnell selber finden.


Schafschaf - Mi 01.04.15 12:08

user profile iconTh69 hat folgendes geschrieben Zum zitierten Posting springen:


Außerdem scheint dir die Descendants-Methode nicht ganz klar zu sein (oder?): Descendants vs. Elements in Linq to XML [http://chodounsky.net/2013/10/23/descendants-vs-elements-in-linq-to-xml/]

Und bzgl. FirstOrDefault: im Gegensatz zu First liefert diese Methode keine Exception, sondern null, wenn kein passendes Element gefunden wurde. Dies bedeutet aber nicht, daß du dann einfach auf untergeordnete Eigenschaften (wie Value) zugreifen darfst (ohne explizit auf null zu testen).


Danke, das hat mir geholfen, jetzt funktioniert es!
Also vom Prinzip her habe ich es verstanden, bevor ich z.B. irgendwo in ShippingAddress was hole/prüfe, schaue ich ob es ShippingAddress überhaupt gibt und mache erst dann weiter.
Ich habe es jetzt so gelöst:


C#-Quelltext
1:
 ShippingAddressFirstName = Order.Elements("Addresses").FirstOrDefault().Elements("ShippingAddress").FirstOrDefault() != null ? Order.Elements("Addresses").FirstOrDefault().Elements("ShippingAddress").FirstOrDefault().Elements("FirstName").FirstOrDefault() != null ?   Order.Elements("Addresses").FirstOrDefault().Elements("ShippingAddress").FirstOrDefault().Elements("FirstName").FirstOrDefault().Value : "" :""                    


Sieht syntaktisch zwar scheußlich aus, aber in dem select-Block kann ich ja keine if-Blöcke benutzen.


Ralf Jansen - Mi 01.04.15 12:16

man kann aber im Ausdruck mit let Dinge vordefinieren und dann auch das ganze mit dem ?? Operator kürzen. Etwa;


C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
var orders = from Order in xdoc.Descendants("Order")
             let shippingAddress = Order.Elements("Addresses").First().Elements("ShippingAddress").FirstOrDefault()
             select new 
             {
                 ...
                 ShippingAddressFirstName = shippingAddress != null ? (shippingAddress.Elements("FirstName").FirstOrDefault() ?? "") :""          
             }


Th69 - Mi 01.04.15 12:22

Hallo

mittels des LINQ Schlüsselworts let [https://msdn.microsoft.com/de-de/library/bb383976.aspx] läßt sich der Ausdruck aber sehr vereinfachen.

Edit: Code entfernt, da Ralf ja schon schneller gepostet hat ;-)


Schafschaf - Mi 01.04.15 15:55

Und wieder mal was dazu gelernt. Danke ( ͡° ͜ʖ ͡°)