LINQ之路21:LINQ to XML之生成X-DOM(Projecting)
??
LINQ之路21:LINQ to XML之生成X-DOM(Projecting)
到目前为止,我们已经讨论了如何使用LINQ从一个X-DOM中获取数据。其实,我们同样可以使用LINQ查询来生成一个X-DOM。数据源可以是支持LINQ查询的任何数据,比如:
不管是何种数据源,使用LINQ来产生X-DOM的策略都是一样的:首先写出产生目标X-DOM的函数式构造表达式,然后针对该表达式创建LINQ查询。 比如,假设我们想从数据库中获取customers并产生如下XML: <customers> customer id="1"> name>Sue</buys>3customer> ... > 我们首先为X-DOM写出函数式构造表达式(这里用简单的字符串值): var customers = new XElement("customers",customernew XAttribute(id1),0)">nameSue"),0)">buys3) ) ); 然后我们我们把它放入数据转换之中,创建出针对该表达式的LINQ查询: from c in dataContext.Customers select 这里是其结果XML: >Tom="2">Harry>2> 通过把上面查询的创建分成两步,我们可以更清楚地理解它的工作方式: 第一步: // 创建一个普通的LINQ to SQL查询,产生一个XElement sequence IEnumerable<XElement> sqlQuery = in dataContext.Customers 第二步: 创建根元素customers var customers = 排除空元素 对于没有lastBigBuy的customers,查询会产生null而不是空的XElement。这正是我们想要的,因为null会被X-DOM简单的忽略掉。 Stream类型元素如果我们仅是为了保存(或调用ToString)的目的来产生X-DOM,那么我们可以通过XStreamingElement来改善内存的使用效率。一个XStreamingElement相当于XElement的简化版本,并且它对子节点应用延迟加载语义。 使用它时,简单的把外层XElements替换成XStreamingElements即可: new XStreamingElement(data.xml"); 直到我们调用Save、ToString或WriteTo方法时,传入XStreamingElement构造函数的查询才会真正被执行。这样就避免了立即把整个X-DOM装载到内存之中。当然另一方面,如果我们多次调用Save,查询也会被重复执行。还有,我们不能遍历XStreamingElement的子节点,它没有提供诸如Elements或Attributes之类的成员。 XStreamingElement不是从XObject(或是其它类)继承而来,它仅有的成员除了Save、ToString和 WriteTo,就是Add方法了,Add方法用于向它添加子节点等内容。 转换X-DOM我们可以对现有的X-DOM进行转换。比如,C#编译器和Visual Studio为了描述一个项目,会创建相应的XML文件,该文件会类似以下格式: Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/dev...>" <PropertyGroupPlatform Condition=" '$(Platform)' == '' ">AnyCPUPlatformProductVersion>9.0.11209> ... PropertyGroupItemGroupCompile Include="ObjectGraph.cs" /> ="Program.cs" ="PropertiesAssemblyInfo.cs" ="TestsAggregation.cs" ="TestsAdvancedRecursiveXml.cs" /> Project> 假如我们现在相对该XML文件进行简化,创建一个只包含相关文件的Report,如下所示: ProjectReportFile>ObjectGraph.cs>Program.cs>PropertiesAssemblyInfo.cs>TestsAggregation.cs>TestsAdvancedRecursiveXml.cs> > 下面的查询可以完成该项转换: XElement project = XElement.Load(myProjectFile.csproj"); XNamespace ns = project.Name.Namespace; 获取Namespacevar query = ProjectReportfrom compileItem in project.Elements(ns + ItemGroup").Elements(ns + Compile") 获取所有的Compile元素 let include = compileItem.Attribute(Include") 获取Include属性 where include != null select File 转换X-DOM,创建新的File元素 ); 查询首先获取所有的ItemGroup元素,然后使用Elements扩展方法获取所有的Compile子元素(结果位于一个平展的sequence之中)。需要注意的是,我们必须指定一个XML命名空间,因为源文件中的所有节点都继承了定义在Project元素上的命名空间,所以单凭一个本地的元素名称如ItemGroup是不够的。之后,我们根据Include属性创建新的元素。 高级转换但我们对一个本地集合如X-DOM进行查询时,我们可以通过自定义的查询运算符来创建更加复杂的查询,完成更加高级的功能。 假如在前面的例子中,我们想要的是一个按目录分组的层次输出,如下: Folder ="Properties">AssemblyInfo.csFolder="Tests">Aggregation.cs="Advanced"> >RecursiveXml.cs> 产生这样的结果,我们需要递归的处理路径字符串如: TestsAdvancedRecursiveXml.cs。下面的方法可以完成这项工作:它接收一个路径sequence,产生我们期望的X-DOM层次结果级: static IEnumerable<XElement> ExpandPaths(IEnumerable<string> paths) { var brokenUp = from path in paths let split = path.Split(new char[] { '' },128)">2) orderby split[0] new { name = split[0],remainder = split.ElementAtOrDefault(1) }; IEnumerable<XElement> files = from b in brokenUp where b.remainder == null filein brokenUp where b.remainder != null group b.remainder by b.name into grp folderreturn files.Concat(folders); } 上面的方法中,第一个查询以第一个反斜线符号把路径分为两部分:name + remainder。比如:TestsAdvancedRecursiveXml.cs到Tests + AdvancedRecursiveXml.cs。如果remainder为null,则name就是一个文件,files查询会把它装载到files sequence。如果remainder不为null,name就是一个folder,folders查询会处理这种情况。因为其它文件可能也位于相同的目录中,我们必须按目录名来进行分组。对每一个分组,会递归调用该方法来处理子元素。 最终的结果是包含了所有文件和目录的sequence。Concat运算符会保持元素的顺序,所以所有的文件按字母顺序排在前面,然后是按字母顺序排列的所有目录。 有了这个方法,我们可以通过两步来完成我们的查询。首先,获取所有的文件路径: IEnumerable<string> paths = in project.Elements(ns + ") let include = compileItem.Attribute(") null select include.Value; 然后,在查询中使用ExpandPaths方法来获得最终结果: var query = Project(编辑:李大同) |