Read from XML to an object using LINQ - c#

I am trying to read from an XML and enter into an object list.
My XML is
<User>
<Name>John</Name>
<Role>Admin</Role>
<QAList>
<QA>
<Question> Question 1 </Question>
<Answers>
<Answer> Answer 1 </Answer>
<Answer> Answer 2 </Answer>
<Answer> Answer 3 </Answer>
</Answers>
</QA>
</QAList>
</User>
This is my object class:
public class ListQuestionAnswers: INotifyPropertyChanged
{
private List<QuestionAnswer> _questionAnswer;
public List<QuestionAnswer> QuestionAnswerList
{
get { return _questionAnswer; }
set { _questionAnswer = value; }
}
private string _name;
public string Name
{
get { return _name; }
set { _name = value; }
}
private string _role;
public string Role
{
get { return _role; }
set { _role = value; }
}
}
public class QuestionAnswer
{
public string _question;
public List<AnswerList> _answers;
public string Question
{
get { return _question; }
set { _question = value; }
}
public List<AnswerList> Answers
{
get { return _answers; }
set { _answers = value; }
}
}
public class AnswerList
{
private string _answer;
public string Answer
{
get { return _answer; }
set {_answer = value; }
}
}
I am able to get Name and Role, but how can I get QA List? I tried writing a select within the query but that didnt seem to work.
XDocument xmlDoc = XDocument.Load(#"C:\Test\Sample.xml");
var files = (from x in xmlDoc.Elements("User")
select new TestCasesViewModel
{
Name = (string)x.Element("Name").Value ?? string.Empty,
Role = (string)x.Element("Role").Value ?? string.Empty
}).ToList();

You can do the following:
var files = (from x in xmlDoc.Elements("User")
select
new TestCasesViewModel
{
Name = (string)x.Element("Name") ?? string.Empty,
Role = (string)x.Element("Role") ?? string.Empty,
QuestionAnswerList =
x.Descendants("QA")
.Select(q => new QuestionAnswer
{
Question = (string)q.Element("Question"),
Answers = q.Descendants("Answer").Select(a => new AnswerList { Answer = (string)a}).ToList()
}).ToList()
}).ToList();
Note that you don't need to access Value property if you are using explicit cast, whole point of using explicit cast is to avoid possible exceptions, if you use the Value property then explicit cast become redundant because if the element wasn't found it will throw the exception anyway...

In situations like this, I find it to be extremely helpful to split-up a complex LINQ expression into multiple sub-expressions, if only to help with development and debugging.
Without knowing more about the structure of your real xml data, here are some ideas:
XDocument xmlDoc = XDocument.Load(#"C:\Test\Sample.xml");
var users = xmlDoc.Elements("User");
foreach(var user in users)
{
var qaList = user.Element("QAList");
foreach(var qa in qaList.Elements("QA"))
{
//at this point, you should have access the <Question> node e.g.
string question = qa.Element("Question").Value;
//and the list of answers would be similar to before
var answers = qa.Element("Answers");
foreach(var answer in answers.Elements("Answer"))
{
//now answer.Value should be the text of the answer
}
}
}
Again, this isn't the "cleanest" approach, and certainly there are more concise ways to express this in LINQ. The point is, you have quite a few more options for break-points/debugging, allowing you explore the objects you are working with.

Related

Is it possible to xml-serialize a property (in a xml serialized class) that returns a list of string objects?

I have a class, lets call it, Employee, that has a list of qualifications as property
[XmlRoot("Employee")]
public class Employee
{
private string name;
private IListManager<string> qualification = new ListManager<string>();
public Employee()
{
}
[XmlElement("Name")]
public string Name
{
get
{
return name;
}
set
{
if (value != null)
{
name = value;
}
}
}
public ListManager<string> QualificationList
{
get
{
return qualification as ListManager<string>;
}
}
[XmlElement("Qual")]
public string Qualifications
{
get
{
return ReturnQualifications();
}
}
private string ReturnQualifications()
{
string qual = string.Empty;
for (int i = 0; i < qualification.Count; i++)
{
qual += " " + qualification.GetElement(i);
}
return qual;
}
public override String ToString()
{
String infFormat = String.Format("{0, 1} {1, 15}", this.name, Qualifications);
return infFormat;
}
}
}
Then I have a method that serializes the above class tom XML by taking a list of Employees as patameter, the method is generic:
public static void Serialize<T>(string path, T typeOf)
{
XmlSerializer xmlSer = new XmlSerializer(typeof(T));
TextWriter t = new StreamWriter(path);
try
{
xmlSer.Serialize(t, typeOf);
}
catch
{
throw;
}
finally
{
if (t != null)
{
t.Close();
}
}
}
This is how I call the method XMLSerialize:
controller.XMLSerialize<Employee>(opnXMLFileDialog.FileName, employeeList);
The result of the method execution returns a file and is shown below:
<?xml version="1.0" encoding="utf-8"?>
<ArrayOfEmployees xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Employees>
<Name>g</Name>
</Employees>
</ArrayOfEmployees>
as you can see there is only the property name included in the file. How do I proceed from here and serialize the list of qualifications too? Any suggestionswill be greatly appreciated.
Thanks in advance.
In order to serialize a property, XmlSerializer requires that the property have both a public setter and public getter. So whatever property you choose to serialize your qualifications must have a setter as well as a getter.
The obvious solution would be for your QualificationList to have a setter as well as a getter:
public ListManager<string> QualificationList
{
get
{
return qualification as ListManager<string>;
}
set
{
qualification = value;
}
}
If for some reason this cannot be done -- perhaps because your ListManager<string> class is not serializable -- you could serialize and deserialize it as a proxy array of strings, like so:
[XmlIgnore]
public ListManager<string> QualificationList
{
get
{
return qualification as ListManager<string>;
}
}
[XmlElement("Qual")]
[Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string[] QualificationArray
{
get
{
return Enumerable.Range(0, qualification.Count).Select(i => qualification.GetElement(i)).ToArray();
}
set
{
// Here I am assuming your ListManager<string> class has Clear() and Add() methods.
qualification.Clear();
if (value != null)
foreach (var str in value)
{
qualification.Add(str);
}
}
}
Then for the following employee list:
var employee = new Employee() { Name = "Mnemonics" };
employee.QualificationList.Add("posts on stackoverflow");
employee.QualificationList.Add("easy to remember");
employee.QualificationList.Add("hard to spell");
var list = new List<Employee>();
list.Add(employee);
The following XML is generated:
<?xml version="1.0" encoding="utf-16"?>
<ArrayOfEmployee xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Employee>
<Name>Mnemonics</Name>
<Qual>posts on stackoverflow</Qual>
<Qual>easy to remember</Qual>
<Qual>hard to spell</Qual>
</Employee>
</ArrayOfEmployee>
Is that satisfactory?

Get node value of Parent two level up in XML in C#

I have XML as shown in sample below -
<?xml version="1.0" encoding="UTF-8"?>
<Model>
<Part Id="0">
<Specs>
<Spec Id = "100" name="SpecNode">
</Spec>
</Specs>
</Part>
</Model>
This is an sample illustration of XML I have , so pls ignore any typing errors.
So in this XML I was able to retrieve Spec as XPathNodeIterator object. So now I would like to go for Part node and get Id attribute value of it. for what I have is XPathNodeIterator object which points to Spec and Part node lies two level above.
So please guide me in code changes I shall do to achieve this requirement.
Her is my code snippet..
System.Xml.XPath.XPathDocument xmlDoc = new System.Xml.XPath.XPathDocument("my.xml");
XPathNavigator xmlNav = xmlDoc.CreateNavigator();
XPathNodeIterator nodes = xmlNav.Select(XPathExpression.Compile(#"//Part/Specs/Spec[#name="SpecNode"]))
Above code sample gives me nodes object. SO my requirement now is to get Id value of Part node which two level above it.
You could use the following LINQ to retrieve the Id-attribute of the Part-node:
const string file = #"D:\Temp\file.xml";
// Load the file.
XDocument doc = XDocument.Load(file);
// Retrieve the attribute selecting the Part-Element and then its Id-attribute.
XAttribute partAttribute = doc.Root.Descendants("Part").Select(element => element.Attribute("Id")).FirstOrDefault();
// Call partAttribute.Value to retrieve its' value.
Console.WriteLine(partAttribute.Value);
Console.Read();
Output: 0.
Do note that if the Part-Element does not exist, a NullReferenceException will be thrown on the partAttribute.Value call. The same happens if the Part-Element exists but the Id does not. Use if(partAttribute != null) to make sure if the value exists.
If you will be getting alot of Part-nodes, you could sort them on ID using KeyValuePairs:
List<KeyValuePair<int, XElement>> partNodesWithIDValue = new List<KeyValuePair<int, XElement>>();
// Get a list of all Part-nodes where an ID exists.
List<XElement> partNodes = doc.Root.Descendants("Part").Where(element => element.Attribute("Id") != null).ToList();
// Sort the XElements according to their ID-value.
foreach (XElement partNode in partNodes)
{
KeyValuePair<int, XElement> elementWithAttribID = new KeyValuePair<int, XElement>(int.Parse(partNode.Attribute("Id").Value), partNode);
partNodesWithIDValue.Add(elementWithAttribID);
}
// Get a list of all Part-elements where the ID = 1.
List<XElement> partNodesWithID1 = partNodesWithIDValue.Where(kvp => kvp.Key == 1).Select(kvp => kvp.Value).ToList();
Seeing as you want to keep your original code, a simple change of XPath does the trick:
XPathNodeIterator nodes = xmlNav.Select(XPathExpression.Compile(#"//Part[#Id]"));
//Part[#Id]: //Part selects all nodes that are named Part. Adding the [#Id] makes it only select those Part-nodes that have an Id-attribute.
Deserialize the XML to an object, it'll be easier to work with and produces cleaner code:
Model class:
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "", IsNullable = false)]
public partial class Model
{
private ModelPart[] partField;
[System.Xml.Serialization.XmlElementAttribute("Part")]
public ModelPart[] Part
{
get
{
return this.partField;
}
set
{
this.partField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class ModelPart
{
private ModelPartSpecs specsField;
private byte idField;
public ModelPartSpecs Specs
{
get
{
return this.specsField;
}
set
{
this.specsField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte Id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class ModelPartSpecs
{
private ModelPartSpecsSpec specField;
public ModelPartSpecsSpec Spec
{
get
{
return this.specField;
}
set
{
this.specField = value;
}
}
}
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true)]
public partial class ModelPartSpecsSpec
{
private byte idField;
private string nameField;
[System.Xml.Serialization.XmlAttributeAttribute()]
public byte Id
{
get
{
return this.idField;
}
set
{
this.idField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public string name
{
get
{
return this.nameField;
}
set
{
this.nameField = value;
}
}
}
Deserialization:
XmlSerializer serializer = new XmlSerializer(typeof(Model));
Model model = null;
using (XmlReader reader = XmlReader.Create("data.xml"))
{
model = (Model)serializer.Deserialize(reader);
}
Now you'll be able to access model as an object and accessing the Part node is as simple as doing:
model.Part

question about linq select and ToList()

I have a problem with returning a list by executing a Select LINQ query. This is the query:
var data = Repository<EducationString>
.Find()
.ToList()
.Select(p => new EducationStringModel() {
Id = p.Id,
Title = p.Title,
EducationDegree=p.EducationDegree })
.ToList();
As you can see I used ToList() 2 times. I don't know why but when I delete the first ToList() I see this error, "Index was outside the bounds of the array", but by having both ToList() there is no problem.
Would it help if I said EducationDegree in EducationStringModel is an IList<EducationDegree>?
Is there anybody who knows the reason?
#Mark :its L2O
if u need to see the classes:
public class EducationStringModel
{
private IList _educationDegree = new List();
public IList EducationDegree
{
get
{
if (_educationDegree == null)
{
_educationDegree = new List();
}
return _educationDegree;
}
set { _educationDegree = value; }
}
public int? Id { get; set; }
public string Title { get; set; }
}
public class EducationString{
private string _title;
private IList _educationExperiences;
private IList _educationDegree;
virtual public string Title
{
get { return _title; }
set { _title = value; }
}
virtual public IList<EducationExperience> EducationExperiences
{
get
{
if (_educationExperiences == null)
{
_educationExperiences = new List<EducationExperience>();
}
return _educationExperiences;
}
set
{
_educationExperiences = value;
}
}
virtual public IList<EducationDegree> EducationDegree
{
get
{
if (_educationDegree == null)
{
_educationDegree = new List<EducationDegree>();
}
return _educationDegree;
}
set
{
_educationDegree = value;
}
}
}
Is that the actual code? The only unclear thing there is: what does Find() return?
It sounds like the ToList is helping here by breaking composition and using LINQ-to-Objects, in which case AsEnumerable() should work just as well. After that you just do a Select (which for L2O just takes each item in turn and applies the map). If Find() is something more exotic, it sounds like a bug in that LINQ provider (or perhaps more fairly: that provider struggling to cope with an atypical construct). Hard to say more without a fully reproducible example.

Storing entity in XML, using MVVM to read/write in WPF Application

Say I've a class (model) called Instance with Properties DatbaseHostname, AccessManagerHostname, DatabaseUsername and DatabasePassword
public class Instance
{
private string _DatabaseHostname;
public string DatabaseHostname
{
get { return _DatabaseHostname; }
set { _DatabaseHostname = value; }
}
private string _AccessManagerHostname;
public string AccessManagerHostname
{
get { return _AccessManagerHostname; }
set { _AccessManagerHostname = value; }
}
private string _DatabaseUsername;
public string DatabaseUsername
{
get { return _DatabaseUsername; }
set { _DatabaseUsername = value; }
}
private string _DatabasePassword;
public string DatabasePassword
{
get { return _DatabasePassword; }
set { _DatabasePassword = value; }
}
}
I'm looking for a sample code to read/write this Model to XML (preferably linq2XML) => storing 1:n instances in XML.
i can manage the the view and ViewModel part myself, although it would be nice if someone had a sample of that part too..
Well, you could use Linq to XML, but your class is a perfect candidate for XML Serialization, which is much simpler IMHO :
var list = new List<Instance>();
...
// Serialization
var xs = new XmlSerializer(typeof(List<Instance>));
using (var writer = XmlWriter.Create(filename))
{
xs.Serialize(writer, list);
}
...
// Deserialization
using (var reader = XmlReader.Create(filename))
{
list = xs.Deserialize(reader) as List<Instance>;
}
Not sure how you want your xml structured, but this should work:
List<Instance> instances = new List<Instance>();
// Get your instances here...
var baseNode = new XElement("Instances");
instances.ForEach(instance => baseNode.Add("Instance",
new XAttribute("DatabaseHostname", instance.DatabaseHostname),
new XAttribute("AccessManagerHostname", instance.AccessManagerHostname),
new XAttribute("DatabaseUsername", instance.DatabaseUsername),
new XAttribute("DatabasePassword", instance.DatabasePassword)));

XML Serialization - XmlCDataSection as Serialization.XmlText

I am having problems serializing a cdata section using c#
I need to serialize XmlCDataSection object property as the innertext of the element.
The result I am looking for is this:
<Test value2="Another Test">
<![CDATA[<p>hello world</p>]]>
</Test>
To produce this, I am using this object:
public class Test
{
[System.Xml.Serialization.XmlText()]
public XmlCDataSection value { get; set; }
[System.Xml.Serialization.XmlAttributeAttribute()]
public string value2 { get; set; }
}
When using the xmltext annotation on the value property the following error is thrown.
System.InvalidOperationException:
There was an error reflecting property
'value'. --->
System.InvalidOperationException:
Cannot serialize member 'value' of
type System.Xml.XmlCDataSection.
XmlAttribute/XmlText cannot be used to
encode complex types
If I comment out the annotation, the serialization will work but the cdata section is placed into a value element which is no good for what I am trying to do:
<Test value2="Another Test">
<value><![CDATA[<p>hello world</p>]]></value>
</Test>
Can anybody point me in the right direction to getting this to work.
Thanks, Adam
Thanks Richard, only now had chance to get back to this. I think I have resolved the problem by using your suggestion. I have created a CDataField object using the following:
public class CDataField : IXmlSerializable
{
private string elementName;
private string elementValue;
public CDataField(string elementName, string elementValue)
{
this.elementName = elementName;
this.elementValue = elementValue;
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
w.WriteStartElement(this.elementName);
w.WriteCData(this.elementValue);
w.WriteEndElement();
}
public void ReadXml(XmlReader r)
{
throw new NotImplementedException("This method has not been implemented");
}
}
The way Test is defined, your data is a CData object. So the serialisation system is trying to preserve the CData object.
But you want to serialise some text data as a CData section.
So first, the type of Test.value should be String.
You then need to control how that field is serialised, but there does not appear to be any inbuilt method or attribute to control how strings are serialised (as string, maybe with entities for reserved characters, or as CDATA). (Since, from an XML infoset perspective all of these are the same, this is not surprising.)
You can of course implemented IXmlSerializable and just code the serialisation of the Test type yourself which gives you complete control.
This basically shorter version of Jack answer with better error messages:
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] ContentAsCData
{
get => new[] { new XmlDocument().CreateCDataSection(Content) };
set => Content = value?.Cast<XmlCDataSection>()?.Single()?.Data;
}
Just found an alternative from here:
[XmlIgnore]
public string Content { get; set; }
[XmlText]
public XmlNode[] CDataContent
{
get
{
var dummy = new XmlDocument();
return new XmlNode[] {dummy.CreateCDataSection(Content)};
}
set
{
if (value == null)
{
Content = null;
return;
}
if (value.Length != 1)
{
throw new InvalidOperationException(
String.Format(
"Invalid array length {0}", value.Length));
}
var node0 = value[0];
var cdata = node0 as XmlCDataSection;
if (cdata == null)
{
throw new InvalidOperationException(
String.Format(
"Invalid node type {0}", node0.NodeType));
}
Content = cdata.Data;
}
}
}
I had very same problem as Adam. However this Answer does not helped me at 100% :) but gives me a clue. So I'va created a code like below. It generates XML like this:
<Actions>
<Action Type="reset">
<![CDATA[
<dbname>longcall</dbname>
<ontimeout>
<url>http://[IPPS_ADDRESS]/</url>
<timeout>10</timeout>
</ontimeout>
]]>
</Action>
<Action Type="load">
<![CDATA[
<dbname>longcall</dbname>
]]>
</Action>
</Actions>
Code:
public class ActionsCDataField : IXmlSerializable
{
public List<Action> Actions { get; set; }
public ActionsCDataField()
{
Actions = new List<Action>();
}
public XmlSchema GetSchema()
{
return null;
}
public void WriteXml(XmlWriter w)
{
foreach (var item in Actions)
{
w.WriteStartElement("Action");
w.WriteAttributeString("Type", item.Type);
w.WriteCData(item.InnerText);
w.WriteEndElement();
w.WriteString("\r\n");
}
}
public void ReadXml(XmlReader r)
{
XmlDocument xDoc = new XmlDocument();
xDoc.Load(r);
XmlNodeList nodes = xDoc.GetElementsByTagName("Action");
if (nodes != null && nodes.Count > 0)
{
foreach (XmlElement node in nodes)
{
Action a = new Action();
a.Type = node.GetAttribute("Type");
a.InnerText = node.InnerXml;
if (a.InnerText != null && a.InnerText.StartsWith("<![CDATA[") && a.InnerText.EndsWith("]]>"))
a.InnerText = a.InnerText.Substring("<![CDATA[".Length, a.InnerText.Length - "<![CDATA[]]>".Length);
Actions.Add(a);
}
}
}
}
public class Action
{
public String Type { get; set; }
public String InnerText { get; set; }
}

Categories

Resources