LINQ学习笔记:投射到X-DOM

2009-04-09 15:40:10  来源:网页教学网 

WebjxCom提示:我们可以将LINQ查询投射到一个X-DOM. 其数据源可以是LINQ支持的任何一种.

投射到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属性并将其投射到一个元素上面. (全序列完!)

更多

推荐文章