投射到X-DOM
我們可以將LINQ查詢投射到一個X-DOM. 其數據源可以是LINQ支持的任何一種, 例如:
不管是那種數據源, 使用LINQ投射一個X-DOM的策略是一樣的: 你首先需要編寫一個構建表達式用於產生需要的X-DOM形狀, 然後圍繞這個表達式編寫LINQ查詢
例如, 假設我們想從一個數據庫當中查詢客戶並產生相應的XML:
1: <customers>
2: <customer id="1">
3: <name>Sue</name>
4: <buys>3</buys>
5: </customer>
6: </customers>
我們開始使用簡單的文字為該X-DOM編寫一個功能性的構造表達式:
1: var customers =
2: new XElement ("customers",
3: new XElement ("customer", new XAttribute ("id", 1),
4: new XElement ("name", "Sue"),
5: new XElement ("buys", 3)
6: )
7: );
然後我們將其轉換成為一個影射創建LINQ查詢:
1: var customers =
2: new XElement ("customers",
3: from c in dataContext.Customers
4: select
5: new XElement ("customer",
6: new XAttribute ("id", c.ID),
7: new XElement ("name", c.Name),
8: new XElement ("buys", c.Purchases.Count)
9: )
10: );
最後的結果可能類似:
1: <customers>
2: <customer id="1">
3: <name>Tom</firstname>
4: <buys>3</buys>
5: </customer>
6: <customer id="2">
7: <name>Harry</firstname>
8: <buys>2</buys>
9: </customer>
10: ...
11: </customers>
在這個例子中外部的查詢使得查詢從遠程的LINQ to SQL轉換成了本地的可枚舉查詢. XElement的構造器並不知道IQueryable<>, 因此它將導致LINQ to SQL立即執行SQL語句.
消滅空元素
假設前面的例子我們還想要包括客戶最近的高價值的采購單的信息, 我們可以這樣做:
1: var customers =
2: new XElement ("customers",
3: from c in dataContext.Customers
4: let lastBigBuy = (from p in c.Purchases
5: where p.Price > 1000
6: orderby p.Date descending
7: select p).FirstOrDefault()
8: select
9: new XElement ("customer",
10: new XAttribute ("id", c.ID),
11: new XElement ("name", c.Name),
12: new XElement ("buys",c.Purchases.Count),
13: new XElement ("lastBigBuy",
14: new XElement("description",
15: lastBigBuy == null
16: ? null: lastBigBuy.Description),
17: new XElement("price",
18: lastBigBuy == null
19: ? 0m :lastBigBuy.Price)
20: )
21: )
22: );
23:
這會去掉空的元素, 也就是那些沒有高價值采購單的客戶. (如果它是一個本地查詢, 而不是LINQ to SQL查詢, NullReferenceException異常將會拋出. 在這個例子中, 整個省略lastBigBuy節點會更好. 我們可以通過在條件操作符裡面包裝一個lastBigBuy的構造器來完成這個目標)
1: select
2: new XElement ("customer",
3: new XAttribute ("id", c.ID),
4: new XElement ("name", c.Name),
5: new XElement ("buys", c.Purchases.Count),
6: lastBigBuy == null ? null :
7: new XElement ("lastBigBuy",
8: new XElement ("description",
9: lastBigBuy.Description),
10: new XElement ("price", lastBigBuy.Price)
對於那些沒有lastBigBuy的客戶, null將會被發出而不是XElement. 這也是我們所想要的因為null的內容通常都是被忽略的.
流化一個投射
如果你正在通過調用Save來投射一個X-DOM, 你可以使用XStreamingElement來提高內存的效率. XStreamingElement是一個削減過的XElement版本, 對其子內容使用了延遲加載.要使用它, 你可以簡單的使用XStreamingElement來替換外圍的XElement:
1: var customers =
2: new XStreamingElement ("customers",
3: from c in dataContext.Customers
4: select
5: new XStreamingElement ("customer",
6: new XAttribute ("id", c.ID),
7: new XElement ("name", c.Name),
8: new XElement ("buys", c.Purchases.Count)
9: )
10: );
11: customers.Save ("data.xml");
這個查詢將通過XStreamingElement的構造器並且不會被執行直到你在Element上面調用了Save, ToString或者WriteTo; 這避免了一次將整個X-DOM加載到內存當中. 另外一點是該查詢還會自動判別是否是重新Save, 你也不能橫貫XStreamingElement的子內容——因為它並沒有暴露類似Elements或者Attribute的方法.
XStreamingElement是基於XObject的——而不是其他類——因為它有一些有限的成員. 除了Save, ToString和WriteTo之外其他的成員就是:
XStreamingElement不允許按流行的方法讀取流的內容——為了達到這個目標, 你必須和X-DOM一起使用XmlReader.
轉換X-DOM
我們可以通過重新投影來轉換一個X-DOM. 例如, 假設我們想要轉換一個msbuild的XML文件到一個簡單的格式以便可以用其產生一個報表. 一個mubuild文件看起來類似這樣:
1: <Project DefaultTargets="Build"
2: xmlns="http://schemas.microsoft.com/dev...>
3: <PropertyGroup>
4: <Platform Condition=" '$(Platform)' == '' ">
5: AnyCPU
6: </Platform>
7: <ProductVersion>9.0.11209</ProductVersion>
8: ...
9: </PropertyGroup>
10: <ItemGroup>
11: <Compile Include="ObjectGraph.cs" />
12: <Compile Include="Program.cs" />
13: <Compile Include="Properties\AssemblyInfo.cs" />
14: <Compile Include="Tests\Aggregation.cs" />
15: <Compile Include="Tests\Advanced\RecursiveXml.cs" />
16: </ItemGroup>
17: <ItemGroup>
18: ...
19: </ItemGroup>
20: ...
21: </Project>
假設我們只想包含文件, 如下:
1: <ProjectReport>
2: <File>ObjectGraph.cs</File>
3: <File>Program.cs</File>
4: <File>Properties\AssemblyInfo.cs</File>
5: <File>Tests\Aggregation.cs</File>
6: <File>Tests\Advanced\RecursiveXml.cs</File>
7: </ProjectReport>
以下的查詢執行了這個變換:
1: XElement project = XElement.Load("myProjectFile.csproj");
2: XNamespace ns = project.Name.Namespace;
3: var query =
4: new XElement ("ProjectReport",
5: from compileItem in
6: project.Elements (ns + "ItemGroup")
7: .Elements (ns + "Compile")
8: let include = compileItem.Attribute ("Include")
9: where include != null
10: select new XElement ("File", include.Value)
11: );
此查詢提取了所有的ItemGroup元素, 然後使用Elements的擴展方法去獲取一個扁平的包含所有Compile子元素的序列. 注意我們必須要指定一個XML命名空間——原來的文件裡面所有的東西都繼承了Project元素中定義的命名空間——因此本地元素例如ItemGroup 並不會自動生成一樣的命名空間. 我們還提取了Include屬性並將其投射到一個元素上面. (全序列完!)