How to get different values with same name from xml with linq - c#

XML - Code:
<Store>
<Products>
<Product id="PROD01">
<Title>Product 1</Title>
<Description><![CDATA[Product <b>1</b> description]]></Description>
<Image>prod01.gif</Image>
<Specs>
<Spec>Good computer</Spec>
<Spec>Good display</Spec>
<Spec>Latest version</Spec>
</Specs>
<Availability>same day</Availability>
</Product>
<Product id="PROD02">
<Title>Product 2</Title>
<Description><![CDATA[Product <b>2</b> description]]></Description>
<Image>prod01.gif</Image>
<Specs>
<Spec>Good computer</Spec>
<Spec>Soon available</Spec>
</Specs>
<Availability>next day</Availability>
</Product>
</Products>
</Store>
C# - Code:
public List<DetailList> GetDetails()
{
DetailList d = new DetailList();
List<DetailList> DetailLists =
(from product in xdocList.Descendants("Product")
join detail in xdocDetail.Descendants("Product")
on (string)product.Attribute("id") equals (string)detail.Attribute("id")
into outerProducts
from outerProduct in outerProducts
select new DetailList
{
Detail1 = (string)product.Attribute("id"),
Detail2 = (string)product.Element("Title"),
Detail3 = (string)product.Element("Description"),
Detail4 = (string)product.Element("Image"),
Detail5 = (string)outerProduct.Elements("Specs")
Detail6 = (string)outerProduct.Element("Availability"),
Detail7 = (string)product.Element("Price"),
}).ToList();
return DetailLists;
}
Output: Good computerGood displayLatest version
But wanted output is:
Good computer
Good display
Latest version
For output I used asp:repeater. I tried to add tags like < b r/> and much more, but cant'find my mistake, how to get Spec to three different strings, not only one string. How to achive that?

I am not sure why you are joing the nodes with self, but as per your XML it is not required. You can simply project the elements like this:-
public static List<DetailList> GetDetails(XDocument xdocList)
{
DetailList d = new DetailList();
List<DetailList> DetailLists = (from product in xdocList.Descendants("Product")
select new DetailList
{
Detail1 = ((string)product.Attribute("id")),
Detail2 = ((string)product.Element("Title")),
Detail3 = ((string)product.Element("Description")),
Detail4 = ((string)product.Element("Image")),
Detail5 = product.Element("Specs")
.Elements("Spec")
.Select(x => (string)x).ToList(),
Detail6 = ((string)product.Element("Availability")),
Detail7 = ((string)product.Element("Price")),
}).ToList();
return DetailLists;
}
Since you need all the Specs separately, you should have a collection of string and not just string. So the datatype of property Detail5 should be:-
List<string> or string[]

Friend got solution from Rahul
Detail5 = String.Join("<br/>", outerProduct.Element("Specs").Elements("Spec").Select(x => (string)x).ToList()),
Thanks #Rahul Singh

Related

How can I use XML as a data source in WebAPI .NET

I'm having some problems with my WebAPI. I have followed the guide from Microsoft and it works: https://learn.microsoft.com/en-us/aspnet/web-api/overview/getting-started-with-aspnet-web-api/tutorial-your-first-web-api?fbclid=IwAR0tEMDMX3URn2YO0pwnTTAbY2RuGkdu-HUobznac4Lwus6rOVPSeiX-lFs
Now I want to be able to get data from my XML file instead of the hardcoded values the guide use. I have tried to search for this already but not sure I find the right thing. How can I do this?
Code I have tried:
public IEnumerable<Book> GetAllProducts()
{
XDocument doc = XDocument.Load("C:\\Users\\Name\\Desktop\\products.xml");
foreach (XElement element in doc.Descendants("Catalog")
.Descendants("Product"))
{
Product product = new Product();
product.Id = element.Element("Id").Value;
product.Name = element.Element("Name").Value;
product.Category = element.Element("Category").Value;
product.Added_Date = element.Element("Added_Date").Value;//this will not work because its not a string
product.Price = element.Element("Price").Value;//this will not work because its not a string
products.Add(product);
}
return products;
}
XML Code:
<?xml version="1.0"?>
<catalog>
<product id="P1">
<name>Royal Gala</name>
<category>Apple</category>
<country>New Zeeland</country>
<price>33.00</price>
<added_date>2011-01-11</added_date>
<description>This is a lovely red apple.</description>
</product>
<product id="P2">
<name>Granny Smith</name>
<category>Apple</category>
<country>Australia</country>
<price>33.00</price>
<added_date>2013-12-25</added_date>
<description>This is a lovely green apple.</description>
</product>
</catalog>
You just need to parse string to a specific type
Product product = new Product();
//..
product.Added_Date = DateTime.Parse(element.Element("Added_Date").Value);
product.Price = double.Parse(element.Element("Price").Value); //or float, or decimal
And consider using Elements method instead of Descendants cause last one returns all children and their inner children and so on and Elements returns first level children
var productNodes = doc.Root.Elements("product"); //note this is case sensitive
foreach (XElement element in productNodes) {
Product product = new Product();
//..
}

Retrieving Multiple Items from an XML File using LINQ to XML with C#

I'm new to LINQ to XML and I'm having problems writing C# to retrieve multiple items from an XML file, i.e. in the code sample below. I would like to go through the file and retrieve each OrderProduct id=??? and get the information in Quantities and Product. I can retrieve a single order only, but not if more than one is in the file.
This is the C# code I'm using which only retrieves the first order.
xelement = XElement.Load (orderXML);
IEnumerable<XElement> OrderXml = xelement.Elements ();
foreach (var order in OrderXml.Elements ("OrderProducts"))
{
m_productOrderID = order.Element ("OrderProduct").Attribute ("id").Value;
m_productName = order.Element ("OrderProduct").Element ("Product").Element ("Name").Value;
m_productCatalogNumber = order.Element ("OrderProduct").Element ("Product").Element ("CatalogNumber").Value;
m_productQuantity = order.Element ("OrderProduct").Element ("Quantities").Element ("NumberOfCopies").Value;
}
The XML file:
<?xml version="1.0" encoding="utf-16"?>
<OrderXml>
<Order>
<OrderProducts>
<OrderProduct id="569">
<Quantities>
<NumberOfRecipients>1</NumberOfRecipients>
<NumberOfCopies>1</NumberOfCopies>
<TotalUnits>1</TotalUnits>
</Quantities>
<Product id="444">
<Name>Product 1</Name>
<CatalogNumber>20130621-001</CatalogNumber>
</Product>
</OrderProduct>
<OrderProduct id="570">
<Quantities>
<NumberOfRecipients>1</NumberOfRecipients>
<NumberOfCopies>100</NumberOfCopies>
<TotalUnits>100</TotalUnits>
</Quantities>
<Product id="258">
<Name>Product 2</Name>
<CatalogNumber>20130621-002</CatalogNumber>
</Product>
</OrderProduct>
</OrderProducts>
</Order>
</OrderXml>
from op in xdoc.Descendants("OrderProduct")
let q = op.Element("Quantities")
let p = op.Element("Product")
select new {
Id = (int)op.Attribute("id"),
Quantities = new {
NumberOfRecipients = (int)q.Element("NumberOfRecipients"),
NumberOfCopies = (int)q.Element("NumberOfCopies"),
TotalUnits = (int)q.Element("TotalUnits")
},
Product = new {
Id = (int)p.Attribute("id"),
Name = (string)p.Element("Name"),
CatalogNumber = (string)p.Element("CatalogNumber")
}
}
Then getting single order product:
var orderProduct = query.FirstOrDefault(x => x.Id == yourId);
if (orderProduct != null)
// ...
Getting all ids:
var ids = xdoc.Descendants("OrderProduct")
.Select(op => (int)op.Attribute("id"));
BTW Next time provide code which you already have

Sorting XDocument elements based on InnerXML

I have an XML document similar to this:
<document>
<post>
<author>Bill Smith</author>
<subject>Test Article</subject>
<dates>
<uploaded>some date</uploaded>
<published>some date</published>
</dates>
<price>
<provider>Amazon</provider>
<cost>1540</cost>
</price>
<price>
<provider>WH Smith</provider>
<cost>2640</cost>
</price>
</post>
<post>
<author>Bill Smith</author>
<subject>Test Article</subject>
<dates>
<uploaded>some date</uploaded>
<published>some date</published>
</dates>
<price>
<provider>Amazon</provider>
<cost>1540</cost>
</price>
<price>
<provider>WH Smith</provider>
<cost>2640</cost>
</price>
</post>
</document>
I'm using XDocument w/ .NET 4.5. I know there are other methods I could use to sort this.
I have it working OK to pull each post and put it into a Post model. However, I would like to sort the price elements and pick out the lowest price (and also the provider) so I can insert it into my EF database.
Any help would be much appreciated, I'm totally stuck on where to even start with this.
You can give this a try:
XDocument doc = XDocument.Load(#"Data.xml");
var elements = doc.Root
.Elements("post")
.Select(post => new
{
Author = post.Element("author").Value,
Subject = post.Element("subject").Value,
Uploaded = Convert.ToDateTime(post.Element("dates").Element("uploaded").Value),
Published = Convert.ToDateTime(post.Element("dates").Element("published").Value),
Price = new
{
P = post
.Elements("price")
.OrderByDescending(price => Convert.ToDecimal(price.Element("cost").Value))
.Select(o => new
{
Provider = o.Element("provider").Value,
Cost = Convert.ToDecimal(o.Element("cost").Value)
})
.First()
}
});
var p = elements.First();
Based on code you provided, see, if this works:
var xmlDocumentElement = xDocument.Load("xmlData.xml").Root;
var posts = xmlDocumentElement.Elements("post").Select(post => new
{
Author = post.Element("author").Value.ToString(),
Subject = post.Element("subject").Value.ToString(),
AvailableDate = DateTime.Parse(post.Descendants("dates").FirstOrDefault().Value),
Price = GetPriceFromPriceXElement(post.Elements("price").Aggregate((prev, next) =>
Decimal.Parse(prev.Element("cost").Value.ToString()) <= Decimal.Parse(next.Element("cost").Value.ToString()) ? prev : next ))
}
);
public Price GetPriceFromPriceXElement(XElement price)
{
return new Price
{
Provider = price.Element("provider").Value.ToString(),
Cost = price.Element("cost").Value.ToString()
};
}
Try this. It should work. Once you've data, you can massage it. Please take care of NULL etc. This is just basic idea around your requirement.
XDocument doc = XDocument.Load(#"XMLFile1.xml");
var posts = doc.Root.Elements("post")
.Select(p =>
new
{
Author = p.Element("author").Value,
Price = p.Elements("price").OrderBy(a => decimal.Parse(a.Element("cost").Value)).First()
});
foreach(var a in posts)
{
Console.WriteLine(a.Author + " " + a.Price);
}
Here is a pretty straightforward attempt (and actually very similar to Ivan G's answer):
var posts = from post in document.Root.Elements("post")
select new
{
Author = post.Element("author").Value,
Subject = post.Element("subject").Value,
Uploaded = DateTime.Parse(post.Element("dates").Element("uploaded").Value),
Published = DateTime.Parse(post.Element("dates").Element("published").Value),
Price = (from price in post.Elements("price")
let cost = decimal.Parse(price.Element("cost").Value)
orderby cost
select new
{
Provider = price.Element("provider").Value,
Cost = cost
}).First()
};
It works fine for the sample data you gave us (except the dates "some date").
If the data might be incomplete or wrong (i.e. missing/misspelled nodes or wrong datatypes) I would suggest to just take each XElement instead of the values: Author = post.Element("author") etc. Maybe you want to allow posts without an uploaded or published date. This way you can inspect and/or cast/convert data later and maybe fix problems properly.

XDocument Descendants of Descendants

<Tickets>
<Extract_Date>2011-02-25 00:00:00</Extract_Date>
<Incidents>
<Ticket xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Ticket_Number>INC000000578057</Ticket_Number>
<Status>
<Value>Cancelled</Value>
<Reason>Cancelled by user</Reason>
</Status>
</Ticket>
I can get ticket_number OK with:
var q1 = from c in xmlDoc.Descendants("Ticket")
select new
{
Ticket_Number = (string)c.Element("Ticket_Number"),
};
How to get Reason also?
This should work:
var q1 = from c in xmlDoc.Descendants("Ticket")
select new
{
Ticket_Number = (string)c.Element("Ticket_Number"),
Reason = (string)c.Element("Status").Element("Reason")
};
//if you have exactly one <Ticket> with exactly one <Reason>
string strReason = xmlDoc.Descendants("Ticket").Single()
.Descendants("Reason").Single().Value;
//if you have one or multiple <Ticket> elements,
//each with exactly one <Reason> element
string[] astrReasons = xmlDoc.Descendants("Ticket")
.Select(ticket => ticket.Descendants("Reason").Single().Value).ToArray();
//if you have one or multiple <Ticket> elements,
//each with one or multiple <Reason> elements
string[] astrReasons2 = xmlDoc.Descendants("Ticket")
.SelectMany(ticket => ticket.Descendants("Reason")
.Select(reason => reason.Value)).ToArray();

LINQ to XML via C#

I'm new to LINQ. I understand it's purpose. But I can't quite figure it out. I have an XML set that looks like the following:
<Results>
<Result>
<ID>1</ID>
<Name>John Smith</Name>
<EmailAddress>john#example.com</EmailAddress>
</Result>
<Result>
<ID>2</ID>
<Name>Bill Young</Name>
<EmailAddress>bill#example.com</EmailAddress>
</Result>
</Results>
I have loaded this XML into an XDocument as such:
string xmlText = GetXML();
XDocument xml = XDocument.Parse(xmlText);
Now, I'm trying to get the results into POCO format. In an effort to do this, I'm currently using:
var objects = from results in xml.Descendants("Results")
select new Results
// I'm stuck
How do I get a collection of Result elements via LINQ? I'm particularly confused about navigating the XML structure at this point in my code.
Thank you!
This will return a IEnumerable of anonymous class:
var q = from result in xml.Descendants
select new
{
ID = result.Descendants("ID"),
Name= result.Descendants("Name"),
EmailAddress= result.Descendants("EmailAddress")
};
or if you have defined class `Result, e.g.:
class Result
{
public ID { get; set; }
public Name { get; set; }
public EmailAddress { get; set; }
}
then:
var q = from result in xml.Descendants
select new Result
{
ID = result.Descendants("ID"),
Name = result.Descendants("Name"),
EmailAddress = result.Descendants("EmailAddress")
};
(returns IEnumerable<Result>)
If your Results child elements are only Result elements, then you can get them like this:
var objects = from result in xml.Descendants
select result;
But in this lucky case you can just use xml.Descendants.
If it's not only Result elements, then this will do fine:
var object = from result in xml.Descendants
where result.Name == "Result"
select result;

Categories

Resources