Reputation: 39898
I am trying to understand how LINQ to XML Functional Construction works.
I have the following sample XML:
string xml = @"<?xml version=""1.0"" encoding=""utf-8"" ?>
<People>
<Person firstName=""John"" lastName=""Doe"">
<ContactDetails>
<EmailAddress>[email protected]</EmailAddress>
</ContactDetails>
</Person>
<Person firstName=""Jane"" lastName=""Doe"">
<ContactDetails>
<EmailAddress>[email protected]</EmailAddress>
<PhoneNumber>001122334455</PhoneNumber>
</ContactDetails>
</Person>
</People>";
I try to modify this XML by adding an IsMale
attribute to the Person
tag and by adding a PhoneNumber
if it doesn't exist.
I can easily write this code by using some procedural code:
XElement root = XElement.Parse(xml);
foreach (XElement p in root.Descendants("Person"))
{
string name = (string)p.Attribute("firstName") + (string)p.Attribute("lastName");
p.Add(new XAttribute("IsMale", IsMale(name)));
XElement contactDetails = p.Element("ContactDetails");
if (!contactDetails.Descendants("PhoneNumber").Any())
{
contactDetails.Add(new XElement("PhoneNumber", "001122334455"));
}
}
But the documentation on MSDN says that Functional Construction should be easier and better to maintain. So I tried writing the same sample with Functional Construction.
XElement root = XElement.Parse(xml);
XElement newTree = new XElement("People",
from p in root.Descendants("Person")
let name = (string)p.Attribute("firstName") + (string)p.Attribute("lastName")
let contactDetails = p.Element("ContactDetails")
select new XElement("Person",
new XAttribute("IsMale", IsMale(name)),
p.Attributes(),
new XElement("ContactDetails",
contactDetails.Element("EmailAddress"),
contactDetails.Element("PhoneNumber") ?? new XElement("PhoneNumber", "1122334455")
)));
It could be me, but I don't find this code better readable.
How can I improve my functional construction? Is there a better way to write this code?
Upvotes: 1
Views: 318
Reputation: 1866
Part of Microsoft's advice to use a functional constructor is to eliminate the static XML resource in the first place. If your initial XML string comes from an external source, then you must load it, modify it and return it.
But if the static XML comes from within the same code that will make the modifications, and your data is available directly, then you would use functional constructors to create the code with the modificiations already made.
Person[] peopleData = new Person[]
{
new Person("John", "Doe", "[email protected]", ""),
new Person("Jane", "Doe", "[email protected]", "001122334455")
}
XElement people =
new XElement("People",
from p in peopleData
select new XElement("Human",
new XAttribute("isMale", p.IsMale),
new XAttribute("name", p.FullName),
new XAttribute("age", p.Age)
)
);
This approach is nearly as easy to read as the original XML, but is also much faster because the parsing of the original XML has been eliminated.
Upvotes: 0
Reputation: 2086
You may be able to increase the expressiveness of the code by breaking up the functionality a bit further, in particular by trying to hide all of those constructors that are being called in the query. I'd suggest a couple of utility functions:
XElement PersonWithGender(XElement person)
{
string name = (string)p.Attribute("firstName") + (string)p.Attribute("lastName");
XAttribute genderAttribute = new XAttribute("isMale", IsMale(name));
return new XElement(genderAttribute, person.Attributes);
}
XElement PersonWithPhoneNumber(XElement person)
{
XElement contactDetails = person.Element("ContactDetails");
XElement emailAddress = contactDetails.Element("EmailAddress");
XElement phoneNumber = contactDetails.Element("PhoneNumber") ?? new XElement("PhoneNumber", "1122334455");
return new XElement("ContactDetails", emailAddress, phoneNumber);
}
These functions each map an initial XElement to a new improved XElement, and so should fit quite nicely into your query. If you feed the result of one into the other, then your query becomes:
XElement newTree = new XElement("People",
from p in root.Descendants("Person")
let pWithGender = PersonWithGender(p)
select PersonWithPhoneNumber(pWithGender));
which I think is approaching the terseness and expressiveness that you'd expect from functional programming. Each of the new functions is short enough that they can be examined without much effort, and the query itself now declares its intention more clearly.
Upvotes: 0
Reputation: 236268
As stated in msdn article you have referenced, it depends on what you are doing. If you are doing many changes to xml, then non functional approach will become complex and not easy to understand. What will be result in this case?
foreach (XElement p in doc.Descendants("Person"))
{
var name = (string)p.Attribute("firstName") + " " + (string)p.Attribute("lastName");
int age = (int)p.Attribute("age");
p.RemoveAttributes();
p.SetAttributeValue("isMale", IsMale(name));
p.SetAttributeValue("name", name);
p.SetAttributeValue("age", age);
p.RemoveNodes();
p.Name = "Human";
}
I have doubts you can see it in a glance. This example is not very descriptive as for me. Also I don't see which structure will have xml after changes.
XElement people =
new XElement("People",
from p in doc.Descendants("Person")
let name = (string)p.Attribute("firstName") + " " + (string)p.Attribute("lastName")
select new XElement("Human",
new XAttribute("isMale", IsMale(name)),
new XAttribute("name", name),
p.Attribute("age")
)
);
As for me, second example describes result and shows xml structure better. So, I'd go with non functional approach if I need to do small changes to existing xml. And I'd go with functional approach for big modifications. What is small and big? I think it's subjective part.
BTW In result I want only age
attribute left and one name
attribute instead of first and last name. Also instead of Person
elements, I'd like to have Human
elements:
<People>
<Human isMale="false" name="John Doe" age="28" />
<Human isMale="true" name="Jane Doe" age="27" />
</People>
Upvotes: 1