2012-12-28 60 views
0

一位同事在工作時遇到了一個非常不尋常的XML文件問題,當試圖查詢它並試圖幫助他時,我和其他人都在創意塊....看看這個,它可能感興趣的很多人在這裏....複雜的XML結構很難用LINQ查詢到XML

結構:

<Root> 
<MainFoo> 
    <Foo> 
     <A bla="bla" /> 
     <B bla1="blablabla" /> 
     <C bla2="blabla" /> 
     <Bar N="Education" V="Some Text" /> 
     <Bar N="Other Node" V="Some other Text" /> 
     <Bar N="Yet Other Node" V="Some other other Text" /> 
     <Bar N="fourth Bar Node" V="Some other other otherText" /> 
     <Bar N="UserID" V="1" /> 
    </Foo> 
    <Foo> 
     <A bla="bla" /> 
     <B bla1="blablabla" /> 
     <C bla2="blabla" /> 
     <Bar N="Education" V="Specific Text" /> 
     <Bar N="Other Node" V="Some other Text" /> 
     <Bar N="Yet Other Node" V="Some other other Text" /> 
     <Bar N="fourth Bar Node" V="Some other other otherText" /> 
     <Bar N="UserID" V="2" /> 
    </Foo> 
    <Foo> 
     <A bla="bla" /> 
     <B bla1="blablabla" /> 
     <C bla2="blabla" /> <!--***No Bar node with N="Education" in this Foo Node, not a mistake! this might be part of the problem but this is the XML Structure and can't be changed***--> 
     <Bar N="Other Node" V="Some other Text" /> 
     <Bar N="Yet Other Node" V="Some other other Text" /> 
     <Bar N="fourth Bar Node" V="Some other other otherText" /> 
     <Bar N="UserID" V="3" /> 
    </Foo> 
    <Foo> 
     <A bla="bla" /> 
     <B bla1="blablabla" /> 
     <C bla2="blabla" /> 
     <Bar N="Education" V="Specific Text" /> 
     <Bar N="Other Node" V="Some other Text" /> 
     <Bar N="Yet Other Node" V="Some other other Text" /> 
     <Bar N="fourth Bar Node" V="Some other other otherText" /> 
     <Bar N="UserID" V="4" /> 
    </Foo> 
</MainFoo> 
<OtherMainFoo></OtherMainFoo> 
<MoreMainFoo></MoreMainFoo> 
</Root> 

OK,現在手頭的問題: 我們正在與LINQ努力XML獲取每個用戶ID值對於每個用戶節點成爲每個字符串Foo元素IF有這美孚一杆節點杆節點的N屬性是「教育」且僅當與屬性教育這個杆節點都有一個值在V不是包含了我們在LINQ

例如指定,如果我們希望所有用戶ID爲富教育節點不包含單詞「一些」我們將得到的2,4結果的話因爲Foo第一個有一個酒吧具有N屬性的教育值的節點,但它在V屬性和Foo編號中具有一些字符串3在N屬性中沒有具有教育值的條形節點(非常重要,因爲我們認爲這是其中一個原因我們總是空着的,結果是我們做了什麼)。

任何LINQ to XML專家在這裏有一個想法,這是一個非常不尋常的XML場景,但那是我們必須處理的,而且這個問題會引起很多人的興趣。

+0

不同於論壇的網站,我們不使用「謝謝」,或者「任何幫助表示讚賞「,或簽名[so]。請參見「[應‘你好’,‘謝謝’標語,並稱呼從撤職?](http://meta.stackexchange.com/questions/2950/should-hi-thanks-taglines-and-salutations-be -removed - 從 - 個)。 –

+0

有沒有理由使用LINQ這個任務嗎?這裏的業務規則聽起來更適合的System.Xml – Hogan

+0

首先,不會再發生了約翰,霍根,爲什麼不使用LINQ?它應該做System.xml所做的每件事情來查詢XML Structure,但速度更快,代碼行更少,速度更快,或者我錯了嗎?是不是圍繞LINQ to XML的所有想法?爲什麼使用XML Node查詢這裏,那麼LINQ – Erez

回答

2

TL;博士:

var hasEducation = contacts.Elements("MainFoo").Elements("Foo") 
.Where(foo => foo.Elements("Bar") 
       .Any(bar => (bar.Attribute("N").Value == "Education") && 
        (!bar.Attribute("V").Value.ToLower().Contains("some")))) 

注:我LinqPad(http://www.linqpad.net/)使用它,喜歡它測試了這個LinqPad是完美的這些問題。 。下面是LinqPad查詢的完整源代碼,用於測試和演示自己。

主要在哪裏工作的元素foo。然後它會檢查您希望應用的規則的元素(特別是「Bar」元素及其屬性)。

這裏的關鍵問題是這種類型的查詢是如何可維護的。你能保持這樣的linq查詢嗎?嘗試與LinqPad合作 - 我相信這會使您對這些查詢的修改和開發更容易(或任何人)。)


要獲得用戶ID列表(約翰的答案),你只需添加

.Element("User").Attribute("ID").Value; 

上述查詢的末尾。

當然,這並不包括約翰性感的錯誤檢查。


XElement contacts = XElement.Parse (@" 
<Root> 
<MainFoo> 
    <Foo> 
     <A bla='bla' /> 
     <B bla1='blablabla' /> 
     <C bla2='blabla' /> 
     <Bar N='Education' V='Some Text' /> 
     <Bar N='Other Node' V='Some other Text' /> 
     <Bar N='Yet Other Node' V='Some other other Text' /> 
     <Bar N='fourth Bar Node' V='Some other other otherText' /> 
     <User ID='1' /> 
    </Foo> 
    <Foo> 
     <A bla='bla' /> 
     <B bla1='blablabla' /> 
     <C bla2='blabla' /> 
     <Bar N='Education' V='Specific Text' /> 
     <Bar N='Other Node' V='Some other Text' /> 
     <Bar N='Yet Other Node' V='Some other other Text' /> 
     <Bar N='fourth Bar Node' V='Some other other otherText' /> 
     <User ID='2' /> 
    </Foo> 
    <Foo> 
     <A bla='bla' /> 
     <B bla1='blablabla' /> 
     <C bla2='blabla' /> <!--***No Bar node with N='Education' in this Foo Node, not a mistake! this might be part of the problem but this is the XML Structure and can't be changed***--> 
     <Bar N='Other Node' V='Some other Text' /> 
     <Bar N='Yet Other Node' V='Some other other Text' /> 
     <Bar N='fourth Bar Node' V='Some other other otherText' /> 
     <User ID='3' /> 
    </Foo> 
    <Foo> 
     <A bla='bla' /> 
     <B bla1='blablabla' /> 
     <C bla2='blabla' /> 
     <Bar N='Education' V='Specific Text' /> 
     <Bar N='Other Node' V='Some other Text' /> 
     <Bar N='Yet Other Node' V='Some other other Text' /> 
     <Bar N='fourth Bar Node' V='Some other other otherText' /> 
     <User ID='4' /> 
    </Foo> 
</MainFoo> 
<OtherMainFoo></OtherMainFoo> 
<MoreMainFoo></MoreMainFoo> 
</Root>"); 

var hasEducation = contacts.Elements("MainFoo").Elements("Foo") 
     .Where(foo => foo.Elements("Bar") 
       .Any(bar => (bar.Attribute("N").Value == "Education") && 
          (!bar.Attribute("V").Value.ToLower().Contains("some")))) 
     .Dump(); 
1

以下似乎工作:

public static IEnumerable<int> QueryComplexXml() 
{ 
    var doc = XDocument.Parse(XML); 
    if (doc.Root == null) 
    { 
     throw new System.InvalidOperationException("No root"); 
    } 

    var mainFoo = doc.Root.Element("MainFoo"); 
    if (mainFoo == null) 
    { 
     throw new System.InvalidOperationException("No MainFoo"); 
    } 

    var userIDs = from foo in mainFoo.Elements("Foo") 
        where 
         foo.Elements("Bar") 
         .Any(
          bar => 
          bar.Attribute("N").Value == "Education" && 
          bar.Attribute("V").Value == "Specific Text") 
        let user = foo.Element("User") 
        where user != null 
        select int.Parse(user.Attribute("ID").Value); 
    return userIDs; 
} 

代碼認爲所有的「富」的元素,但只有那些存在具有「教育」的「N」屬性「欄」元素和「特定文本」的「V」屬性(你可以放置任何你想要的謂詞)。對於所選元素中的每一個,它都會提取「用戶」元素(假設之一,並解析並返回「ID」屬性。 。

+0

謝謝約翰。我在我的問題中犯了一些錯誤,而你回答對我的問題非常好,但問題是我需要特定Foo節點中Bar節點的N屬性的V屬性,它的值是UserID,即是什麼讓它更復雜....如果你不介意,請再次看看我的編輯... – Erez

2

爲了保持你的選擇餘地,這是一個使用XPath的,而不是LINQ的解決方案。這不包括根據John的答案進行錯誤檢查,但它的工作原理完全相同。

public static IEnumerable<string> GetIDs(XDocument doc, string negation) 
{ 
    //The following xpath string will select all Foo elements that contain a Bar child 
    // that has a N attribute with the value "Education" and also has a V attribute 
    // that does not contain the specified string. 
    string xPathString = String.Format("//Foo[(Bar/@N = 'Education') and (not(contains(Bar/@V, '{0}')))]", negation); 

    return doc.Root 
       .XPathSelectElements(xPathString) //Select the proper Foo elements 
       .Select(a => a.Element("User").Attribute("ID").Value); //Grab the User elements under the previous Foo elements and return their ID attribute value 
} 
+0

+1巧妙的解決方案(但搜索字符串不區分大小寫) –

2
string text = "Some"; 
var query = from foo in xdoc.Descendants("Foo") 
      let user = foo.Element("User") 
      where user != null && 
        foo.Elements("Bar") 
        .Any(bar => (string)bar.Attribute("N") == "Education" && 
           !Regex.IsMatch((string)bar.Attribute("V"), text, 
               RegexOptions.IgnoreCase)) 
      select (int)user.Attribute("ID"); 

// result: 2, 4 

我用正則表達式來搜索欄的屬性詞有兩個原因 - 爲了使搜索不區分大小寫,當Bar元素沒有V屬性照顧案件。你也可以改變模式來匹配單詞(不是單詞的一部分)。


如果所有Foo節點有User元素,你可以刪除用戶空檢查。此外,如果Bar元素總是包含V屬性,你並不需要區分大小寫的搜索,然後查詢可以簡化爲:

var query = from foo in xdoc.Descendants("Foo")      
      where foo.Elements("Bar") 
         .Any(bar => (string)bar.Attribute("N") == "Education" && 
            !((string)bar.Attribute("V")).Contains(text)) 
      select (int)foo.Element("User").Attribute("ID"); 
+0

使用正則表達式匹配和使用String.Equals(屬性,文本,StringComparison.CurrentCultureIgnoreCase)'? –

+0

@IchabodClay同意,這也是選項,如果沒有需要匹配的話 –

+0

當我發表評論時,我沒有想到過。正則表達式在這種情況下更好:P –