I am working with LINQ to XML in C#. I have the following code, the last line keeps throwing a System.Exception: Value cannot be null. I can't figure out what the problem is. I've tried everything.
AuthorizedToSign is a List. I was able to perform the same action using a bulky nested foreach loop.
I know for sure there are no errors as far as the XML file itself.
If anyone can help I would greatly appreciate it.
BusinessAccounts = (from a in accountRoot.Elements()
where (bool)a.Element("Business") == true
select new BusinessAccount()
{
OpenDate = (DateTime)a.Element("DateOpened"),
Password = a.Element("Password").Value,
Balance = (double)a.Element("Balance"),
AccountStatus = (Status)Enum.Parse(typeof(Status), a.Element("Status").Value),
//Element AuthToSign has a collection of sub-elements called "authName"
//couldn't get the code below to work
AuthorizedToSign = (from el in a.Element("AuthorizedToSign").Elements()
select el.Element("AuthName").Value).ToList()
}).ToList();
changing select el.Element("AuthName").Value)
to select (string)el.Element("AuthName"))
doesn't help.
The XML file has a lot of entries that look like this:
<?xml version="1.0" encoding="utf-8"?>
<Accounts>
<BusinessAccount>
<Business>true</Business>
<AccountNumber>34534456</AccountNumber>
<AccountBranchID>100</AccountBranchID>
<AccountName>Elgris Tech</AccountName>
<CompanyName>Elgris Tech</CompanyName>
<CompanyID>235</CompanyID>
<CreditLimit>50000</CreditLimit>
<DateOpened>2014-12-13T00:00:00</DateOpened>
<Balance>1200</Balance>
<Password>1234</Password>
<Status>Active</Status>
<AuthorizedToSign>
<AuthName>Yechiel</AuthName>
<AuthName>Lev</AuthName>
<AuthName>Roman</AuthName>
</AuthorizedToSign>
</BusinessAccount>
<PrivateAccount>
<Business>false</Business>
<AccountNumber>34534458</AccountNumber>
<AccountBranchID>100</AccountBranchID>
<AccountName>Yechiel L.</AccountName>
<CustomerName>Yechiel L.</CustomerName>
<CustomerAddress>2sadfasosa, CA</CustomerAddress>
<CustomerPhone>8-4268</CustomerPhone>
<CardNumber>304456</CardNumber>
<CreditLimit>10000</CreditLimit>
<DateOpened>1994-06-23T00:00:00</DateOpened>
<Balance>555000</Balance>
<Password>pass</Password>
<Status>Active</Status>
</PrivateAccount>
</Accounts>
UPDATE
According to your xml, PrivateAccount element does not have AuthorizedToSign node, thus referencing to AuthorizedToSign node throws an exception. So, in your case solution will be simple:
from a in accountRoot.Elements()
let authorized = a.Element("AuthorizedToSign")
where (bool)a.Element("Business")
select new BusinessAccount()
{
OpenDate = (DateTime)a.Element("DateOpened"),
Password = (string)a.Element("Password"),
Balance = (double)a.Element("Balance"),
AccountStatus = (Status)Enum.Parse(typeof(Status), (string)a.Element("Status")),
AuthorizedToSign = authorized == null ? null : // or new List<string>()
authorized.Elements()
.Select(auth => (string)auth)
.ToList()
};
Getting auth names with query syntax:
AuthorizedToSign = authorized == null ? null : // or new List<string>()
(from auth in authorized.Elements()
select (string)auth).ToList()
Related
I am trying to retrieve the value of two XML attributes and having a hard time using Xelement.
Basically I am getting an API response that has uses the same attribute naming convention for two different values.
This is the response I am getting back after making the call.
-<ResponseData>
<Type value="7" id="Level"/>
<Type value="67.80" id="Score"/>
</ResponseData>
I want to set the two values to two different variables. level & score. I have the fulling code however I keep getting back a null reference error.
var xmlResponse = XElement.Parse(response);
var level = "";
var score = "";
if(xmlResponse.Attribute("id").Value == "Level")
{
level = (string) xmlResponse.Attribute("value");
}
if(xmlResponse.Attribute("id").Value == "Score")
{
score = (string) xmlResponse.Attribute("value");
}
I want my two variables to be set to the values I get back from the API call.
Any help is appreciated
Side note, if this is a SOAP service with a WSDL, have you tried letting Visual Studio scaffold it?
So you have to think about it this way.. your now parsed XElement is a ResponseData object, which has a collection of Type objects in it now, and each Type object has a value and id attribute, which means you need to find the right Type entry for each value you're looking for. EG:
string data = #"<ResponseData>
<Type value=""7"" id=""Level""/>
<Type value=""67.80"" id=""Score""/>
</ResponseData>";
var xmlResponse = XElement.Parse(data);
var levelElement = xmlResponse.Elements("Type")?
.FirstOrDefault(x => x.Attribute("id")?.Value == "Level");
var scoreElement = xmlResponse.Elements("Type")?
.FirstOrDefault(x => x.Attribute("id")?.Value == "Score");
int level;
double score;
level = int.Parse(levelElement?.Attribute("value")?.Value ?? "-1");
score = double.Parse(scoreElement.Attribute("value")?.Value ?? "-1");
Console.WriteLine($"Score {score}; Level {level}");
Why not use?
-<ResponseData>
<Type Level="7" Score="67.80"/>
</ResponseData>
I would recommend using XML that powershell knows how to work with.
[xml]$xml = Get-Content C:\temp\xml.txt
$TypeNode = $xml.SelectSingleNode("//Type[#id='Score']")
$TypeNode.Attributes["value"].Value
I have two xml files which I am comparing with each other. The Linq query result1 is throwing Null Reference Exception after executing correctly for one rule. And when I debugged I found the section is displaying wrong values. I am unable to figure out the cause.
Rules.xml file:
<rule id="1" numberofsections="2">
<section id="1" attributeid="1686" ruleoperator="==" condition="and">
<name>Processor type</name>
<value>Core i3</value>
</section>
<section id="2" attributeid="1438" ruleoperator="<" condition="and" >
<name>Weight</name>
<value>3.8 LBS</value>
</section>
<type>ultrabook</type>
</rule>
And the code snippet:
XDocument rulesXml = XDocument.Load("/RulesEnginescope/RulesEnginescope/rulesSubType.xml");
XDocument productXml = XDocument.Load("c:/RuleEngine/RuleEngine/product.xml");
var getSelectedLeafCategoryRules = from rules2 in rulesXml.Descendants("QueryTransformation").Descendants("leafcategory")
where ((long)System.Convert.ToDouble(rules2.FirstAttribute.Value) == 4590)
select rules2;
var rules = getSelectedLeafCategoryRules.Descendants("rule");
var productAttribute = productXml.Descendants("AttrList").Descendants("Attr");
foreach (var x in rules)
{
var section = x.Elements("section");
/*Wrong value in section.count()*/
Console.WriteLine(section.Count());
var result1 = from p in section
from pa in productAttribute
where (p.Attribute("attributeid").Value == pa.Attribute("id").Value
&& p.Element("name").Value == pa.Element("Name").Value)
select new
{
ruleAttribute = new
{
ruleId = p.Attribute("attributeid").Value,
ruleOperator = p.Attribute("ruleoperator").Value,
name = p.Element("name").Value,
value = p.Element("value").Value,
condition = p.Attribute("condition").Value
},
prodAttribute = new
{
productId = pa.Attribute("id").Value,
name = pa.Element("Name").Value,
value = pa.Element("ValueList").Element("Value").Value
/*Error*/ }
};
if (result1.Count() != 0 && result1.Count() == System.Convert.ToInt64(x.Attribute("numberofsections").Value))
{
//checking each section
foreach (var r in result1)
{
...
}
}
The idiomatic way to get the value of elements and attributes in LINQ-to-XML is to cast the element or attribute to the type you want, rather than accessing the Value attribute.
prodAttribute = new
{
productId = (string)pa.Attribute("id"),
name = (string)pa.Element("Name"),
// ...
}
Using this pattern avoids null ref exceptions caused when calls to Attribute() and Element() don't find a matching node. It also reduces verbosity:
((long)System.Convert.ToDouble(rules2.FirstAttribute.Value)
// should be
(long)rules2.FirstAttribute
You'll still need to add null checks when you're accessing children of children. This can get verbose; one way to keep it succinct is to use IEnumerable-oriented methods so that you're operating on a (possibly empty) collection, rather than a (possibly null) instance.
pa.Element("ValueList").Element("Value").Value
// could be
(string)pa.Elements("ValueList").Elements("Value").FirstOrDefault ()
Finally, note that capitalization matters in LINQ-to-XML. In your code you seem to be switching capitalization patterns ("id" vs. "Name") often; it's likely that your source XML is more consistent.
I'm trying to write an XPath expression to select the name of a node from its value in "qualities" and then select in "qualityNames" the value inside node whose name has previously captured.
E.g. In "qualities" - got value "4", take name "rarity3" then in "qualityNames" I got node named "rarity3" and take value "amazingrarity"
<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>
I'm doing this in C# (It's a MVC App) and I'd prefer to use XPath because I'm indexing the XML and I haven't found a fastest way to query in-memory technique (this XML file has ~3MB and I'm using IndexingXPathNavigator).
Use the local-name() and text() functions + predicates. For value "4" it will be
//qualityNames/*[local-name()=local-name(//qualities/*[text() = '4'])]
Tested with http://www.xpathtester.com
Sounds like you want to create a dictionary of key/value pairs (assuming the node names are only needed to find matches and aren't important to your code).
If so, you can use the following:
var doc = XElement.Parse(#"<result>
<status>1</status>
<qualities>
<Normal>0</Normal>
<rarity1>1</rarity1>
<rarity2>2</rarity2>
<vintage>3</vintage>
<rarity3>4</rarity3>
<rarity4>5</rarity4>
</qualities>
<qualityNames>
<Normal>Normal</Normal>
<rarity1>Genuine</rarity1>
<rarity2>rarity2</rarity2>
<vintage>Vintage</vintage>
<rarity3>amazingrarity</rarity3>
<rarity4>Unusual</rarity4>
</qualityNames>
</result>");
var query = from quality in doc.XPathSelectElements("qualities/*")
join qualityName in doc.XPathSelectElements("qualityNames/*")
on quality.Name equals qualityName.Name
select new { Key = quality.Value, Value = qualityName.Value };
var qualities = query.ToDictionary(a => a.Key, a => a.Value);
var quality3 = qualities["3"];
// quality3 == "Vintage"
var quality4 = qualities["4"];
// quality4 == "amazingrarity"
EDIT: example of how to cache this dictionary
// add reference to System.Web dll
public Dictionary<string, string> GetQualities()
{
// assuming this code is in a controller
var qualities = this.HttpContext.Cache["qualities"] as Dictionary<string, string>;
if (qualities == null)
{
// LoadQualitiesFromXml() is the code above
qualities = LoadQualitiesFromXml();
this.HttpContext.Cache["qualities"] = qualities;
}
return qualities;
}
I think this is what you asked
var rarity3ValueInQualities = xml.SelectSingleNode("/result/qualities/rarity3").InnerText;
var rarity3ValueInqualityNames = xml.SelectSingleNode("/result/qualityNames/rarity3").InnerText;
I have the following xml structure
<userlist>
<user Name="something">
<function name="funcname">
<picture name="pictname">
<curve name="curvename">
<name>NAME</name>
...
</curve>
</picture>
</function>
<function name="function2">
...
</function>
</user>
It goes on a bit more. I have written a function to extract of the "function" tags and place them in objects using code that simplifies to this:
from function in xmlDoc.Descendants("function")
select new FUNCTIONOBJECT {
do all the rest...
}.toList<FUNCTIONOBJECT>();
I am now trying to make it so that I only filter the functions for a given user. so the name attribute of the user is given. Can anyone tell me how I can make this work with LINQ?
My attempt was:
from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == givenusername
select {
var functions =
from function in user.Descendants("function")
select new FUNCTIONOBJECT {
... more stuff
}.toList<FUNCTIONOBJECT>();
But this is wrong and doesnt work.
All help is good. I am pretty new to c# and still trying to wrap my head around xml parsing with LINQ.
EDIT:
updated version of what I have and still doesnt work:
XDocument xmlDoc = XDocument.Load(path);
var functionlist =
(from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == username
select(
(from function in user.Descendants("function")
select new Function
{
name = function.Attribute("name").Value,
pictures =
(from picture in function.Descendants("picture")
select new Picture
{
name = picture.Attribute("name").Value,
layout = picture.Element("layout").Value,
curves =
(from curve in picture.Descendants("curve")
select new Curve
{
name = curve.Attribute("name").Value,
section = curve.Element("section").Value,
run = curve.Element("run").Value,
folder = curve.Element("folder").Value,
drivingpoint = curve.Element("drivingpoint").Value,
display = int.Parse(curve.Element("display").Value),
points =
(from point in curve.Descendants("point")
select new Point
{
id = point.Element("id").Value != null ? point.Element("id").Value : string.Empty,
direction = point.Element("direction").Value != null ? point.Element("direction").Value : string.Empty,
}).ToList<Point>(),
}).ToList<Curve>(),
}).ToList<Picture>(),
}).ToList<Function>(),
).toList();
}
Just few syntax mistakes. Otherwise, the content was correct. Its a bit tricky to learn C# and LINQ syntax at the same time (a language in a language). Here is the corrected code:
from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == givenusername
select ((from function in user.Descendants("function") // When you do a "select something" "something" must have a value, so you can't begin with "{ var functions = ..."
select new FUNCTIONOBJECT
{
// more stuff
}).ToList(); // You don't have to specify <FUNCTIONOBJECT> because the compiler deduce it from the context (here there a new FUNCTIONOBJECT
But here, you will have a List<List<FUNCTIONOBJECT>>. Why? Because there is no information in the code that specify that only 1 user has the givenusername.
If it is the case, just split the code:
// Gets the user
var user = (from user in xmlDoc.Descendants("user")
where user.Attribute("Name").Value == givenusername
select user).Single(); // Get the only user that satisfy the condition (Throw an exception if no user has the given name or if multiple users have the given name)
// Gets its functions
List<FUNCTIONOBJECT> functions = (from function in user.Descendants("function")
select new FUNCTIONOBJECT
{
// more stuff
}).ToList();
I have a complicated xml file and in different levels the following part may exist:
<ChrNote>
<note>The appropriate character is:</note>
</ChrNote>
<ChrDef>
<extension char="A">initial</extension>
<extension char="D">subsequent</extension>
<extension char="G">subsequent delayed</extension>
<extension char="S">sequel </extension>
</ChrDef>
...
and here is the code snippet that I use to build a text file:
var lv1s = from lv1 in XMLDoc.Descendants("chapter").DescendantsAndSelf()
select new
{
SChar = (string)lv1.Element("ChrNote") ?? "",
SCharDef = (string)lv1.Element("ChrDef") ?? "",
//Returns only first attribute ..?
CharLetter = (lv1.Element("ChrDef") == null ? "" :
(string)(lv1.Element("ChrDef")
.Element("extension")
.Attribute("char")) ?? "")
};
The problem is that the above query returns only the first attribute ("A") from element "extension". I have no experience in linq and any help will be appreciated.
(I know that your question says "returns only the first attribute", but there is only one attribute there -- so I'm answering your implied question instead.)
It's only returning the first extension element because you're using Element("extension"). If you used Elements("extension") instead, you'd get the others.
See the documentation:
http://msdn.microsoft.com/en-us/library/system.xml.linq.xcontainer.element.aspx
http://msdn.microsoft.com/en-us/library/bb348975.aspx
Is this what you are looking for?
var result = from x in XMLDoc.Descendants("chapter")
let Definitions = x.XPathSelectElements("ChrDef/extension")
select new
{
Note = x.XPathSelectElement("ChrNote/note") == null ? "" : x.XPathSelectElement("ChrNote/note").Value,
Definitions = Definitions.Select(y=> new { Extension = y.Value, Char = y.Attribute("char").Value })
};
Console.WriteLine ("{0}", result.First().Note);
foreach (var definition in result.First().Definitions)
{
Console.WriteLine ("{0}, {1}", definition.Extension, definition.Char);
}
This will give you the following output:
The appropriate character is:
initial, A
subsequent, D
subsequent delayed, G
sequel , S