c# linq to xml to list - c#

I was wondering if there is a way to get a list of results into a list with linq to xml. If I would have the following xml for example:
<?xml version="1.0"?>
<Sports xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SportPages>
<SportPage type="test">
<LinkPage>
<IDList>
<string>1</string>
<string>2</string>
</IDList>
</LinkPage>
</SportPage>
</SportPages>
</Sports>
How could I get a list of strings from the IDList?
I'm fairly new to linq to xml so I just tried some stuff out, I'm currently at this point:
var IDs = from sportpage in xDoc.Descendants("SportPages").Descendants("SportPage")
where sportpage.Attribute("type").Value == "Karate"
select new
{
ID = sportpage.Element("LinkPage").Element("IDList").Elements("string")
};
But the var is to chaotic to read decently. Isn't there a way I could just get a list of strings from this?
Thanks

This query works - tested and verified:
var ID2 = (from sportpage in xDoc.Descendants("SportPages").Descendants("SportPage")
where sportpage.Attribute("type").Value == "Karate"
select sportpage)
.Descendants("LinkPage")
.Descendants("IDList")
.Elements("string")
.Select(d => d.Value)
.ToList();
Gives me a list of two strings, "1" and "2".

var myStrings = xDoc.Descendants("SportPage")
.Where(d => d.Attribute("type").Value == "Karate")
.Descendants("IDList")
.Descendants("string")
.Select(d => d.Value);
to see your string:
xDoc.Descendants("SportPage")
.Descendants("IDList")
.Where(d => d.Attribute("type").Value == "Karate")
.Descendants("string")
.Select(d => d.Value)
.ToList()
.ForEach(Console.WriteLine);

Do you mean this?
List<string> IDs = xDoc.Descendants("SportPages").Descendants("SportPage")
.Where( anySportPage => anySportpage.Attribute("type").Value == "Karate" )
.Select( karateSportPage => karateSportpage.Element("LinkPage").Element("IDList").Elements("string"))
.ToList();

I think the reason you find the "var" chaotic is your creation of the anonymous type with the "new" in your select. If you just select the one item you're after then the var will not be an anonymous type.
e.g.
select sportpage.Element("LinkPage").Element("IDList").Elements("string");
However, my preference would be to do that using the . notation like this.
List<string> ids = xDoc.Elements("SportPages").Elements("SportPage").Where(sportPage => sportPage.Attribute("type").Value == "Karate").Elements("LinkPage").Elements("IDList").Elements("string").Select(id => id.Value).ToList();

The biggest issue you were having was that you didn't grab the .Value from the returned element set. But here's another way to do it.
var ids = from sportPage in xDoc.Descendants("SportPage")
let attrib = sportPage.Attribute("type")
where attrib != null
let type = attrib.Value
where !string.IsNullOrEmpty(type)
&& type == "Karate"
from id in sportPage.Descendants("IDList").Elements()
select id.Value;

Related

How do I select all distinct strings in a list of lists of another type?

I'm still very new with LINQ. I have the following "simplified" data structure:
List<List<Field>> myData = new List<List<Field>>();
Field consists of two string members, Type and Name.
My goal is to get a List<string> containing all distinct Name corresponding to a given Type. My first approach is this:
var test = myData
.Where(a => a.FindAll(b => b.Type.Equals("testType"))
.Select(c => c.Name)
.Distinct());
Does somebody have a hint for me? =)
You just need to use SelectMany to flatten your list of lists and then proceed as normal
var test = myData.SelectMany(x => x)
.Where(x => x.Type == "testType")
.Select(x => x.Name)
.Distinct()
.ToList();
Or in query syntax
var test = (from subList in myData
from item in subList
where item.Type == "testType"
select item.Name).Distinct().ToList();
Another way to do it using query notation:
var test= from list in myData
from e in list
where e.Type=="testType"
group e.Name by e.Name into g
select g.Key;
But is better go for one of the #juharr's solutions

Linq query to select all numbers that match

I have some difficulties to query a table. In the table there are a column for ResourceID and a column for ProjectID. I want to get a list of all int numbers from the ResourceID where ProjectID match the projId number. Is this possible?
I have tested, but I guess this is wrong. I thought I could get the value by using the resources variable, but that isn't working.
var resources = db.Activities.Where(x => x.ProjectID == projId).ToList();
resources. ????
You could accomplish this via a Select() query to only pull the specific properties that you need (in this case the ResourceID property) for each of the elements within your collection :
// Get your resources that meet your requirement
var resources = db.Activities.Where(x => x.ProjectID == projId).ToList();
// Get your Resource IDs from your previous query
var resourceIds = resources.Select(r => r.ResourceID);
You could actually just perform this in a single call if you preferred :
// This will return a list of ResourceIDs that match
var resourceIds = db.Activities.Where(a => a.ProjectID == projId)
.Select(a => a.ResourceID)
.ToList();
I believe the answer is:
var resources = db.Activities.Where(x => x.ProjectID == projId).Select(x => x.ResourceID).ToList();
You're getting a list of the Activities. What you need to do is to select the item you want--in this case the ResourceID column.
var resources = db.Activities.Where(x => x.ProjectID == projId)
.Select(x => x.ResourceID)
.Distinct() // Thought you might want this
.ToList();
You can also use a different format like this, which can sometimes be clearer.
var resources = (from a in db.Activities
where a.ProjectID == projId
select a.ResourceID).Distinct().ToList();
Please update your query if you want to get list of int..
var resources = db.Activities.Where(x => x.ProjectID == projId).Select(x => x.ResourceID).ToList();

Linq IEnumerable<IGrouping<string, Class>> back to List<Class>

How can I turn the following statement back to List<DocumentData>
IEnumerable<IGrouping<string, DocumentData>> documents =
documentCollection.Select(d => d).GroupBy(g => g.FileName);
the goal is to get List that should be smaller than documentCollection.
FileName contains duplicates so I want to make sure I don't have duplicate names.
I have also tried the following but it's still providing me with duplicate file names
documentCollection =
documentCollection.GroupBy(g => g.FileName).SelectMany(d => d).ToList();
Each IGrouping<string, DocumentData> is an IEnumerable<DocumentData>, so you could simply call SelectMany to flatten the sequences:
var list = documents.SelectMany(d => d).ToList();
Edit: Per the updated question, it seems like the OP wants to select just the first document for any given filename. This can be achieved by calling First() on each IGrouping<string, DocumentData> instance:
IEnumerable<DocumentData> documents =
documentCollection.GroupBy(g => g.FileName, StringComparer.OrdinalIgnoreCase)
.Select(g => g.First())
.ToList();
You haven't said what T should stand for in List<T> you're looking for, so here are couple the most likely to be desired:
List<DocumentData> - rather pointless as you already have that on documentCollection
var results = documents.SelectMany(g => g).ToList();
List<KeyValuePair<string, List<DocumentData>>
var results =
documents.Select(g => new KeyValuePair(g.Key, g.ToList())).ToList();
List<string> - just the names
var results = documents.Select(g => g.Key).ToList();
List<IGrouping<string, DocumentData>>
var results = documents.ToList();

C# XML Fetching

My XML looks so:
<sensor>
<data>26.7</data>
<data>54.53</data>
<log>false</log>
</sensor>
To retrieve the data fields in a list, I use:
List<string> list = xml.Root.Descendants("sensor").Elements("data").Select(element => element.Value).ToList();
And it works well.
Sometimes the XML looks like this (log = true):
<sensor>
<data>26.7</data>
<data>54.53</data>
<log>true</log>
</sensor>
And I want to ignore these values. How can I achieve this?
I tried this:
var lastUser = xml.Element("response").Descendants("sensor")
.First(u => u.Element("data") != null
&& u.Element("log").Value == "false");
But I only can retrieve of course only the first value.
Why do you use First if you don't want to select the first element? Use Where instead.
var data = xml.Element("response")
.Descendants("sensor")
.Where(x => ((string)x.Element("log")) == "false")
.Elements("data")
.Select(x => x.Value)
.ToList();
Why not use .Where instead of .First and you should be getting an array back?
Use casting nodes to bool or to double:
var data= xml.Element("response").Descendants("sensor")
.Where(s => (bool)s.Element("log"))
.Elements("data")
.Select(d => (double)d)
.ToList(); // produces List<double>
And yes #Daniel is right - First returns only first element from sequence which matches your conditions.
Also consider to use XPath
var data = xdoc.XPathSelectElements("response/sensor[log='true']/data")
.Select(d => (double)d)
.ToList();

Return nested alias for linq expression

I have the following Linq Expression
var tooDeep = shoppers
.Where(x => x.Cart.CartSuppliers.First().Name == "Supplier1")
.ToList();
I need to turn the name part into the following string.
x.Cart.CartSuppliers.Name
As part of this I turned the Expression into a string and then split on the . and removed the First() argument. However, when I get to CartSuppliers this returns a Suppliers[] array. Is there a way to get the single type from this. eg. I need to get a Supplier back.
Update: Got it to work
var fullName = m.ToString().Split(new[] { '.' }, StringSplitOptions.RemoveEmptyEntries);
// this supports the "deep graph" name - "Product.Address.City"
var fixedName = fullName.Skip(1).Take(fullName.Length - 1)
.Where(x => x != "First()")
.Select(x => System.Text.RegularExpressions.Regex.Replace(x, #"\[[0-9]+\]",""))
.ToArray();
with this:
var prop = property.PropertyType.HasElementType ? property.PropertyType.GetElementType() property.PropertyType;
which enabled be to find the individual type from an array.
Thanks
firstSupplierWithNeededName = shoppers
.SelectMany(s => s.Cart.CartSuppliers)
.First(s => s.Name == "Supplier1");
But also look into using FirstOrDefault or Single if it has to return just one.

Categories

Resources