ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

LINQ之路21:LINQ to XML之生成X-DOM(Projecting)

2019-06-27 21:30:49  阅读:269  来源: 互联网

标签:XML 21 DOM XElement 之路 LINQ 查询 new


到目前为止,我们已经讨论了如何使用LINQ从一个X-DOM中获取数据。其实,我们同样可以使用LINQ查询来生成一个X-DOM。数据源可以是支持LINQ查询的任何数据,比如:

  • LINQ to SQL或 Entity Framework查询
  • 本地集合
  • 另外一个X-DOM

不管是何种数据源,使用LINQ来产生X-DOM的策略都是一样的:首先写出产生目标X-DOM的函数式构造表达式,然后针对该表达式创建LINQ查询。

比如,假设我们想从数据库中获取customers并产生如下XML:

    <customers>
<customer id="1">
<name>Sue</name>
<buys>3</buys>
</customer>
...
</customers>

我们首先为X-DOM写出函数式构造表达式(这里用简单的字符串值):

            var customers =
new XElement("customers",
new XElement("customer", new XAttribute("id", 1),
new XElement("name", "Sue"),
new XElement("buys", 3)
)
);

然后我们我们把它放入数据转换之中,创建出针对该表达式的LINQ查询:

            var customers =
new XElement("customers",
from c in dataContext.Customers
select
new XElement("customer", new XAttribute("id", c.ID),
new XElement("name", c.Name),
new XElement("buys", c.Purchases.Count)
)
);

这里是其结果XML:

    <customers>
<customer id="1">
<name>Tom</name>
<buys>3</buys>
</customer>
<customer id="2">
<name>Harry</name>
<buys>2</buys>
</customer>
...
</customers>

通过把上面查询的创建分成两步,我们可以更清楚地理解它的工作方式:

第一步:

            // 创建一个普通的LINQ to SQL查询,产生一个XElement sequence
IEnumerable<XElement> sqlQuery =
from c in dataContext.Customers
select
new XElement("customer", new XAttribute("id", c.ID),
new XElement("name", c.Name),
new XElement("buys", c.Purchases.Count)
);

第二步:

            // 创建根元素customers
var customers = new XElement("customers", sqlQuery);

唯一不同寻常的事情是内容参数sqlQuery,它不是一个简单的XElement,而是IQueryable<XElement>(实现了IEnumerable<XElement>)。还记得X-DOM处理XML内容的过程吗?记住,它会自动遍历实现了IEnumerable的集合。所以,每个XElement都被作为一个子节点被添加到customers元素下。

排除空元素

假如在上面的例子中,我们还想把customer最新的大额purchase包含进来,那么我们可以创建如下查询:

            var customers =
new XElement("customers",
from c in dataContext.Customers
let lastBigBuy = (from p in c.Purchases // 子查询获取最新的大额订单
where p.Price > 1000
orderby p.Date descending
select p).FirstOrDefault()
select
new XElement("customer", new XAttribute("id", c.ID),
new XElement("name", c.Name),
new XElement("buys", c.Purchases.Count),
new XElement("lastBigBuy",
new XElement("description",
lastBigBuy == null ? null : lastBigBuy.Description),
new XElement("price",
lastBigBuy == null ? 0m : lastBigBuy.Price)
)
)
);

当customer没有大额订单时,上面的查询会产生空的lastBigBuy元素。其实在这种情况下,更好的解决方案是完全忽略lastBigBuy节点。我们可以通过把lastBigBuy元素的构造函数包装在条件运算符中来实现这一目的:

                    select
new XElement ("customer", new XAttribute ("id", c.ID),
new XElement ("name", c.Name),
new XElement ("buys", c.Purchases.Count),
lastBigBuy == null ? null :
new XElement ("lastBigBuy",
new XElement ("description", lastBigBuy.Description),
new XElement ("price", lastBigBuy.Price)
)
)

对于没有lastBigBuy的customers,查询会产生null而不是空的XElement。这正是我们想要的,因为null会被X-DOM简单的忽略掉。

Stream类型元素

如果我们仅是为了保存(或调用ToString)的目的来产生X-DOM,那么我们可以通过XStreamingElement来改善内存的使用效率。一个XStreamingElement相当于XElement的简化版本,并且它对子节点应用延迟加载语义。

使用它时,简单的把外层XElements替换成XStreamingElements即可:

            var customers =
new XStreamingElement("customers",
from c in dataContext.Customers
select
new XStreamingElement("customer", new XAttribute("id", c.ID),
new XElement("name", c.Name),
new XElement("buys", c.Purchases.Count)
)
);
customers.Save("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...>"
<PropertyGroup>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>9.0.11209</ProductVersion>
...
</PropertyGroup>
<ItemGroup>
<Compile Include="ObjectGraph.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests\Aggregation.cs" />
<Compile Include="Tests\Advanced\RecursiveXml.cs" />
</ItemGroup>
<ItemGroup>
...
</ItemGroup>
...
</Project>

假如我们现在相对该XML文件进行简化,创建一个只包含相关文件的Report,如下所示:

    <ProjectReport>
<File>ObjectGraph.cs</File>
<File>Program.cs</File>
<File>Properties\AssemblyInfo.cs</File>
<File>Tests\Aggregation.cs</File>
<File>Tests\Advanced\RecursiveXml.cs</File>
</ProjectReport>

下面的查询可以完成该项转换:

            XElement project = XElement.Load("myProjectFile.csproj");
XNamespace ns = project.Name.Namespace; // 获取Namespace
var query =
new XElement("ProjectReport",
from compileItem in
project.Elements(ns + "ItemGroup").Elements(ns + "Compile") // 获取所有的Compile元素
let include = compileItem.Attribute("Include") // 获取Include属性
where include != null
select new XElement("File", include.Value) // 转换X-DOM,创建新的File元素
);

查询首先获取所有的ItemGroup元素,然后使用Elements扩展方法获取所有的Compile子元素(结果位于一个平展的sequence之中)。需要注意的是,我们必须指定一个XML命名空间,因为源文件中的所有节点都继承了定义在Project元素上的命名空间,所以单凭一个本地的元素名称如ItemGroup是不够的。之后,我们根据Include属性创建新的元素。

高级转换

但我们对一个本地集合如X-DOM进行查询时,我们可以通过自定义的查询运算符来创建更加复杂的查询,完成更加高级的功能。

假如在前面的例子中,我们想要的是一个按目录分组的层次输出,如下:

    <Project>
<File>ObjectGraph.cs</File>
<File>Program.cs</File>
<Folder name="Properties">
<File>AssemblyInfo.cs</File>
</Folder>
<Folder name="Tests">
<File>Aggregation.cs</File>
<Folder name="Advanced">
<File>RecursiveXml.cs</File>
</Folder>
</Folder>
</Project>

产生这样的结果,我们需要递归的处理路径字符串如: Tests\Advanced\RecursiveXml.cs。下面的方法可以完成这项工作:它接收一个路径sequence,产生我们期望的X-DOM层次结果级:

        static IEnumerable<XElement> ExpandPaths(IEnumerable<string> paths)
{
var brokenUp = from path in paths
let split = path.Split(new char[] { '\\' }, 2)
orderby split[0]
select new
{
name = split[0],
remainder = split.ElementAtOrDefault(1)
};

IEnumerable<XElement> files = from b in brokenUp
where b.remainder == null
select new XElement("file", b.name);
IEnumerable<XElement> folders = from b in brokenUp
where b.remainder != null
group b.remainder by b.name into grp
select new XElement("folder",
new XAttribute("name", grp.Key),
ExpandPaths(grp)
);

return files.Concat(folders);
}

上面的方法中,第一个查询以第一个反斜线符号把路径分为两部分:name + remainder。比如:Tests\Advanced\RecursiveXml.cs到Tests + Advanced\RecursiveXml.cs。如果remainder为null,则name就是一个文件,files查询会把它装载到files sequence。如果remainder不为null,name就是一个folder,folders查询会处理这种情况。因为其它文件可能也位于相同的目录中,我们必须按目录名来进行分组。对每一个分组,会递归调用该方法来处理子元素。

最终的结果是包含了所有文件和目录的sequence。Concat运算符会保持元素的顺序,所以所有的文件按字母顺序排在前面,然后是按字母顺序排列的所有目录。

有了这个方法,我们可以通过两步来完成我们的查询。首先,获取所有的文件路径:

            IEnumerable<string> paths =
from compileItem in
project.Elements(ns + "ItemGroup").Elements(ns + "Compile")
let include = compileItem.Attribute("Include")
where include != null
select include.Value;

然后,在查询中使用ExpandPaths方法来获得最终结果:

            var query = new XElement("Project", ExpandPaths(paths));

至此,LINQ之路系列博客已经接近完成了。从开始的LINQ介绍到C#3.0的语言特性;从LINQ方法语法到查询表达式语法;从延迟执行到子查询;从解释查询到LINQ to SQL/Entity Framework;从各个查询运算符到LINQ to XML。共计21篇博客,前后历时将近2个月,好漫长的过程,真心感谢各位园友的大力支持和陪伴!稍后,会为大家奉上系列博客后记和全篇博客导航。

 


系列博客导航:

LINQ之路系列博客导航

LINQ之路 1:LINQ介绍

LINQ之路 2:C# 3.0的语言功能(上)

LINQ之路 3:C# 3.0的语言功能(下)

LINQ之路 4:LINQ方法语法

LINQ之路 5:LINQ查询表达式

LINQ之路 6:延迟执行(Deferred Execution)

LINQ之路 7:子查询、创建策略和数据转换

LINQ之路 8:解释查询(Interpreted Queries)

LINQ之路 9:LINQ to SQL 和 Entity Framework(上)

LINQ之路10:LINQ to SQL 和 Entity Framework(下)

LINQ之路11:LINQ Operators之过滤(Filtering)

LINQ之路12:LINQ Operators之数据转换(Projecting)

LINQ之路13:LINQ Operators之连接(Joining)

LINQ之路14:LINQ Operators之排序和分组(Ordering and Grouping)

LINQ之路15:LINQ Operators之元素运算符、集合方法、量词方法

LINQ之路16:LINQ Operators之集合运算符、Zip操作符、转换方法、生成器方法

LINQ之路17:LINQ to XML之X-DOM介绍

LINQ之路18:LINQ to XML之导航和查询

LINQ之路19:LINQ to XML之X-DOM更新、和Value属性交互

LINQ之路20:LINQ to XML之Documents、Declarations和Namespaces

LINQ之路21:LINQ to XML之生成X-DOM(Projecting)

LINQ之路系列博客后记

 

转载于:https://www.cnblogs.com/lifepoem/archive/2011/12/13/2285780.html

标签:XML,21,DOM,XElement,之路,LINQ,查询,new
来源: https://blog.csdn.net/weixin_33989780/article/details/93911910

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有