Reputation: 308
I am trying to get the dictionary of distinct values using LINQ. I have tried using this:
var roleRefList =
xDocument.Root.Descendants()
.Where(x => x.Name.LocalName.Equals("roleRef") &&
!string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) &&
!string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
.Select(l => new {
roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
})
.Distinct()
.ToDictionary(a => a.roleUri);
The problem here is that when there are duplicate entries in the roleUri
then an error occurs.
I am parsing the XML document and making a dictionary of xElement attributes roleUri
and roleref
if they are present in the xElement.
The other workaround is using a for loop:
Dictionary<string, string> roleRefList = new Dictionary<string, string>();
foreach (XElement element in xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef")))
{
string roelUri = Convert.ToString(element.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")));
string href = Convert.ToString(element.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")));
if (!string.IsNullOrEmpty(roelUri) && !string.IsNullOrEmpty(href) && !roleRefList.ContainsKey(roelUri))
{
roleRefList.Add(roelUri, href);
}
}
but I want to implement this using LINQ.
Upvotes: 4
Views: 8291
Reputation: 236228
Duplicate attributes are not allowed in XML. If you will have two roleURI
attributes in roleRef
element then you will get exception during XDocument loading:
'roleURI' is a duplicate attribute name. Line 42, position 42.
So, actually your code should look like this:
var xdoc = XDocument.Load("foo.xml");
XNamespace ns = "http://www.adventure-works.com"; // put your namespace here
Dictionary<string, string> roleRefList =
xdoc.Root.Descendants(ns + "roleRef")
.Select(r => new {
Uri = (string)r.Attribute("roleURI"),
Href = (string)r.Attribute("href")
})
.Where(r => !String.IsNullOrEmpty(r.Uri) && !String.IsNullOrEmpty(r.Href))
.ToDictionary(r => r.Uri, r => r.Href);
Result will be same as with your for loop. Sample xml:
<root xmlns="http://www.adventure-works.com">
<roleRef/>
<roleRef roleURI=""/>
<roleRef href=""/>
<roleRef roleURI="" href=""/>
<roleRef roleURI="a" />
<roleRef roleURI="" href="b"/>
<roleRef roleURI="c" href="d"/>
</root>
Upvotes: 1
Reputation: 2099
Couple of ways to achieve this:
1) You can call the distinct method using a custom equality comparer. In order to achieve this you will need to write a class which will contain the roleUri and href fields first. Something like:
public class AttributePair
{
public string RoleUri {get; set;}
public string Href {get; set;}
}
Next step is writing the equality comparer for your class:
public class AttributePairComparer : IEqualityComparer<AttributePair>
{
public bool Equals(AttributePair x, AttributePair y)
{
return x.RoleUri.Equals(y.RoleUri);
}
public int GetHashCode(AttributePair obj)
{
return obj.RoleUri.GetHashCode();
}
}
And after that we'll just pass the instance of the equality comparer to the distinct method:
var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
.Select(l => new AttributePair
{
RoleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
Href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
}).Distinct(new AttributePairComparer()).ToDictionary(a => a.RoleUri, a => a.Href);
I agree that this solution is quite complicated, but this is a classic way of achieving the needed result, so to speak.
2) Another solution is a DistinctBy method from MoreLinq. Where you can pass a lambda expression as a parameter:
var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
.Select(l => new
{
roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
}).DistinctBy(p => p.roleUri).ToDictionary(a => a.roleUri, a => a.href);
3) The chief architect of C#, Anders Hejlsberg, suggested the solution which uses GroupBy. You can read about it here.
Upvotes: 0
Reputation: 125630
You could write your own Distinct
method that would take Func<T,TKey>
as an argument. You can find example of that here: Distinct list of objects based on an arbitrary key in LINQ
With that method you should be able to write:
var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
.Select(l => new
{
roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
}).Distinct(l => l.roleUri).ToDictionary(a => a.roleUri);
Update
Or you can use grouping:
var roleRefList = xDocument.Root.Descendants().Where(x => x.Name.LocalName.Equals("roleRef") && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")))) && !string.IsNullOrEmpty(Convert.ToString(x.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")))))
.Select(l => new
{
roleUri = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("roleURI")).Value,
href = l.Attributes().FirstOrDefault(a => a.Name.LocalName.Equals("href")).Value
})
.GroupBy(l => l.roleUri)
.ToDictionary(g => g.Key, g => g.FirstOrDefault());
Upvotes: 9