tags:

views:

100

answers:

3

I'm new to Linq and I'm trying to query a XML document to find a list of account managers for a particular user. (I realize it might make more sense to put this in a database or something else, but this scenario calls for a XML document).

<user emailAddress='[email protected]'>
    <accountManager department='Customer Service' title='Manager'>[email protected]</accountManager>
    <accountManager department='Sales' title='Account Manager'>[email protected]</accountManager>
    <accountManager department='Sales' title='Account Manager'>[email protected]</accountManager>
</user>

I trying to create a list of objects (anonymous type?) with properties consisting of both XElement attributes (department, title) and values (email). I know that I can get either of the two, but my problem is selecting both.

Here is what I'm trying:

var managers = _xDoc.Root.Descendants("user")
               .Where(d => d.Attribute("emailAddress").Value == "[email protected]")
               .SelectMany(u => u.Descendants("accountManager").Select(a => a.Value));

foreach (var manager in managers)
{
     //do stuff
}

I can get at a.Value and a.Attribute but I can't figure out how to get both and store them in an object. I have a feeling it would wind up looking something like:

select new { 
    department = u.Attribute("department").Value,
    title = u.Attribute("title").Value,
    email = u.Value
};
+2  A: 

You are correct. It would look exactly like that.

For example:

_xDoc.Root.Descendants("user")
          .Where(d => d.Attribute("emailAddress").Value == "[email protected]")
          .SelectMany(u => u.Descendants("accountManager"))
          .Select(a => new { 
              department = a.Attribute("department").Value,
              title = a.Attribute("title").Value,
              email = a.Value
          });

EDIT: Using query comprehension syntax:

from u in _xDoc.Root.Descendants("user")
where u.Attribute("emailAddress").Value == "[email protected]"
from a in u.Descendants("accountManager")
select new { 
               department = a.Attribute("department").Value,
               title = a.Attribute("title").Value,
               email = a.Value
           });
SLaks
AH! I was so close. This is perfect, thank you.
Phil Scholtes
A: 

If you want to return anonymous type from a Select method you can write:

var managers = 
  _xDoc.Root.Descendants("user") 
       .Where(d => d.Attribute("emailAddress").Value == "[email protected]") 
       .SelectMany(u => 
          u.Descendants("accountManager")
           .Select(a => new {  
              Department = u.Attribute("department").Value, 
              Title = u.Attribute("title").Value, 
              Email = u.Value })
        );

The syntax for creating anonymous types isn't directly bound to the select keyword. You can use it for example like this:

var manager = new { Department = "Somewhere", Title = "Mr" };

So, the syntax a => new { ... } is a lambda expression that returns a new anonymous type, just like select new { ... } is a clause that constructs new anonymous type.

Regarding the choice between dot-notation and query syntax - this is really a personal preference (although some things look better with the query syntax and some methods such as Count can be used only using dot-notation). However, all queries all translated to method calls by the compiler.

Tomas Petricek
A: 

I assume that there's only a single user with sipAddress == sipUri?

Let's start with this:

var managers = _xDoc.Root

You can select just the first element that match by using .First

.First(d => d.Attribute("sipAddress").Value == sipUri)

This will gets the first user where the predicate returns true. Then, you'll want to extract both the attributes and the values in the element into a new object (anonymous type).

If there're many users and you want all the listed managers, this can be done by using .SelectMany (i.e. for each users, there're many managers, select all of them) but in this case I assume there's only one, and .First has returned that very element so you can just use its properties to access the list of managers

.Descendants("accountManager")

Then you'll get all the XElement descending from the selected element. Transforming it into another object by using the values from the elements is done by using a mapping function which, in Linq, is .Select

.Select(managerElem => new {
    department = u.Attribute("department").Value,
    title = u.Attribute("title").Value,
    email = u.Value 
});

As I've said earlier, if there're multiple users with the correct sipAddress and you want all the managers, just wrap the .Descendants inside .SelectMany.

.SelectMany(userElem => userElem.Descendants("accountManager"))

I havn't tested the code but I believe you'll be able to figure it out by following what I've written.

Hope this helps! :) Feel free to ask in the comments if you're still confused.

chakrit
Thanks for the detailed explanation, great help. I'm still learning.
Phil Scholtes