Linq query on XML to Select multiple elements of subnodes - c#

I want to select all distinct values of child from following xml
<root>
<parent>
<child>value 1</child>
<child>value 2</child>
</parent>
<parent>
<child>value 1</child>
<child>value 4</child>
</parent>
</root>
I tried following:
var vals = (from res in XmlResources.Elements("root").Elements("parent") select res)
.SelectMany(r => r.Elements("child")).Distinct().ToList();
But can't get the value from it, gives me value wrapped in tag and not Distinct
Is it possible to show both ways to get it - query and chaining aka lambda.

yes it is possible both ways
var doc = new XDocument("your xml string");
var values = (from c in doc.Root.Descendants("child") select c.Value).Distinct();
//chaining style
var values = doc.Root.Descendants("child").Select(c=>c.Value).Distinct();

You're selecting the elements, and the elements are all distinct. You need to get the distinct values. For example:
var values = XmlResources.Element("root")
.Elements("parent")
.Elements("child")
.Select(x => x.Value)
.Distinct();
There's really no benefit in using a query expression here - it only adds cruft. I only use a query expression when the query has multiple aspects to it (e.g. a where and a meaningful select, or a join). For just a select or just a where it's pretty pointless. So yes, you can use:
var values = (from x in XmlResources.Element("root")
.Elements("parent")
.Elements("child")
select x.Value).Distinct();
... but why would you? It's a lot less clear IMO.
Note that if you don't care too much about the root/parent/child hierarchy, and are happy to just get all the child descendants, you can use:
var values = XmlResources.Descendants("child")
.Select(x => x.Value)
.Distinct();

Related

Use LINQ to get only the most recent JOINed item for each element

I have a LINQ query:
Elements.Join(ElementStates,
element => element.ElementID,
elementState => elementState.ElementID,
(element , elementState ) => new { element, elementState })
OK, so each Element has an ElementState associated to it. However there can be multiple states for each element for historical purposes, marked by a DateModified column. In this query, I would like to return only the most recent ElementState for each Element.
Is such a thing possible, using LINQ?
EDIT:
Credit to Gilad Green for their helpful answer.
I have converted it to Method syntax for anyone else who would like to see this in the future:
Elements.GroupJoin(ElementStates,
element => element.ElementID,
elementState => elementState.ElementID,
(element, elementState) =>
new { element, elementState = elementState.OrderByDescending(y => y.DateModified).FirstOrDefault() });
You can use GroupJoin instead of Join and then retrieve the first record after ordering the group by the DateModified:
var result = from e in Elements
join es in ElementStates on e.ElementID equals es.ElementID into esj
select new {
Element = e,
State = esj.OrderByDescending(i => i.DateModified).FirstOrDefault()
};
The same can be implemented with method syntax instead of query syntax but in my opinion this is more readable
For the difference between simply joining and group joining: Linq to Entities join vs groupjoin

Linq - check from list some thing like in used in to query on set in SQL

List<Guid?> MobileAppID = new List<Guid?>();
return d
.Where(x => x.MarketplaceID IN MobileAppID)
.ToList();
I want to select values from the d where MarketplaceID in MobileAppID.MobileAppID is a set
How would i do that in LINQ C#. Something like select in query in SQL Server
d is a class containing MarketplaceID
The equivalent would be a Contains query:
return d
.Where(x => MobileAppID.Contains(x.MarketplaceID))
.ToList();
Might want to make MobileAppID a HashSet to speed this up.

Use LINQ to convert comma separated strings in a table into a distinct collection of values

I'm working through this MVC3 tutorial and have entered the genre of a film as a comma separated string.
In part 6 we take the genres from the table to populate a drop down list.
I'd like to populate the drop down list with a distinct collection of single genres but I just can't get it to work.
This is what the tutorial suggest as a start point
var GenreLst = new List<string>();
var GenreQry = from d in db.Movies
orderby d.Genre
select d.Genre;
GenreLst.AddRange(GenreQry.Distinct());
... and this is where I'd got to
var GenreLst = new List<string>();
var GenreQry = (from d in db.Movies
orderby d.Genre
select d.Genre ).Select(s=>s.Split(','))
.Distinct();
GenreLst.AddRange( GenreQry );
Linq2Sql doesn't know s.Split(',') method, so it should throw an exception, you can do this:
var GenreQry = (from d in db.Movies
orderby d.Genre
select d.Genre ).Distinct().ToList();
GenreLst.AddRange( GenreQry.SelectMany(x=>x.Split(',')).Distinct());
about above code:
When calling ToList() in the end of query, your data will be fetched and your query in fact is list,
in second part, SelectMany flats separated strings as a IEnumberable of strings.
Edit: Also in first part you can call .AsEnumerable() instead of .ToList() for fetching data, it seems better way.
In case you find the SelectMany syntax a bit confusing, consider the following (which compiles into a select many method call under the covers but I find easier to read):
var GenreQry = (from d in db.Movies.AsEnumerable()
from s in d.Split(',')
select s)
.Distinct()
.OrderBy(s => s);

Reorder using Join in Linq

I want my XElements in document order.
Can I reorder my xpathGroups using Join like in this example?
XDocument message_doc = XDocument.Load(message);
var xpathGroups =
from e in contextErrors
group e by SelectElement(message_doc, e.XPath) into g
select new
{
Element = g.Key,
ErrorItems = g,
};
var documentOrderedGroups =
from elem in message_doc.Root.DescendantsAndSelf()
join e in xpathGroups on elem equals e.Element
select e;
Message:
<root>
<a>
<aa>
<aaa>9999</aaa>
</aa>
<aa>
<aaa>8888</aaa>
</aa>
</a>
<b>
<bb>
<bbb>7777</bbb>
</bb>
</b>
<c>
<cc>
<ccc>6666</ccc>
</cc>
</c>
</root>
Input data:
/root/c[1]/cc[1]/ccc[1]
/root/a[1]/aa[2]/aaa[1]
/root/b[1]/bb[1]/bbb[1]
/root/a[1]/aa[1]/aaa[1]
Expected result:
/root/a[1]/aa[1]/aaa[1]
/root/a[1]/aa[2]/aaa[1]
/root/b[1]/bb[1]/bbb[1]
/root/c[1]/cc[1]/ccc[1]
Your original queries work, and the result is an object with the element and its relevant XPath query in document order. However, the result conflicts with the comment you made that you only want the elements in document order.
Elements and XPath: if you want both the element and its XPath then the join will remain as part of the query but I would replace the grouping with a projection into an anonymous type.
var xpathElements = contextErrors.Select(e => new
{
Element = message_doc.XPathSelectElement(e.XPath),
XPath = e.XPath
});
var ordered = from e in message_doc.Descendants()
join o in xpathElements on e equals o.Element
select o;
Elements only: if you only want the elements to be in document order, the following approach would work as well.
var xpathElements = contextErrors.Select(e => message_doc.XPathSelectElement(e.XPath));
var ordered = message_doc.Descendants()
.Where(e => xpathElements.Any(o => e == o));
In both examples I've used the XPathSelectElement method to take the place of your
SelectElement method, which I gather has the same purpose.

Linq: using StringComparer with GroupBy/Distinct in query syntax

I have this (XLinq) query and was wondering how to convert it to the query syntax:
var grouped = doc.Descendants()
.GroupBy(t => t.Element(ns + "GroupingAttr").Value, StringComparer.OrdinalIgnoreCase);
This is the query syntax without the StringComparer:
var grouped = from t in doc.Descendants()
group t by t.Element(ns + "GroupingAttr").Value into group
select group
My groupby is a little more complicated than this, so I prefer to use the key of the group instead of introducing a new property.
This is what I tried, but doesn't work because the let "key" is not available in the context of the select (I've uses my more complicated key definition to illustrate the fact I don't want to repeat this in the select):
var grouped = from t in doc.Descendants()
let key = ((t.Name != ns + "SomeElementName") ? t.Element(ns + "SomeAttribute") : t.Element(ns + "SomeOtherAttribute")).ElementValueOrDefault("Empty group")
group t by key.ToUpper() into g
select new { Name = key, Items = g };
In the end, case-sensitivity was not important because I could presume that all casings were the same...
Related question: LINQ Distinct operator ignore case?
I don't think you can use the comparer within the query syntax, however you could call ToUpper on your value. This will then ignore case for you. As a side note using ToUpper is more efficient than using ToLower, so ToUpper would be the way to go.
The C# team were very sparse with what they introduced into the query syntax, so for anything like this you'll have to use the extension methods syntax.
var grouped = from t in doc.Descendants()
group t by t.Element(ns + "GroupingAttr").Value into MyGroup
select MyGroup.Key

Categories

Resources