I am trying to fill a list of clients from an xml file using linq, but i always get this null exception: Object reference not set to an instance of an object.. Here is my code, starting with the Client class:
public class Client
{
// Personne Physique
public string IdClient { get; set; }
public string NomClient { get; set; }
public string PrenomClient { get; set; }
public Client(){}
}
The code to fill the list:
var doc = XDocument.Load(pathXml);
List<Client> lc = doc.Descendants("Anzeige")
.FirstOrDefault()
.Descendants("Kunde")
.Select(p => new Client()
{
IdClient = p.Element("KundNr").Value,
NomClient = p.Element("Nachname").Value,
PrenomClient = p.Element("Vorname").Value
}).ToList<Client>();
The xml file looks like this:
<Anzeige>
<Kunde>
<KundNr>111</KundNr>
<Nachname>111</Nachname>
<Vorname>111</Vorname>
</Kunde>
</Anzeige>
Help please! I am pressed by time.
That code will be fine for the sample Xml you have posted.
However you are not handling some scenarios where your code will break. For example:
An Xml document with no <Anzeige> node will cause a null exception
in doc.Descendants("Anzeige").FirstOrDefault().Descendants("Kunde")
as the result from FirstOrDefault() will be null.
An Xml document where one of the <Kunde> nodes doesn't have one of
the value nodes will also cause an exception. For example if there is
no <Vorname> value then this piece of code will throw an exception
p.Element("Vorname").Value
You can tweak a bit your code to handle these scenarios.
Edit: You can use Elements instead of Descendants to force an xml where the Anzeige nodes come directly after the root and Kunde are direct childs of Anzeige. I have also edited my answer to take advantage of the cast operators that you can use directly on an XElement. This way (int?) p.Element("KundNr") returns either an int or null value if the node doesn't exist. Combined with ?? operator its a clean way to read the value. The cast will work with string values and basic value types like int or decimal. (Just for the purpose of demonstrating this I changed IdClient to an int)
You can still have an error if you try to convert to int? and the node value is something that cannot be converted on an int like "ABC". However you had all fields as strings so that shouldn't be a problem for you:
var clients = doc.Descendants("Anzeige").Descendants("Kunde").Select(p => new Client()
{
IdClient = (int?) p.Element("KundNr") ?? -1,
NomClient = (string) p.Element("Nachname") ?? String.Empty,
PrenomClient = (string) p.Element("Vorname") ?? String.Empty
}).ToList();
I have put together a small console application testing a few sample xmls:
static void Main(string[] args)
{
var doc = XDocument.Parse(
#"<Anzeige>
<Kunde>
<KundNr>111</KundNr>
<Nachname>111</Nachname>
<Vorname>111</Vorname>
</Kunde>
<Kunde>
<KundNr>222</KundNr>
<Nachname>222</Nachname>
<Vorname>222</Vorname>
</Kunde>
</Anzeige>");
ExtractClients(doc);
var docWithMissingValues = XDocument.Parse(
#"<Anzeige>
<Kunde>
<KundNr>111</KundNr>
<Vorname>111</Vorname>
</Kunde>
<Kunde>
<KundNr>222</KundNr>
<Nachname>222</Nachname>
</Kunde>
<Kunde>
</Kunde>
</Anzeige>");
ExtractClients(docWithMissingValues);
var docWithoutAnzeigeNode = XDocument.Parse(
#"<AnotherNode>
<Kunde>
<KundNr>111</KundNr>
<Vorname>111</Vorname>
</Kunde>
</AnotherNode>");
ExtractClients(docWithoutAnzeigeNode);
var docWithoutKundeNodes = XDocument.Parse(
#"<Anzeige>
<OtherNode></OtherNode>
</Anzeige>");
ExtractClients(docWithoutKundeNodes);
var emptyDoc = new XDocument();
ExtractClients(emptyDoc);
Console.ReadLine();
}
private static void ExtractClients(XDocument doc)
{
var clients = doc.Descendants("Anzeige").Descendants("Kunde").Select(p => new Client()
{
//You can manually get the value like this:
//IdClient = p.Element("KundNr") != null ? p.Element("KundNr").Value : String.Empty,
//NomClient = p.Element("Nachname") != null ? p.Element("Nachname").Value : String.Empty,
//PrenomClient = p.Element("Vorname") != null ? p.Element("Vorname").Value : String.Empty
//Or directly cast the node value to the type (value types or strings) required like:
IdClient = (int?) p.Element("KundNr") ?? -1,
NomClient = (string) p.Element("Nachname") ?? String.Empty,
PrenomClient = (string) p.Element("Vorname") ?? String.Empty
}).ToList();
Console.WriteLine();
foreach (var client in clients)
{
Console.WriteLine("{0},{1},{2}", client.IdClient, client.NomClient, client.PrenomClient);
}
Console.WriteLine(new string('-',30));
}
public class Client
{
// Personne Physique
public int IdClient { get; set; }
public string NomClient { get; set; }
public string PrenomClient { get; set; }
public Client() { }
}
Hope this helps!
Related
I have the following document in MongoDB and I want to check if the field FileName has a specific value.
Following are my classes:
public class Invoice
{
private InvoiceMetaData _metadata = null;
private List<InvoiceColumns> _invoiceFields = null;
public InvoiceMetaData Metadata
{
get
{
if (_metadata == null) _metadata = new InvoiceMetaData();
return _metadata;
}
set { _metadata = value; }
}
public List<InvoiceColumns> InvoiceFields
{
get
{
if (_invoiceFields == null)
_invoiceFields = new List<InvoiceColumns>();
return _invoiceFields;
}
set { _invoiceFields = value; }
}
}
public class InvoiceMetaData
{
public string FileName { get; set; }
public string FileProcessedOn { get; set; }
public string DirectoryPath { get; set; }
}
I've tried using the following but it's returning false even though documents with this filename exist.
string filename = "01.png";
var collection = myDB.GetCollection<Invoice>(collection_name);
var exists = collection.AsQueryable().Any(avm => avm.Metadata.FileName == filename);
I've also tried this but it's returning nothing i.e. List count is 0.
var query = Query<Invoice>.EQ(u => u.Metadata.FileName, filename).ToBsonDocument();
var exist = collection.Find(query).ToList();
Also tried this and list Count is 0,
var filter1 = Builders<Invoice>.Filter.Eq(u => u.Metadata.FileName, filename, filename);
var result = collection.Find(filter1).ToList();
Can anyone please tell me what am I doing wrong?
Any help will be much appreciated.
I haven`t worked with this for a while. But this can be helpful for you:
how to check if a field exists in a specific document Mongodb using C#?
As i remeber, you can do something like that:
var collection = myDB.GetCollection<BsonDocument>(collection_name); // you use Invoice, try BsonDocument
var documents = collection.Find(new BsonDocument()).ToList(); // this one should get you all documents
var fieldName = "Metadata" // name of field you need data from
foreach (var document in documents)
{
// this one should contain your metadata object from document, note it is BsonDocument
var metaDataObject= document.Contains(fieldName) ? document[fieldName].ToBsonDocument() : null;
var fileName= metaDataObject!= null ? metaDataObject["FileName"] : "No file name."; // should be your file name
// you also should be able to convert to dictionary, under this namespace MongoDB.Bson.BsonDocument
// var metadataDic= metaDataObject.ToDictionary();
// var fileName= metadataDic["FileName"]; // should be your file name
}
Filter examples:
var builder = Builders<BsonDocument>.Filter; // in your example invoice, try BsonDocument
var filter = builder.Eq("FileName", fileName);
var count = collection.Count(filter);
<?xml version="1.0" encoding="utf-8" ?>
<root>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>configurationtestbucket</UploadBucket>
<FileType>
<type>*.txt</type>
<type>*.OpticomCfg</type>
</FileType>
</fileUploadSpecification>
<fileUploadSpecification>
<DirectoryPath>C:\watchFolder</DirectoryPath>
<Region>us-west-2</Region>
<UploadBucket>loguploadbucket</UploadBucket>
<FileType>
<type>*.Xml</type>
<type>*.Json</type>
</FileType>
</fileUploadSpecification>
</root>
This is the XML file I need to parse, I want to get each instance of fileUploadSpecification so that I can put each set of details into a list, I think some type of for loop would be appropriate, where I loop through and add the first set of upload details and then loop through and add the second. This is what I currently have, but it never gets to the second fileUploadSpecification element, it just returns the same one again.
The idea would be to create a new SettingsData for every set of fileUploadSpecification elements, whether it be two like shown above, or 10.
public interface ISettingsEngine
{
IEnumerable<SettingsData> GetSettings();
}
public class SettingsEngine : ISettingsEngine
{
public IEnumerable<SettingsData> GetSettings()
{
List<SettingsData> dataList = new List<SettingsData>();
try
{
var xDoc = XDocument.Load("File1.xml");
var instancesToParse = xDoc.Root.Elements().Count();
var fileCount = xDoc.Root.Elements("FileType").Count();
for (int x = 0; x < instancesToParse; x++)
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = xDoc.Root.Element("fileUploadSpecification").Element("UploadBucket").Value;
newSettingsData.Region = xDoc.Root.Element("fileUploadSpecification").Element("Region").Value;
newSettingsData.DirectoryPath = xDoc.Root.Element("fileUploadSpecification").Element("DirectoryPath").Value;
var query = xDoc.Root.Descendants("FileType").Elements("type");
foreach (XElement e in query)
{
newSettingsData.FileType.Add(e.Value);
}
dataList.Add(newSettingsData);
}
return dataList;
}
catch(Exception)
{
return dataList;
}
}
}
public class SettingsData
{
public List<string> FileType { get; set; }
public string DirectoryPath { get; set; }
public string Region { get; set; }
public string UploadBucket { get; set; }
public SettingsData()
{
FileType = new List<string>();
}
}
var dataList = (from fus in xDoc.Root.Elements("fileUploadSpecification")
select new SettingsData
{
UploadBucket = fus.Element("UploadBucket").Value,
Region = fus.Element("Region").Value,
DirectoryPath = fus.Element("DirectoryPath").Value,
FileType = fus.Element("FileType")
.Elements("type").Select(f =>f.Value).ToList()
}).ToList();
Each time through the loop, you're looking up the first fileUploadSpecification element all over again. You used the Elements() method already, in a few places. That's the one you want. Always favor foreach over for in C#, when you're looping over a collection. It's quicker (to code, not at runtime) and less error prone.
foreach (var uploadSpec in xDoc.Root.Elements("fileUploadSpecification"))
{
var newSettingsData = new SettingsData();
newSettingsData.UploadBucket = uploadSpec.Element("UploadBucket").Value;
newSettingsData.Region = uploadSpec.Element("Region").Value;
newSettingsData.DirectoryPath = uploadSpec.Element("DirectoryPath").Value;
var types = uploadSpec.Descendants("FileType").Elements("type").Select(e => e.Value);
foreach (var type in types)
{
newSettingsData.FileType.Add(type);
}
// Or if newSettingsData.FileType is List<String>...
//newSettingsData.FileType.AddRange(types);
dataList.Add(newSettingsData);
}
James Curran's answer is functionally the same, but it's better form.
I am using XDocument.Parse method to load following XML :
<AuditMessage>
<Event Action="Read" DateTime="2013/26/7" EventID="100"/>
<User Role="Admin" UserID="12123"/User>
<SourceIdentification SourceID="TeamLondon" SourceType="3"/>
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="1" />
<Network AccessPointID="143.176.8.32" AccessPointTypeCode="`2" />
<Participant ParticipantID="0001" ParticipantType ="2"/>
<Participant ParticipantID="0002" ParticipantType ="3"/>
<Participant ParticipantID="0003" ParticipantType ="3" ParticipantName = "Housh Mangrove"/>
</AuditMessage>
I need to retrieve the values of following attributes in the above XML.
-DateTime
-Role
-AccessPointID
-ParticipantID
-ParticipantName
I have used sourceXML.Root.Element(nodeName).Attribute(attributeToMatch).Value to read a single attribute. I am failing to understand how can I iterate the same thing over different nodes, provided some nodes might be missing.
Please notice :
<Network> and <Participant> nodes are repeating.
ParticipantName attribute exists only in one Instance of
Lastly, any node could be missing in different XMLs provided as Input. Therefore I need to write code in such a way that if a node is missing, the application doesn't throw OBJECT REFERENCE NOT FOUND Exception
here's a quick attempt, you can figure out the ones I didn't add in:
public static void Main()
{
GetAtts(xml);
}
public static Atts GetAtts(string xml)
{
Atts atts = new Atts();
XDocument doc = XDocument.Parse(xml);
if (doc.Root.Element("Event") != null)
atts.datetime = doc.Root.Element("Event").Attribute("DateTime").Value;
//...
foreach (XElement ele in doc.Descendants("Network"))
atts.accesspointid.Add(ele.Attribute("AccessPointID").Value);
return atts;
}
public class Atts
{
public string datetime { get; set; }
public string role { get; set; }
private List<string>_accesspointid = new List<string>();
public List<string> accesspointid { get { return _accesspointid; } set { _accesspointid = value; } }
public List<string> _participantid = new List<string>();
public List<string> participantid { get { return _participantid; } set { _participantid = value; } }
public string participantname { get; set; }
}
you'll have an object you can handle more easily
You can use the Elements method to get an enumeration of nodes of a given name.
You can then test if the enumeration returned any results and look for the appropriate attributes in them.
Something like this, if you want it as CSV:
var data = new List<string>();
var events = doc.Root.Elements("Event");
if (events.Any())
{
foreach (var evt in events)
{
data.Add(evt.Attribute("DateTime").Value);
}
}
var participants = doc.Root.Elements("Participant");
if (participants.Any())
{
foreach (var participant in participants)
{
data.Add(participant.Attribute("ParticipantID").Value);
}
}
var csv = string.Join(", ", data);
Quick and dirty solution - hopefully you can take it from here:
var participantID = String.Join(", ",
xdoc.Root.Elements("Participant")
.Select(e => e.Attribute("ParticipantID"))
.Where(a => a != null)
.Select(a => a.Value)
.Distinct());
How can i import xml elements to object? My code below doesn't work, it fails at the SetValue and i can't figure out why.
But even then, i suspect that linq has a much cleaner way of doing this but i can't find any examples.
class Printers {
public List<Printer> list = new List<Printer>();
public Printers()
{
var xDoc = XDocument.Load(Properties.Settings.Default.XmlSetupPath).Root;
var xPrinters = xDoc.Element("printers").Elements();
foreach (var xPrinter in xPrinters)
{
var printer = new Printer();
foreach (var xEl in xPrinter.Elements())
{
printer.GetType().GetProperty(xEl.Name.ToString()).SetValue(printer, xEl.Value);
}
}
}
}
class Printer
{
public string name;
public string ip;
public string model;
public string infx86;
public string infx64;
public string location;
public string comment;
}
my XML:
<printers>
<printer>
<name>my Printer</name>
<ip>192.168.100.100</ip>
<model>Brother</model>
<driver>ab</driver>
<infx86>ab\cd.INF</infx86>
<comment>Copycenter</comment>
</printer>
<printer>
<name>my Printer</name>
<foobar>oh no!</foobar>
</printer>
</printers>
I want to
You're asking for properties - but your type only has fields. Either make them properties, like this:
public string name { get; set; }
... or use Type.GetField instead.
In terms of making it prettier, I'd personally add a static FromXElement method to your Printer class, at which point you could have:
list = xDoc.Element("printers")
.Elements()
.Select(Printer.FromXElement)
.ToList();
Or you could write a generic method to create a new instance and populate it via reflection, e.g.
public static T FromXElement<T>(XElement element) where T : class, new()
{
T value = new T();
foreach (var subElement in element.Elements())
{
var field = typeof(T).GetField(subElement.Name.LocalName);
field.SetValue(value, (string) subElement);
}
return value;
}
Then:
list = xDoc.Element("printers")
.Elements()
.Select(XmlReflection.FromXElement<Printer>)
.ToList();
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 10 years ago.
I have xml code that would be something like this:
file named: player.xml
<root>
<person>
<fname>Dwight</fname>
<lname>Howard</lname>
<vertLeap>
<try1>32.33</try1>
<try2>33.33</try2>
<try3>34.33</try3>
</vertLeap>
</person>
<person>
<fname></fname>
<lname>Jordan</lname>
<vertLeap>
<try1>40.33</try1>
</vertLeap>
</person>
</root>
This isn't my real xml, but should work for the example.
Now I want to use linq to xml to read the data. Iam am trying like this.
Class:
public class Player
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Leap1 { get; set; }
public int Leap2 { get; set; }
public int Leap3 { get; set; }
public WritePlayertoDatabase()
{
//do stuff to write
}
}
Query:
XDocument xDoc = XDocument.Load("player.xml");
var player_query = from p xDoc.Desendants("person")
select new Player
{
FirstName = p.Element("fname"),
LastName = p.Element("lname"),
Leap1 = p.Element("try1"),
Leap2 = p.Element("try2"),
Leap3 = p.Element("try3")
};
I'm getting a NullReferenceException. Is there a way to test if the elements exist before I try to use the value? Or is there a way better way to accomplish this?
So there are a few things wrong with your linq query.
1) p.Element("fname") will return the fname XML Element, not a string. So you still need to get the element's value. Similarly, the Leap1-3 properties are int, but you will get the element value as a stirng and need to convert it. But, try1-3 are not ints in the xml, so you probably want to change the type to somehting else in the Player class.
2) try1 - tryx element s are all children of 'vertleap'. You can't directly get element 'try1' from 'person'. It will be null.
So how about something more like this:
var player_query = from p in xDoc.Descendants("person")
select new Player
{
FirstName = p.Element("fname") != null ? p.Element("fname").Value : "",
LastName = p.Element("lname") != null ? p.Element("lname").Value : "",
Leap1 = p.Element("vertLeap") != null ? (p.Element("vertLeap").Element("try1") != null ? Decimal.Parse(p.Element("vertLeap").Element("try1").Value) : 0) : 0,
Leap2 = p.Element("vertLeap") != null ? (p.Element("vertLeap").Element("try2") != null ? Decimal.Parse(p.Element("vertLeap").Element("try2").Value) : 0) : 0,
Leap3 = p.Element("vertLeap") != null ? (p.Element("vertLeap").Element("try3") != null ? Decimal.Parse(p.Element("vertLeap").Element("try3").Value) : 0) : 0,
};
Using the XML you posted, the p.Element("try<X>") calls will ALWAYS return nulll... as I see #SimonC has just pointed out. You'll need to tunnel down the XML tree to get the value out, or use Descendants("try<x>").FirstOrDefault() to get the first descendant with a matching name. This may still be null, which leads us back to the real point of the question.
The issue really is that you are trying to perform an operation on an object which may not exist. Apart from the conditional sequence that #SimonC suggested, you can use helper methods or extensions to detect a missing element and provide a meaningful default value.
public static string AsString(this XElement self)
{
if (self == null)
return null;
return self.Value;
}
public static double AsDouble(this XElement self, double defValue = default(double))
{
if (self == null)
return defValue;
double res = defValue;
try { res = (double)self; }
catch { }
return res;
}
public static int AsInt(this XElement self, int defValue = default(int))
{
if (self == null)
return defValue;
double res = defValue;
try { res = (double)self; }
catch { }
return (int)res;
}
Then your Linq query becomes:
var player_query = from p in xDoc.Descendants("person")
select new Player
{
FirstName = p.Element("fname").AsString(),
LastName = p.Element("lname").AsString()
Leap1 = p.Descendants("try1").FirstOrDefault().AsInt(),
Leap2 = p.Descendants("try2").FirstOrDefault().AsInt(),
Leap3 = p.Descendants("try3").FirstOrDefault().AsInt()
};
If there is no 'try1' node within the 'player' node's descendants, the FirstOrDefault() method will return null. The AsInt() extension method is then called with a null this reference, which it detects and returns default(int) instead of throwing an exception.
You could also write a bunch of ConvertElementToXXX(elem, defValue) methods, but I think this is a reasonably valid use of extensions. It's just a shame that XElement doesn't implement IConvertible.
I know I went overboard with this quesiton, but after fiddling around with it I decided to re-write your example with XmlSerialization instead. Depending on your needs, I would highly recommend you serialize your data into objects, and pass these around.
You will need the using using System.Xml.Serialization; namespace, but I wrote the below complete program if you want to use any of it.
public class Program
{
[XmlRoot("root")]
public class Team
{
private List<Player> players = new List<Player>();
[XmlElement("player")]
public List<Player> Players { get { return this.players; } set { this.players = value; } }
// serializer requires a parameterless constructor class
public Team() { }
}
public class Player
{
private List<int> verticalLeaps = new List<int>();
[XmlElement]
public string FirstName { get; set; }
[XmlElement]
public string LastName { get; set; }
[XmlElement]
public List<int> vertLeap { get { return this.verticalLeaps; } set { this.verticalLeaps = value; } }
// serializer requires a parameterless constructor class
public Player() { }
}
static public void Main(string[] args)
{
Program myProgram = new Program();
myProgram.WritePlayertoDatabase();
myProgram.ReadPlayerDatabase();
}
public void WritePlayertoDatabase()
{
Player p1 = new Player() { FirstName = "dwight", LastName = "howard", vertLeap = new List<int>() { 1, 2, 3 } };
Player p2 = new Player() { FirstName = "dwight", LastName = "howard", vertLeap = new List<int>() { 1 } };
Team players = new Team();
players.Players.Add(p1);
players.Players.Add(p2);
XmlSerializer serializer = new XmlSerializer(typeof(Team));
using (TextWriter textWriter = new StreamWriter(#"C:\temp\temp.txt"))
{
serializer.Serialize(textWriter, players);
textWriter.Close();
}
}
public void ReadPlayerDatabase()
{
Team myTeamData = new Team();
XmlSerializer deserializer = new XmlSerializer(typeof(Team));
using (TextReader textReader = new StreamReader(#"C:\temp\temp.txt"))
{
myTeamData = (Team)deserializer.Deserialize(textReader);
textReader.Close();
}
}
}