开发者

selection using LINQ

I have a sample xml file that looks like this:

<Books>
   <Category Genre="Fiction" BookName="book_name" BookPrice="book_price_in_$" />
   <Category Genre="Fiction" BookName="book_name" BookPrice="book_price_in_$" />
   <Category Genre="NonFiction" BookName="book_name" BookPrice="book_price_in_$" />
   <Category Genre="Children" BookName="book_name" BookPrice="book_price_in_$" />
</Books>  

I need to collect all book names and book prices and pass to some other method. Right now, i get all book names and book prices seperately into two different List<string> using the following command:

List<string>BookNameList = root.Elements("Cat开发者_JAVA技巧egory").Select(x => (string)x.Attribute("BookName")).ToList();
List<string>BookPriceList = root.Elements("Category").Select(x => (string)x.Attribute("BookPrice")).ToList();

I create a text file and send this back to the calling function (stroing these results in a text file is a requirement, the text file has two fields bookname and bookprice).

To write to text file is use following code:

for(int i = 0; i < BookNameList.Count; i++)
{
   //write BookNameList[i] to file
   // Write BookPriceList[i] to file
}

I somehow dont feel good about this approach. suppose due to any reason both lists of not same size. Right now i do not take that into account and i feel using foreach is much more efficient (I maybe wrong). Is it possible to read both the entries into a datastructure (having two attributes name and price) from LINQ? then i can easily iterate over the list of that datastructure with foreach.

I am using C# for programming.

Thanks,

[Edit]: Thanks everyone for the super quick responses, i choose the first answer which I saw.


Selecting:

var books = root.Elements("Category").Select(x => new {
    Name = (string)x.Attribute("BookName"), 
    Price = (string)x.Attribute("BookPrice")
}).ToList();

Looping:

foreach (var book in books)
{
    // do something with
    // book.Name
    // book.Price
}


I think you could make it more tidy by some very simple means.

A somewhat simplified example follows.

First define the type Book:

public class Book
{
   public Book(string name, string price)
   {
      Name = name;
      Price = price;
   }

   public string Name { get; set; }
   public string Price { get; set; } // could be decimal if we want a proper type.
}

Then project your XML data into a sequence of Books, like so:

var books = from category in root.Elements("Category")
            select new Book((string) x.Attribute("BookName"), (string) x.Attribute("BookPrice"));

If you want better efficiency I would advice using a XmlReader and writing to the file on every encountered Category, but it's quite involved compared to your approach. It depends on your requirements really, I don't think you have to worry about it too much unless speed is essential or the dataset is huge.

The streamed approach would look something like this:

using (var outputFile = OpenOutput())
using (XmlReader xml = OpenInput())
{
   try
   {
       while (xml.ReadToFollowing("Category"))
       { 
           if (xml.IsStartElement())
           {
               string name = xml.GetAttribute("BookName");
              string price = xml.GetAttribute("BookPrice");

              outputFile.WriteLine(string.Format("{0} {1}", name, price));
          }
      }
   }
   catch (XmlException xe)
   {
        // Parse error encountered. Would be possible to recover by checking
        // ReadState and continue, this would obviously require some 
        // restructuring of the code.
        // Catching parse errors is recommended because they could contain
        // sensitive information about the host environment that we don't
        // want to bubble up.
        throw new XmlException("Uh-oh");
   }       
}

Bear in mind that if your nodes have XML namespaces you must register those with the XmlReader through a NameTable or it won't recognize the nodes.


You can do this with a single query and a foreach loop.

var namesAndPrices = from category in root.Elements("Category")
                     select new
                     {
                        Name = category.Attribute("BookName").Value,
                        Price = category.Attribute("BookPrice").Value
                     };

foreach (var nameAndPrice in namesAndPrices)
{
    // TODO: Output to disk
}


To build on Jeff's solution, if you need to pass this collection into another function as an argument you can abuse the KeyValuePair data structure a little bit and do something along the lines of:

var namesAndPrices = from category in root.Elements("Category")
                     select new KeyValuePair<string, string>(
                        Name = category.Attribute("BookName").Value,
                        Price = category.Attribute("BookPrice").Value
                     );

// looping that happens in another function
// Key = Name
// Value = Price
foreach (var nameAndPrice in namesAndPrices)
{
    // TODO: Output to disk
}
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜