dealing with Linq XML and complex queries
this is quite a brainteaser to me:
Assume i have XML data similar to the following:
<?xml version="1.0"?>
<site>
<open_auction id="open_auction0">
<initial>25.39</initial>
<reserve>67.91</reserve>
<bidder>
<date>09/27/1999</date>
<time>18:47:09</time>
<personref person="person308"/>
<increase>18.00&开发者_StackOverflowlt;/increase>
</bidder>
<bidder>
<date>06/03/2000</date>
<time>17:47:49</time>
<personref person="person942"/>
<increase>19.50</increase>
</bidder>
<bidder>
...
</bidder>
<current>202.39</current>
<itemref item="item2"/>
<seller person="person573"/>
<annotation>
<author person="person128"/>
<description>
<parlist>
<listitem>
<text>
niece <keyword> till banqueting little unkindly </keyword>
</text>
</listitem>
<listitem>
<text>
emilia jack standards
</text>
</listitem>
</parlist>
</description>
<happiness>1</happiness>
</annotation>
<quantity>1</quantity>
<type>Regular</type>
<interval>
<start>12/23/2001</start>
<end>11/11/2000</end>
</interval>
</open_auction>
<open_auction id="open_auction1">
...
</open_auction>
<open_auction id="open_auction2">
...
</open_auction>
</site>
you can download a complete and much bigger (about 5MB) test-file from www.megamatz.de/auction.xml
I need to determine the IDs of all open_auctions where a certain given person (Person1) issued a bid before another given person (Person2). "Before" in the meaning of the the "bidder"node of Person1 occures before the "bidder"node of Person2 within one open_auction, so its not concerning date or time.
Here is the XPath-Query for this problem maybe it explains the problem better:
let $auction := doc("auction.xml") return
for $b in $auction/site/open_auctions/open_auction
where
some $pr1 in $b/bidder/personref[@person = "person20"],
$pr2 in $b/bidder/personref[@person = "person51"]
satisfies $pr1 << $pr2
return <history>{$b/reserve/text()}</history>
I know somehow that i need some kind of nested queries, but I can´t get it straight with this Linq-concept and these XElement(s). So I am not only interested in a solution but also understanding the concept espacially of any(), contains() and what else there is to cope with queries like these with linq.
I am really desperate and need some help, I have been searching an trying the whole night long, but I simply can´t get it straight and nothing works.
Edited to add
I am sorry to ask again, but no, it doesn´t work. I have tried and tried, which was very good, because I have learned a lot of things. I have the feeling I am nearly finding the answer, but when it comes to the "ElementAfterSelf" there is something going on where I could use a little hint and explanaition in the context of this complex query.
Here is my approach, which I have figured out on the basis of what I learned from Jon Skeets answer:
List<XElement> = doc.Descendants("open_auction")
.Where(x => x.Element("bidder") != null && x.Elements("bidder")
.Any(b => b.Elements("personref")
.First(c => c.IsBidder("person540"))
.ElementsAfterSelf("bidder").Any(d => d.IsBidder("person1068"))
)).ToList();
But it doesn´t work either. But...:
List<XElement> j = XMarkXML.Element("site").Element("open_auctions").Elements("open_auction")
.Where(x => x.Element("bidder") != null && x.Elements("bidder")
.Any(b => b.Elements("personref")
.Any(c => c.IsBidder("person540")) //I changed First with Any
//.ElementsAfterSelf().Any(d => d.IsBidder("person1068")) //and commented the last part out
)).ToList();
... at least brought up the 3 "open_auction"s where "person540" is a bidder. To be specific in my testfile it is id="open_auction11" where "person1068" bidds "after" "person540".
I am asking from the bottom of my heart, can somebody please give it a try with the Testfile. I uploaded it to www.megamatz.de/auction.xml . Inside the file the path to the open auctions is: Element("site").Element("open_auctions").Elements("open_auction")
EDIT: Okay, edited again. I think this is okay, but even with your sample file it would be nice if you could give us a concrete example of inputs and expected outputs:
// Extension methods
internal static bool IsBidder(this XElement bidder, string person)
{
return (string) bidder.Element("personref")
.Attribute("person") == person;
}
internal static IEnumerable<XElement> ElementsAfterFirst
(this IEnumerable<XElement> source, XName name)
{
var first = source.FirstOrDefault();
if (first != null)
{
foreach (var element in first.ElementsAfterSelf(name))
{
yield return element;
}
}
}
var query = doc
.Descendants("open_auction")
.Where(x => x.Elements("bidder")
// All the person20 bids...
.Where(b => b.IsBidder("person20"))
// Now project to bids after the first one...
.ElementsAfterFirst("bidder")
// Are there any from person51? If so, include this auction.
.Any(b => b.IsBidder("person51"))
);
The second extension method can also be written as:
internal static IEnumerable<XElement> ElementsAfterFirst
(this IEnumerable<XElement> source, XName name)
{
var first = source.FirstOrDefault();
return first == null ? new XElement[0] : first.ElementsAfterSelf(name);
}
It changes the timing slightly, but it shouldn't make any difference in this particular case...
Thx a lot Jon! Your code helped me a lot to learn this concept of Linq to XML Ok, I have made a query without any helper methods, which works fine. I have formated a bit unusal but it might help other noops like me who take a look here to understand what is going on:
List<XElement> i = XMarkXML.Element("site").Element("open_auctions").Descendants("open_auction")
.Where(x => (x.Element("bidder") != null )
&&
(x.Elements("bidder").Any(b => (b.Element("personref").Attribute("person").Value == "person540")))
&&
(
x.Elements("bidder").Where(
b1 => (b1.Element("personref").Attribute("person").Value == "person540")
).First().ElementsAfterSelf("bidder").Any(
b2 => (b2.Element("personref").Attribute("person").Value == "person1068")
)
)
).ToList();
When taking actions on Collections, it is important, that there is something inside, so it is important to check if Collection.Count() != 0. After that it is possible to do the query.
精彩评论