C# XML Fetching - c#

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();

Related

How to look at multiple conditions with a LINQ statement

I'm currently using a linq statement to move items from one list to another based of a condition, but i need to add a second condition and i think i'm having a syntax issue
What I'm currently doing:
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber == SkillNumber)
.ToList();
Generatorlist.CallDataGeneratorsDone = Generatorlist.CallDataGeneratorsDone
.Except(selected)
.ToList();
Generatorlist.CallDataGeneratorsNotDone.AddRange(selected);
What I'm trying to do:
var selected = Generatorlist.CallDataGeneratorsDone
.Where((item => item.SkillNumber) & (item => item.CallServer))
.ToList();
Generatorlist.CallDataGeneratorsDone = Generatorlist.CallDataGeneratorsDone
.Except(selected)
.ToList();
Generatorlist.CallDataGeneratorsNotDone.AddRange(selected);
I'm having an issue because you can't us & in a Lambda expression. I'm looking for some assistance in figuring out a way to accomplish this.
You just need to remove the second declaration of item => (it only needs to be defined once):
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber && item.CallServer)
.ToList();
You can chain Where calls together like so...
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber)
.Where(item => item.CallServer)
.ToList();
If I don't miss something, it should be && instead of & in the same expression like this:
var selected = Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber && item.CallServer)
.ToList();
This is assuming that both SkillNumber and item.CallServer are of Boolean. Otherwise you will need to write acomplete predicate like item.SkillNumber = 'blah blah'.
The solution in your case would be:
Generatorlist.CallDataGeneratorsNotDone
.AddRange(Generatorlist.CallDataGeneratorsDone
.Except(Generatorlist.CallDataGeneratorsDone
.Where(item => item.SkillNumber && item.CallServer))
.ToList())

C# LINQ getting duplicates from a list

I have a list of objects that have a string, and int and another int.
I want to be able to create a list of all the objects that have a duplicate string.
Here is what I have so far:
MyObject duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.ToList();
The error I am getting is that I cannot implicitly convert the type System.Collections.Generic.List<string, MyObject> to MyObject
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.SelectMany(g=>g)
.ToList();
you need to write
List<MyObject> duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.ToList();
You could use ToLookup to make a nice data structure with all the info you need
var objectsByString = allMyObjects.ToLookup(o => o.MyString);
This will return a Lookup<string, MyObject>. You can get the duplicate strings using:
var duplicateStrings = objectsByString.Where(l => l.Count()>1).Select(l => l.Key);
which will return a IEnumerable<string> with the duplicate strings. And, for each duplicate you can access the actual objects that have duplicates using something like this:
string duplicateKey = duplicateStrings.First();
var duplicateObjects = objectsByString[duplicateKey]
which returns a IEnumerable<MyObject> with the items that have that string.
There are several problem, the first is a List-of-MyObject cannot be assigned to MyObject, so let's use var to ignore this for a second.
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.ToList();
Now, the type of duplicates is List<IGrouping<string, MyObject>> (despite the incorrectly reported error message). Whoops, gotta get rid of (or write to code to account for) the groups!
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.SelectMany(g => g)
.ToList();
Now the type of duplicates is List<MyObject>, after having selected every ("selected many") object from every group with more than one item. Better, but this still isn't an MyObject. Well, that's fine: fix the declared type of the variable (that var was previously automatically doing)..
List<MyObject> duplicates = /* same as before */;
Or leave var to do it's thing and if an IEnumerable<MyObject> is fine, simply omit the ToList:
var duplicates = allMyObjects.GroupBy(a => a.MyString)
.Where(a => a.Count() > 1)
.SelectMany(g => g);
Go forth and iterate thy duplicates!

LINQ return type/value is not what im awaiting

When i execuse these lines
drpdf["meno"] = matches.Cast<Match>().Where(c => c.Groups["ID"].Value == i.ToString()).Select(c => c.Groups["meno"].Value);
drpdf["info"] = matches.Cast<Match>().Where(c => c.Groups["ID"].Value == i.ToString()).Select(c => Regex.Replace(c.Groups["zvysok"].Value, #"^,\s?", string.Empty));
it wont save into DataRow value that i want, instead of
System.Linq.Enumerable+WhereSelectEnumerableIterator`2[System.Text.RegularExpressions.Match,System.String]
Can you help me pls how to select/cast return value to the readable type? Thanks anyway. Ondro
Your LINQ queries use Select, so you get an IEnumerable<T> back. If you want the result of your LINQ query, and are expecting exactly one result, add .Single():
drpdf["meno"] = matches.Cast<Match>()
.Where(c => c.Groups["ID"].Value == i.ToString())
.Select(c => c.Groups["meno"].Value)
.Single();
On the other hand, if your query can have multiple results, you should use .First() instead to take the first result. At that point, however, it depends what your scenario is and what you're trying to capture.
Something like:
matches.Cast<Match>()
.Where(c => c.Groups["ID"].Value == i.ToString())
.Select(c => c.Groups["meno"].Value)
.FirstOrDefault(); // this expression will evaluate the linq
// expression, so you get the string you want
Please note: You should only use FirstOrDefault or SingleOrDefault if null is actually a valid value in your context. (like said by #Daniel Hilgarth).
If null is not a valid result and instead you want an empty string, append a ?? String.Empty to the expression:
matches
...
.FirstOrDefault() ?? String.Empty;
The result of your queries are enumerable objects. Calling ToString() on these doesn't give you a meaningful string representation as you have already noticed. You need to generate a string appropriate for display.
If you simply want to display the contents as a comma-separated list, you can use String.Join() to do this:
var menos = matches.Cast<Match>()
.Where(c => c.Groups["ID"].Value == i.ToString())
.Select(c => c.Groups["meno"].Value);
drpdf["meno"] = String.Join(", ", menos);
Otherwise if you intended to select a single result, use Single() to select that single string result.

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.

c# linq to xml to list

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;

Categories

Resources