LINQ学习笔记:投射到X-DOM
投射到X-DOM
我们可以将LINQ查询投射到一个X-DOM. 其数据源可以是LINQ支持的任何一种, 例如:
- LINQ to SQL 表
- 本地集合
- 另外一个X-DOM
不管是那种数据源, 使用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之外其他的成员就是:
- Add方法, 其接受类似构造器的内容
- Name属性
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属性并将其投射到一个元素上面. (全序列完!)