I'm new to LINQ and am attempting to query an XML file by attribute and a descendant element value.
Here's a snip of my XML:
<redirurl>
<exceptionList state="FL">
<exception>
<plancode>ZZ</plancode>
<url>https://zzzz.com</url>
</exception>
</exceptionList>
<exceptionList state="NC">
<exception>
<plancode>AA</plancode>
<url>https://aaaa.com</url>
</exception>
<exception>
<plancode>BB</plancode>
<url>https://bbbb.com</url>
</exception>
</exceptionList>
</redirurl>
I'm trying to retrieve the value for url by state and plancode. For example, if the exceptionList state attribute = "NC" and the plancode = "BB", I want to get the url of "https://bbbb.com".
Here's my code:
var xmlFilePath = "myFilePathHere";
XElement xelement = XElement.Load(xmlFilePath );
IEnumerable<XElement> urlAddress = from el in xelement.Elements("exceptionList")
where (string)el.Attribute("state") == "NC"
&& (string)el.Element("exception").Element("plancode") == "BB"
select el;
I can't get the query right to save my life. If I omit the third line of the query (plancode line), I get the entire exceptionList node as a result. I suppose I could loop through it to get the plancode, but that doesn't seem like the right thing to do. The query as is does not return results. I've spent about 10 hours on this, doing tutorials and looking at code examples, but I'm just not getting it. Can someone advise what I'm missing? Thanks in advance.
Here is a Linq sequence that does as you ask:
XDocument doc = XDocument.Parse("<redirurl><exceptionList state=\"FL\"><exception><plancode>ZZ</plancode><url>https:////zzzz.com</url></exception></exceptionList><exceptionList state=\"NC\"><exception><plancode>AA</plancode><url>https:////aaaa.com</url></exception><exception><plancode>BB</plancode><url>https:////bbbb.com</url></exception></exceptionList></redirurl>");
var urlIWant = doc.Root.Descendants("exceptionList").Where(x => x.Attribute("state").Value == "NC").FirstOrDefault()
.Descendants("exception").Where(z => z.Descendants("plancode").FirstOrDefault().Value == "BB")
.FirstOrDefault().Descendants("url").FirstOrDefault().Value;
Related
I am working on a web service. I am using linq to query a database. Seemingly simple, but I've run into an issue. Here is my code for reference:
List<Comment> res = new List<Comment>();
using (ApplicationHistoryEntities ahe = new ApplicationHistoryEntities())
{
res = (from columns in ahe.Comments
where columns.NetforumId == actionuniqueid
select columns).ToList();
}
If I have no entries in the database, will my .ToList() throw an error? I could deploy it, and just try it out but I want to know more about the mechanism that my linq is using. If ahe.Comments database has no rows... what will the (from...) section return?
I could just add a null reference check, use dynamics etc but I want to really understand it.
I found this Q: how to know if my linq query returns null but it seems like all of the answers are in conflict on how it really should be done...
example answers:
Either you can convert it to list and then check the count
Best approach is to check there is null(no items) in list use Any() instead of count()
LINQ queries should never return null and you should not get an exception if the result is empty. You probably have an error in your code.
You can realise the result as a list then check the items.
You can see why I question how it works.
Edit:
Final code looks like this:
List<Comment> res;
using (ApplicationHistoryEntities ahe = new ApplicationHistoryEntities())
{
res = ahe.Comments?.Where(rowItem => rowItem.NetforumId == actionuniqueid).ToList() ??
new List<Comment>().ToList();
}
Look at this example:
List<string> test = new List<string>();
var test1 = test.Where(x => x == "a").ToList();
If test exists but is empty the query returns an empty list. If test is null the query throws an error. So you can adapt the query as follows
List<string> test = new List<string>();
test = null;
var test1 = test?.Where(x => x == "a") ?? new List<string>().ToList();
The query is now 'safe'. Both of the above examples return an empty list i.e. test1.Count() will return zero but will be usable.
You can also look at the definitions of Where and ToList
I want to extract information from various websites. I am using HtmlAgilityPack and Linq to XML. So far I have managed to extract the value from a single node in a website by writing:
var q = document.DocumentNode.DescendantNodes()
.Where(n => n.Name == "img" && n.Id == "GraphicalBoard001")
.FirstOrDefault();
But I am really interested in the whole collection of img's that start with "GraphicalBoard". I tried something like:
var q2 = document.DocumentNode.DescendantNodes()
.Where(n => n.Name == "img" && n.Id.Contains("GraphicalBoard"))
.Select...
But it seems that linq doesn't like the Contains-method, since I lose the Select option in intellisense. How can I extract all the img-tags where the Id starts with "GraphicalBoard"?
How can I extract all the img-tags where the Id starts with "GraphicalBoard"?
You had it already, just stop at the call to Where(). The Where() call filters the collection by the items that satisfies the predicate.
Though you should write it so you filter through the img descendants, not all descendants.
var query = doc.DocumentNode.Descendants("img")
.Where(img => img.Id.StartsWith("GraphicalBoard"));
I have the following code
nodes = data.Descendants(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Results")).Nodes();
System.Collections.Generic.IEnumerable<Result> res = new List<Result>();
if (nodes.Count() > 0)
{
var results = from uris in nodes
select new Result
{
URL =
((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Url")).Value,
Title =
((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Title")).Value,
Description =
((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Description")).Value,
DateTime =
((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}DateTime")).Value,
};
res = results;
}
Where Results is a object who has those URL, Title, Description, and DateTime variables defined.
This all works fine normally, but when a 'node' in nodes doesnt contain a Description element (or at least I think thats whats throwing it) the program hits the "res = results;"
line of code and throws a 'object reference not set to...' error and highlights the whole section right after "select new Results"..
How do I fix this?
The simplest way is to cast to string instead of using the Value property. That way you'll end up with a null reference for the Description instead.
However, your code can also be made a lot nicer:
XNamespace ns = "http://schemas.microsoft.com/LiveSearch/2008/04/XML/web";
var results = data.Descendants(ns + "Results")
.Elements()
.Select(x => new Result
{
URL = (string) x.Element(ns + "Url"),
Title = (string) x.Element(ns + "Title"),
Description = (string) x.Element(ns + "Description"),
DateTime = (string) x.Element(ns + "DateTime")
})
.ToList();
See how much simpler that is? Techiques used:
Calling ToList() on an empty sequence gives you a list anyway
This way you'll only ever perform the query once; before you were calling Count() which would potentially have iterated over each node. In general, use Any() instead of Count() > 0) - but this time just making the list unconditional is simpler.
Use the Elements() method to get child elements, rather than casting multiple times. (Your previous code would have thrown an exception if it had encountered any non-element nodes)
Use the implicit conversion from string to XNamespace
Use the +(XNamespace, string) operator to get an XName
If the Description element is not included you should test if this
((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Description"))
is not null before using Value. Try this code:
var results = from uris in nodes let des = ((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Description"))
select new Result
{
URL = ((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Url")).Value,
Title = ((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}Title")).Value,
Description = (des != null) ? des.Value : string.Empty,
DateTime = ((XElement)uris).Element(XName.Get("{http://schemas.microsoft.com/LiveSearch/2008/04/XML/web}DateTime")).Value,
};
I've got a list of IQueryable. I'm trying to split this list into an array of IQueryable matching on a certain field (say fieldnum) in the first list...
for example, if fieldnum == 1, it should go into array[1]. I'm using Where() to filter based on this field, it looks something like this:
var allItems = FillListofMyObjects();
var Filtered = new List<IQueryable<myObject>(MAX+1);
for (var i = 1; i <= MAX; i++)
{
var sublist = allItems.Where(e => e.fieldnum == i);
if (sublist.Count() == 0) continue;
Filtered[i] = sublist;
}
however, I'm getting the error Field "t1.fieldnum" is not a reference field on the if line. stepping through the debugger shows the error actually occurs on the line before (the Where() method) but either way, I don't know what I'm doing wrong.
I'm farily new to LINQ so if I'm doing this all wrong please let me know, thanks!
Why don't you just use ToLookup?
var allItemsPerFieldNum = allItems.ToLookup(e => e.fieldnum);
Do you need to reevaluate the expression every time you get the values?
Why not use a dictionary?
var dictionary = allItems.ToDictionar(y => y.fieldnum);
I am parsing an XML API response with HTMLAgilityPack. I am able to select the result items from the API call.
Then I loop through the items and want to write the ChildNodes to a table. When I select
ChildNodes by saying something like:
sItemId = dnItem.ChildNodes(0).innertext
I get the proper itemId result. But when I try:
sItemId = dnItem.ChildNodes("itemId").innertext
I get "Referenced object has a value of 'Nothing'."
I have tried "itemID[1]", "/itemId[1]" and a veriety of strings. I have tried SelectSingleNode and ChildNodes.Item("itemId").innertext. The only one that has worked is using the index.
The problem with using the index is that sometimes child elements are omitted in the results and that throw off the index.
Anybody know what I am doing wrong?
HtmlAgilityPack.HtmlDocument htmlDoc = new HtmlDocument();
htmlDoc.LoadHtml(#webHTMLeditor.TextXhtml);
HtmlNodeCollection tableRows = htmlDoc.DocumentNode.SelectNodes("//tr");
for (int i = 0; i < tableRows.Count; i++)
{
HtmlNode tr = tableRows[i];
HtmlNode[] td = new HtmlNode[2];
string xpath = tr.XPath + "//td";
HtmlNodeCollection cellRows = tr.SelectNodes(#xpath);
//td[0] = tr.ChildNodes[1];
//td[1] = tr.ChildNodes[3];
try
{
td[0] = cellRows[0];
td[1] = cellRows[1];
}
catch (Exception)
{ }
//etc
}
The code is used to extract data from a table, row by row, by cell per row.
I used the existing xpath and I altered it acording to my needs.
Good luck!