Differencebetween Linq to Xml Descendants and Elements

What is the difference between Linq to XML Descendants and Elements

Elements finds only those elements that are direct descendents, i.e. immediate children.

Descendants finds children at any level, i.e. children, grand-children, etc...


Here is an example demonstrating the difference:

<?xml version="1.0" encoding="utf-8" ?>
<foo>
<bar>Test 1</bar>
<baz>
<bar>Test 2</bar>
</baz>
<bar>Test 3</bar>
</foo>

Code:

XDocument doc = XDocument.Load("input.xml");
XElement root = doc.Root;

foreach (XElement e in root.Elements("bar"))
{
Console.WriteLine("Elements : " + e.Value);
}

foreach (XElement e in root.Descendants("bar"))
{
Console.WriteLine("Descendants : " + e.Value);
}

Result:


Elements : Test 1
Elements : Test 3
Descendants : Test 1
Descendants : Test 2
Descendants : Test 3

If you know that the elements you want are immediate children then you will get better performance if you use Elements instead of Descendants.

XDocument.Descendants() versus DescendantNodes()

Descendants returns only elements. DescendantNodes returns all nodes (including XComments, XText, XDocumentType etc).

Consider following xml to see the difference:

<root>
<!-- comment -->
<foo>
<bar value="42"/>Oops!
</foo>
</root>

Descendants will return 3 elements (root, foo, bar). DescendantNodes will return these three elements, and 2 other nodes - text and comment.

What is the difference XElement Nodes() vs Elements()?

The reason is simple: XNode is a base (abstract) class for all xml "parts", and XElement is just one such part (so XElement is subclass of XNode). Consider this code:

XDocument doc = XDocument.Parse("<root><el1 />some text<!-- comment --></root>");
foreach (var node in doc.Root.Nodes()) {
Console.WriteLine(node);
}
foreach (var element in doc.Root.Elements()) {
Console.WriteLine(element);
}

Second loop (over Elements()) will only return one item: <el />

First loop however will return also text node (some text) and comment node (<!-- comment -->), so you see the difference.

You can see what other descendants of XNode there are in documentaiton of XNode class.

LINQ to XML Elements() and Descendants() yielded no results even the elements do exist

You've got a non-standard namespace there:

<RMAStateAcknowledgement ... xmlns="http://XX.ITTS.OA30/digitaldistribution/2012/05">

Therefore all of your elements live in that namespace, and you need to use that when querying them.

XNamespace ns = "http://XX.ITTS.OA30/digitaldistribution/2012/05";
var ele = doc.Descendants(ns + "ReturnRequests");

If you want to use .Element (which only searches down a single level), you need to be querying the root element of the document ("RMAStateAcknowledgement"), not the document itself:

XNamespace ns = "http://XX.ITTS.OA30/digitaldistribution/2012/05";
var ele = doc.Root.Element(ns + "ReturnRequests");

Linq to XML elements and descendants in the same search

If your document structure always has to contain those nodes your query (and accessing SourceTable) is fine. It's fairly obvious of what's going on given reader knows how XML looks like.

However, if you want more top-down approach (which might seem more natural and easier to grasp), you can always query Table node first and store FieldMapping in a variable, but I wouldn't say it has any advantages over your approach:

var nodes = (from table in doc.Descendants("Table")
let fieldMapping = table.Element("FieldMapping")
select new
{
SourceTable = (string)table.Element("SourceTable").Value,
RecordID = (string)fieldMapping.Element("RecordID").Value,
StartYear = (string)fieldMapping.Element("StartYear").Value,
EndYear = (string)fieldMapping.Element("EndYear").Value,
LastName = (string)fieldMapping.Element("LastName").Value,
Title = (string)fieldMapping.Element("Title").Value,
Subject = (string)fieldMapping.Element("Subject").Value
}).ToList();

Linq XML query the descendants of parent with a specific value in attribute

You need to use root.Descendants instead of root.Elements.

For example,

IEnumerable<XElement> xElements =   from element in root.Descendants("TestDriveRequest")
where element.Attribute("Record").Value == "1"
select element;

XContainer.Elements

Returns a filtered collection of the child elements of this element or
document, in document order. Only elements that have a matching XName
are included in the collection.

XContainer.Descendants

Returns a filtered collection of the descendant elements for this
document or element, in document order. Only elements that have a
matching XName are included in the collection Output

Complete Code

IEnumerable<XElement> xElements =   from element in root.Descendants("TestDriveRequest")
where element.Attribute("Record").Value == "1"
select element;

foreach (XElement el in xElements.Descendants().Where(p => !p.HasElements))
{
int keyInt = 0;
string keyName = el.Attribute("name").Value;

while (keyValuePairs.ContainsKey(keyName))
{
keyName = $"{el.Attribute("name").Value}_{keyInt++}";
}
keyValuePairs.Add(keyName, el.Value);
}

Sample Output

Sample Image

Understanding Linq To Xml - Descendants return no results

var result = doc.Descendants("TransactionInformationType");

selects all descendants in the XDocument that have element name "TransactionInformationType" and are in the empty namespace.
From you screenshot it seems the element you're trying to select is in the namespace "https://ssl.ditonlinebetalingssystem.dk/remote/payment" though.
You need to specify that explicitly:

XNamespace ns = "https://ssl.ditonlinebetalingssystem.dk/remote/payment";
↑↑ ↑
var result = doc.Descendants(ns + "TransactionInformationType");

Using LINQ to XML to get specific descendants node value

The LINQ-to-XML methods Elements() and Descendants() only deal with single names, not xpath-like paths. If you want to give an xpath expression, use the xpath extensions.

// using System.Xml.Linq;
var status = (string)xml.XPathSelectElement("//result/status");

Otherwise you need to build up an equivalent query using the methods correctly.

var status = (string)xml.Descendants("result").Elements("status").FirstOrDefault();

LINQ to XML - Unable to read Elements or Descendants from XDocument or XElement

You already have access to those elememts. There is just a missonception on what Console.WriteLine does .

Console.WriteLinejust call ToString over the thing.

For XNode the ToString is overriden to return a Xml string. source

It will work for xEl.Element as it's return an XElement.
XElement is a XContainer, that is an XNode. So XNode overriden method will be called.

But not for Descendants as it return a collection waiting to be enumerated. Lazy linQ.

In order to display your result you can either enumerate the decendants and call ToString on them.

foreach(var el in xEl.Descendants("Student")){
Console.WriteLine(el);
}

Here is an Example: Live Demo

var input = @"<?xml version=""1.0"" encoding=""utf-8""?>
<Students>
<Student>
<Ordinal>1</Ordinal>
<Name>Student1</Name>
<BirthDate>Date1</BirthDate>
<ID>ID1</ID>
</Student>

<Student>
<Ordinal>2</Ordinal>
<Name>Student2</Name>
<BirthDate>Date2</BirthDate>
<ID>ID2</ID>
</Student>
</Students>";

// Parse string is equivalent to load path.
XDocument xDoc = XDocument.Parse(input);
XElement xEl = XElement.Parse(input);

var xDocString = xDoc.ToString();

var XElElement = xEl.Element("Student").ToString();
// => <Student> <Ordinal>1</Ordinal> ...

var XElDecendant = xEl.Descendants("Student").ToString();
// => System.Xml.Linq.XContainer+<GetDescendants>d__39
// I'm a linQ container containing XNode, please enumerate me.

var xDocElement = xDoc.Element("Student").ToString();
//=> null

var xDocDecendant = xDoc.Descendants("Student").ToString();
// => System.Xml.Linq.XContainer+<GetDescendants>d__39
// I'm a linQ container containing XNode, please enumerate me.

foreach(var el in xEl.Descendants("Student")){
Console.WriteLine(el);
}

C# - LINQ to XML - Find Descendants where the attribute starts with something

Your issue is that x.Parent.Parent.Attribute("Name") doesn't exist for the first Shape in your Xml. The parent of the parent is PageContents root of the document and that doesn't have a Name attribute.

You could solve easily like this:

var datasets =
pageContents
.Descendants(nameSpace + "Shape")
.Where(x => x.Parent.Parent.Name == nameSpace + "Shape")
.Where(x => x.Parent.Parent.Attribute("Name").Value.StartsWith("CFF Container"));

You can also write your whole code like this:

public string XmlFindSurveyName(XElement pageContents, XNamespace nameSpace) =>
(
from shape in pageContents.Descendants(nameSpace + "Shape")
where shape.Parent.Parent.Name == nameSpace + "Shape"
where shape.Parent.Parent.Attribute("Name").Value.StartsWith("CFF Container")
where shape.Element(nameSpace + "Text") != null
select shape.Element(nameSpace + "Text").Value
).FirstOrDefault() ?? "";


Related Topics



Leave a reply



Submit