随笔 - 89, 评论 - 163, 引用 - 33

导航

关于

标签

每月存档

最新留言

广告

在VB中LINQ To Dataset如何工作

[原文作者]Jonathan Aneja
 
      LINQ的核心是要求可以对任何数据源进行可查询,意味着它必需实现IEnumerable接口。(实际上实现起来远比这个复杂,需要详细了解的请参阅Visual Basic 9.0 Language Specification)。现在当我们使用LINQ to Dataset时遇到了一个问题:DataTable没有实现IEnumerable,我们怎样去建立对它的查询呢?
之前我们了解到Visual Studio 2008中包含一个程序集System.Data.DataSetExtensions.dll,里面定义了一个扩展方法AsEnumerable()。形式如下:
 
                <Extension()> _
                Public Function AsEnumerable(source As DataTable) As EnumerableRowCollection(Of DataRow)
 
       这个函数接受一个DataTable的参数,返回一个实现IEnumerable的对象,从而可以使用LINQ的标准查询操作。你要做的就是导入程序集System.Data(工程模板默认已经做了),然后就可以调用AsEnumerable()来使用针对DatasetLINQ
 
                Dim customers = TestDS.Tables("Customers")
 
                Dim franceCustomers = From cust In customers.AsEnumerable() _
                          Where cust!Country = "France" _
                          Select cust
 
      现在有一个有趣的事是在VBLINQ的工作方法,不需要在代码中明确地调用AsEnumerable()来实现编译通过。其实是这样的,尽管Datatable没有实现IEnumerable接口,然而编译器通过搜寻特定的方法来帮助实现,该方法能够使Datatable转变为某种可查询的类型。当VB编译器遇到基于某个类型的LINQ没有实现IEnumerable, IEnumerable(Of T), IQueryable, 或者IQueryable(Of T),它将按顺序做下面一些事:
1. 看类型中是否有一个一致的查询方法可见。
2. 看类型中是否有一个名字为AsQueryable的实例方法,改方法返回一个可查询的类型。
3. 看类型中是否有一个名字包含AsQueryable的扩展方法,改方法返回一个可查询的类型。
4. 看类型中是否有一个名字为AsEnumerable的实例方法,改方法返回一个可查询的类型。
5. 看类型中是否有一个名字包含AsEnumerable的扩展方法,改方法返回一个可查询的类型。
      其中任何一步,如果编译器发现一个匹配的方法,将插入一个对它的调用。对于Datatable,当命名空间System.Data已经导入时,编译器在第五步为其发现了一个匹配方法,于是建立了一个对AsEnumerable的调用。结果你就能够写如101 LINQ SamplesLINQ to Dataset的代码了:
 
        Dim customers = TestDS.Tables("Customers")
 
        Dim franceCustomers = From cust In customers _
                              Where cust!Country = "France" _
                              Select cust
 
      实际上就是要你提供一个AsEnumerable的扩展方法,该方法返回一个可查询的类型,从而使LINQ起作用。
注意对于强类型的Datasets,你不需要调用AsEnumerable,因为它们继承自TypedTableBase(Of T),该类型实现了IEnumerable,它是VS2008中的一个新类型,在VS2005Dataset设计器将生成继承自DataTable的代码,并且显式地实现IEnumerable
 
 

posted on 2008-12-17 17:30:52 by VBCTI  评论(0) 阅读(2456)

VB XML 手册6:在VB中写XSLT转换

 
      大多数XSLT程序员对于下面这段复制一个XML文件的XSLT转换都非常熟悉。
<?xmlversion="1.0"encoding="utf-8"?>
<xsl:stylesheetversion="1.0"xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:outputmethod="xml"indent="yes"/>
 
    <xsl:templatematch="@* | node()">
        <xsl:copy>
            <xsl:apply-templatesselect="@* | node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>
        这种XSLT身份转换的应用十分普遍,因为它可以让你复制整个XML文档,并且会“访问”到每一个XML节点和属性。如果你添加一个模板,那么它就会对那些存在匹配的节点或属性进行转换,而那些不存在匹配的节点和属性只会被简单的拷贝。
        我们也可以用Visual Basic 中的XML Literals(包括LINQXMLXML的轴属性)来实现XSLT的转换,VB代码可以通过基于以下伪码的XML文档以递归的方式“访问”到每个节点或元素:
Starting with the root element, perform the following whenever you encounter a node
If the node is an element
If the element has attributes, transform or copy each attribute
If the element has child nodes, transform or copy each node
If the node is text, transform or copy the text
If the node is CData, transform or copy the CData
If the node is a comment, transform or copy the comment
If the node is a processing instruction, transform or copy the processing instruction
 
        对于手册中的这一方法,我们将创建一个抽象(MustInherit)基类来执行上面XML文档中的这段递归调用的伪代码。然后再创建一个继承于这个基类的子类来执行具体的转换。首先,我们将创建那个抽象基类和一个作为“起始点”的Transform函数,它以要被转换的XML文档(XDocument)作为输入,然后将转换后的文档返回。
 
Public MustInherit Class VBXmlTransform
 
 Public Overridable Function Transform(ByVal xmlDoc As XDocument) As XDocument
    Return <?xml version="1.0" encoding="utf-8"?>
           <%= ProcessElement(xmlDoc.Root) %>
 End Function
 
End Class
 
        接着,我们来添加那段要调用到每个XML节点(XNode)的逻辑代码,它包含了elements, text, CData等等。在代码中我们需要判断XML节点的类型从而调用相应的函数来进行转换或者对节点类型进行拷贝,并且返回结果,这个结果可能是一个拷贝而来的节点,也可能是一个经过转换的节点。这个方法即ProcessNode,下面是它的代码:
 
 Public Overridable Function ProcessNode(ByVal xmlNode As XNode) As XNode
' This method ignores DTD (XDocumentType) content.
 
    Dim nodeType = xmlNode.GetType()
 
    ' Because XCData inherits from XText, check for the XCData type before checking
' for XText.
 
    If nodeType Is GetType(XCData) Then Return ProcessCData(xmlNode)
    If nodeType Is GetType(XText) Then Return ProcessText(xmlNode)
    If nodeType Is GetType(XElement) Then Return ProcessElement(xmlNode)
    If nodeType Is GetType(XComment) Then Return ProcessComment(xmlNode)
    If nodeType Is GetType(XProcessingInstruction) Then Return _
      ProcessProcessingInstruction(xmlNode)
 
    Return xmlNode
 End Function
 
        接下来,我们添加一个强类型函数来处理每种节点类型以及属性。由于处理element的函数和其他的不一样,所以我们将稍后给出。处理其他的节点类型和属性的函数相当简单,因为基类的行为就只是拷贝文档,即每一个函数仅仅将其输入值返回。所以我们创建这段代码的目的也就是为了提供一个强类型函数,可以让我们在继承于它的子类的具体方法中来进行覆盖。下面是那些强类型函数(除ProcessElement函数):
 
     Public Overridable Function ProcessAttribute(ByVal xmlAttribute As XAttribute) As XAttribute
        Return xmlAttribute
     End Function
 
     Public Overridable Function ProcessCData(ByVal xmlCData As XCData) As XCData
        Return xmlCData
     End Function
 
     Public Overridable Function ProcessText(ByVal xmlText As XText) As XText
        Return xmlText
     End Function
 
     Protected Overridable Function ProcessComment(ByVal xmlComment As XComment) As XComment
        Return xmlComment
     End Function
 
     Public Overridable Function ProcessProcessingInstruction( _
       ByVal pi As XProcessingInstruction) As XProcessingInstruction
 
        Return pi
      End Function
 
 
        现在让我们来看看那个ProcessElement函数,处理element的方法之所以独特是因为它不仅有属性还有child节点,这些属性和child节点都需要被转换或者拷贝,所以我们必须为每个属性提供ProcessAttribute函数,并且为每个child节点提供ProcessNode函数。我们将把这些代码放入一个名为CopyElement的方法中,这样ProcessElement函数看起来就和其他的强类型函数一样,但是它将返回一个对CopyElement函数的调用,而不仅仅返回其输入值。CopyElement函数使用了XML Literals,嵌入式表达以及LINQXML来实现对XML element的拷贝,代码如下:
 
     Public Overridable Function ProcessElement(ByVal xmlElement As XElement) As XElement
         Return CopyElement(xmlElement)
     End Function
 
     Public Overridable Function CopyElement(ByVal xmlElement As XElement) As XElement
         Return <<%= xmlElement.Name %>
             <%= From attribute In xmlElement.Attributes() _
                 Select ProcessAttribute(attribute) %>>
             <%= From node In xmlElement.Nodes() _
                 Select ProcessNode(node) %>
           </>
     End Function
 
        上面就是我们所说的抽象类了,现在我们可以利用它来进行一些简单或复杂的身份转换了。让我们来看个例子吧:
     创建一个转换
 
        我们的例子将使用和手册1中相同的XML文件,它混合了来自几个不同命名空间的内容,从而提供了一个非常好的例子。这些示例文件中的内容在
< AdditionalContactInfo > element中可以找到: http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo schema。在contact info文件中有address信息,它包含了三种不同的元素:homePostalAddress physicalDeliveryOfficeName registeredAddressaddressType类型含有一个必需的元素PostalCode,下面我们可以创建一个简单的类来对<PostalCode>进行转换并将它重命名为<ZipCode>
 
        首先,我们需要导入那些源文档中不同的schemas,这些源文档中特定的schemas,对于抽象的VBXmlTransform类而言并不需要,但对我们继承而来的子类却是必需的。
 
    Imports <xmlns="http://SampleSchema/AWContacts">
    Imports <xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo">
    Imports <xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
    Imports <xmlns:crm="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactRecord">
 
 
         接下来,我们创建一个继承于VBXmlTransform的类,这个子类叫做AWTransform,在这个子类中,我们覆盖了基类并且添加了新的代码,从而可以执行我们想要的任何转换。这样,我们就必须覆盖掉基类的ProcessElement方法,因为我们需要寻找所有名为PostalCode的元素,如果找到了就进行转换,如果没有找到,才会用基类的ProcessElement方法来处理。
 
   Class AWTransform
    Inherits VBXmlTransform
 
' Rename <act:PostalCode> to <ZipCode>.
' Create an XName object to use for comparisons. This will perform better than comparing
    ' xmlElement.Name.LocalName to a string.
 
    Private postalCodeXName As XName = _
      XName.Get("PostalCode", _
                "http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes")
 
    Public Overrides Function ProcessElement(ByVal xmlElement As XElement) As XElement
        Select Case xmlElement.Name
            Case postalCodeXName
                Return TransformPostalCode(xmlElement)
            Case Else
                Return MyBase.ProcessElement(xmlElement)
        End Select
 
        Return Nothing
    End Function
 
    Public Function TransformPostalCode(ByVal postalCodeElement As XElement) As XElement
        Return <ZipCode><%= postalCodeElement.Value %></ZipCode>
    End Function
  End Class
 
     转换文档
 
        为了实现转换文档,我们需要新建一个子类的实例AWTransform,然后将那个XML源文档传给Transform方法,代码如下:
 
        Dim xmlPath = My.Application.Info.DirectoryPath & "\..\..\AWContacts.xml"
        Dim savePath = My.Application.Info.DirectoryPath & "\..\..\TransformSave.xml"
 
        Dim xmlDoc = XDocument.Load(xmlPath)
 
        Dim transform As New AWTransform()
 
        Dim transformedDoc = transform.Transform(xmlDoc)
        transformedDoc.Save(savePath)
     其他示例
 
        让我们来看看一些其他可以添加到AWTransform类的例子。
        以下这段代码实现了我们怎样去转换一个现有属性的内容如果transform找到名为date的属性就将date值转换为一般的日期和时间格式。
 
    Private dateXName As XName = XName.Get("date")
 
    Public Overrides Function ProcessAttribute(ByVal xmlAttribute As XAttribute) As XAttribute
        If xmlAttribute.Name.Equals(dateXName) Then Return TransformDateAttribute(xmlAttribute)
 
        Return MyBase.ProcessAttribute(xmlAttribute)
    End Function
 
    Public Function TransformDateAttribute(ByVal dateAttribute As XAttribute) As XAttribute
        Dim dateValue As New DateTime()
        If DateTime.TryParse(dateAttribute.Value, dateValue) Then _
            dateAttribute.Value = dateValue.ToString("G")
 
        Return dateAttribute
    End Function 
 
        接下来的这段代码实现了我们怎样从转换后的文档中去掉data:如果transform找到
CData section就返回Nothing,这样CData section就不会存在于结果文档中。
 
    Public Overrides Function ProcessCData(ByVal xmlCData As XCData) As XCData
        Return Nothing
    End Function

posted on 2008-11-07 14:27:45 by VBCTI  评论(0) 阅读(2629)

VB XML手册5:"Halloween"问题

[原文作者]:Doug Rothaus

[原文链接]:VB XML Cookbook, Recipe 5: The “Halloween” Problem (Doug Rothaus)

    在前两次的手册中,我们讲了关于用ReplaceWith这个方法来进行身份转换.如果这个方法满足了你的需要,它会在你的代码中引发另外一个问题--"Halloween".让我们看看这个问题到底是怎样的,怎么样去解决.(详细的"Halloween"问题和解决方案可以参考这个文档).

    "Halloween"描述的场景是,你有一个以某种方式更新的数据集同时这个数据集又在被遍历的话,你会得到一个空引用的异常,更坏的是,你的代码可能会因为修改错误的数据而崩溃.例如前面两个手册用到的代码段:

Private Sub Recipe5(ByVal xmlPath As String)

    Dim xmlDoc = XDocument.Load(xmlPath)

 

    Dim info = xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>

 

    ' Replace e-mail address tags with mailto links.

    For Each email In info...<act:eMail>

      TransformEmail(email)

    Next

  End Sub

 

  Private Sub TransformEmail(ByVal email As XElement)

    Dim emailHtml = <div class="Email">

                      <a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>

                        <%= email.<act:eMailAddress>.Value %>

                      </a>&#32;

                    </div>

 

     email.ReplaceWith(emailHtml)

  End Sub

 

    如果你运行这段代码,就会得到下面的异常:

 

      这个异常的出现是因为For... Each循环正在循环用<eMail>元素查询出来的结果。在第一次调用TransformEmail个方法的时候,<eMail>这个元素已经被HTML替换掉了,结果,这个查询引用的就是已经不存在的XML元素了。

    要怎么解决这个问题呢?有几个解决方案,第一,你可能你已经注意到了在前面的手册中,把这个查询的结果用ToList方法返回一个List。像下面的代码

    For Each email In info...<act:eMail>

    改为:

    For Each email In info...<act:eMail>.ToList()

    返回一个List,表明要处理的就不再是一个查询结果,而更新就会作用到查询上面,例如用HTML替换XML,这样就不会影响到List。

    其他比较普遍的解决方案就是你的代码不要修改原本的文件,把结果放到新的XML文件中。下一个手册我会讲怎么做。


posted on 2008-11-01 08:51:45 by VBCTI  评论(1) 阅读(3200)

LINQ手册8:使用LINQ查询XML(Doug Rothaus)

配料:
·         Visual Studio 2008 (Beta2或更高版本)
类别:LINQ-ToXML
 
步骤:
·         使用VB 创建一个新的Console Application
·         创建RSS文档。添加以下代码到Sub Main中。
    Dim rss = <?xml version="1.0"?>
              <rss version="2.0">
                <channel>
                  <title>LINQ to XML in Visual Basic</title>
                  <description>Sample RSS Feed</description>
                  <language>en-us</language>
                  <pubDate>Tue, 18 Sep 2007 00:56:12 GMT</pubDate>
                  <item>
                    <title>Basic Instincts: Lambda Expressions</title>
                    <description>A new feature added to Visual Basic 9 to
                                 support Language Integrated Queries (LINQ),
                                 which adds data programmability to
                                 Visual Basic.</description>
                    <pubDate>Thu, 27 Sep 2007 23:54:55 GMT</pubDate>
                  </item>
                  <item>
                    <title>Visual Basic Pack for Visual Studio 2005 SDK</title>
                    <description>The Visual Basic Pack for the Visual Studio
                                 2005 SDK includes SDK samples converted into
                                 the Visual Basic language and a new wizard for
                                 generating Visual Basic-based integration
                                 packages for Visual Studio.</description>
                    <pubDate>Mon, 17 Sep 2007 23:58:49 GMT</pubDate>
                  </item>
                  <item>
                    <title>XML to Schema Tool</title>
                    <description>The XML to Schema tool is a free project item
                                 template to automate creation of XML schema
                                 sets from any number of XML documents. If you
                                 are working with Language Integrated Queries
                                 (LINQ) to XML in Visual Basic 9, this utility
                                 can significantly improve your editing
                                 experience by adding XML schemas (.xsd files)
                                 to your project that add IntelliSense for XML
                                 properties.</description>
                    <pubDate>Mon, 17 Sep 2007 23:59:43 GMT</pubDate>
                  </item>
                  <item>
                    <title>Line and Shape Controls in the Power Packs</title>
                    <description>Download the latest version of the Visual
                                 Basic 2005 Power Packs which now includes Line
                                 and Shape controls that enable you to draw
                                 lines, ovals, and rectangles on forms and
                                 containers at design time making it much easier
                                 to enhance the look of your user
                                 interface.</description>
                    <pubDate>Mon, 20 Aug 2007 20:24:25 GMT</pubDate>
                  </item>
                </channel>
              </rss>
 
   VB中你可以使用LINQ语法查询XML就像你在SQL Server或者其它数据库上查询数据和数据集合一样。不同之处在于你如何去查找XML 元素和属性。在VB中有些XML 的特性允许你使用类数据对象那样查找XML 的元素和属性。
   举例来说,要查找之前创建的XDocument对象中的<channel>元素,你只要编写一行简单的代码:rss.<rss>.<channel>.尽管这看起来只是查找出一个<channel>元素,但事实上代码返回的是<rss>对象中所有<channel>子元素的集合。这样你就能通过Index来查找特定的<channedl>元素,或者就像我们在标题中看的那样通过执行LINQ查询所有的元素集合。为了能查找XML元素的任意子元素,可以使用descendant语法"...",示例rss.<rss>...<title>.为了查找属性,可以使用attribute语法"@,示例rss.<rss>.@version。这些为了在XDocumentXElement中查找元素和属性的语法被称之为XML Axis属性。
注意:在VBXML特性包含了XML智能感知,它可以在我们编码过程中使用XDocumentXElement这些LINQ to XML对象时提供它们的XML属性以及子元素列表。要启用XML智能感知只需将XML结构定义(XSD)文件添加到你的项目中。VB中的XML工具会在你没有XSD文件的前提下,通过XML文件的内容创建一个XSD文件。更多信息,请看XML IntelliSense in Visual Basic
   通过在VB中使用XML Axis属性,我们可以使用LINQ来查询XML内容。举例来说,以下的查询在RSS feed中执行了一次搜索,搜索的内容是<item><title><description>元素。添加代码到Sub Main中,并将其置于XDocument对象之后。
    Dim itemList1 = From item In rss.<rss>.<channel>.<item> _
                    Where item.<description>.Value.Contains("LINQ") Or _
                          item.<title>.Value.Contains("LINQ")
 
    Console.WriteLine("Items containing 'LINQ'" & vbCrLf)
    For Each item In itemList1
      Console.WriteLine(vbTab & "Title: " & item.<title>.Value)
      Console.WriteLine(vbTab & "Description: " & item.<description>.Value)
      Console.WriteLine()
    Next
 
   注意示例代码的搜索是从XML元素的Value属性寻找匹配值。Value属性将从XML元素中返回的内容做为字符串。如果不这样处理的话,XML Axis属性将会对应元素名称的元素集合。但是当搜索XML属性时,你并不需要指定Value属性。这种情况下,XML Axis属性默认返回第一个匹配属性的Value.
   由于Value属性总是返回字符串,你或许需要将其转化成其它指定的类型来执行你的查询。举例来说,以       下代码示例搜索的是过去20天发布的RSS。添加代码到Sub Main末尾。
    Dim itemList2 = From item In rss.<rss>.<channel>.<item> _
                    Let pubDate = DateTime.Parse(item.<pubDate>.Value) _
                    Where pubDate >= _
                          DateTime.Now.Subtract(New TimeSpan(20, 0, 0, 0)) _
                    Select item
 
    Console.WriteLine("Items published in the last 20 days" & vbCrLf)
    For Each item In itemList2
      Console.WriteLine(vbTab & "Title: " & item.<title>.Value)
      Console.WriteLine(vbTab & "Publish Date: " & item.<pubDate>.Value)
      Console.WriteLine()
    Next
 
    按下F5查看代码运行情况。
 

posted on 2008-10-29 11:25:07 by VBCTI  评论(2) 阅读(3228)

VB XML 手册2:子代和父代

[原文作者]Doug Rothaus
 
 
   这个手册主要是讲,在XML文件中怎样使用Visual BasicXML轴属性和LINQ toXML对象访问子和父元素。
 
 
   Visual Basic提供XML轴属性,可以很容易的引用子节点和标记。这是使用XML经常会遇到的情况。你可能会需要引用下一级元素在XML的数据层次上的不同层级上出现。那样的话,你可以使用XML 子代轴属性
 
   XML子代轴属性是用三个跟随着你想引用的XML元素的周期来定义的。例如,在手册1中用到的AdventureWorks contacts source文件中(你可以从手册1下载这个XML文件和相关的模式)。里面有个<AdditonalContactInfo>元素,包括了关于联系方式例如电话号码,发货和帐单地址等等的信息。在<AdditionalContactInfo>元素里面的电话号码可以出现在这个元素值里面的任何位置。最终,你就可以用XML子代轴属性来引用电话号码这个元素。例如:
 
Dim xmlDoc = XDocument.Load("AWContacts.xml")
 
' Return all descendant <telephoneNumber> elements
Dim telephoneNumbers = _
 xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:telephoneNumber>
 
   XML子代轴属性返回XML数据层次中所有符合元素的集合。我们看一个更加完整的例子。下面的方法用XML子代轴属性返回一串联系名字和电话号码。
 
Imports <xmlns="http://SampleSchema/AWContacts">
Imports <xmlns:s="sample-output">
Imports <xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo">
Imports <xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
 
Public Class Recipe2
 
 Public Function GetPhoneNumbers(ByVal xmlDoc As XDocument) As XElement
    Return <s:ContactPhoneNumbers>
             <%= From contact In xmlDoc.<Contacts>.<Contact> _
                 Where contact.<aci:AdditionalContactInfo>...<act:telephoneNumber>.<act:number>.Count > 0 _
                 Select <s:Contact>
                          <s:Name><%= contact.<FirstName>.Value & " " & _
                                      contact.<LastName>.Value %></s:Name>
                          <s:PhoneNumbers>
                            <%= From number In _
                                contact.<aci:AdditionalContactInfo>...<act:telephoneNumber>.<act:number> _
                                Select <s:Number><%= number.Value %></s:Number> _
                            %>
                          </s:PhoneNumbers>
                        </s:Contact> _
             %>
           </s:ContactPhoneNumbers>
 End Function
End Class
 
XSLT中,同样传输是这样的:
 
<?xmlversion='1.0'?>
<xsl:stylesheet
 version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:aw="http://SampleSchema/AWContacts"
 xmlns:s="sample-output"
 xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo"
 xmlns:crm="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactRecord"
 xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
<xsl:outputmethod="xml"indent="yes" />
 
 <xsl:templatematch="aw:Contacts">
    <s:ContactPhoneNumbers>
      <xsl:apply-templatesselect="aw:Contact" />
    </s:ContactPhoneNumbers>
 </xsl:template>
 
 <xsl:templatematch="aw:Contact">
    <xsl:iftest="count(aci:AdditionalContactInfo//act:telephoneNumber/act:number) > 0">
      <s:Contact>
        <s:Name>
          <xsl:value-ofselect="aw:FirstName"/>
          <xsl:text> </xsl:text>
          <xsl:value-ofselect="aw:LastName"/>
        </s:Name>
        <xsl:apply-templatesselect="aci:AdditionalContactInfo" />
      </s:Contact>
    </xsl:if>
 </xsl:template>
 
 <xsl:templatematch="aci:AdditionalContactInfo">
    <s:PhoneNumbers>
      <xsl:for-eachselect=".//act:telephoneNumber/act:number">
        <s:Number>
          <xsl:value-ofselect="."/>
        </s:Number>
      </xsl:for-each>
    </s:PhoneNumbers>
 </xsl:template>
 
</xsl:stylesheet>
 
   一个比较重要的需要注意的是,当子元素的位置总是一样的时候就不应该用XML子代轴属性。如果用XML 子轴属性去引用XML数据层次中的特定位置,你的代码会比使用XML子代轴属性去搜索整个XML数据层次中符合的项执行起来好很多。
 
父代
 
   XML轴属性使向下搜索XML数据层次快并且简单。然而,如果你想向上搜索XML数据层次呢?那样的话,你可以用XNode类的Ancestors方法。因为XML直接把LINQ to XML类的功能暴露出来,你可以用那些LINQ to XML类附带的功能。
 
   Ancestors()这个方法在XML数据层次中向上搜索那些符合给出元素名字的元素。传给Ancestors方法的元素的名字必须包括元素XML命名空间。可以用GetXmlNamespace operator返回基于给出的XMLprefix的命名空间。对于默认的XML命名空间,调用GetXmlNamespace操作不传入prefix。如果你在同样的XML命名空间中搜索一个已经引用的元素,可以用Name.Namespace属性访问已经引用的元素的命名空间。
 
   例如,下面的例子从AdventureWorks Contacts得到一串<telephoneNumber>元素(实际上,任何一个类型的phoneNumberType定义在ContactType.xsd文件里面),然后返回一串电话号码和跟电话号码相关的名字。这个例子调用Ancestors()方法去向上搜索XML数据层次中的<Contact>节点。
 
Imports <xmlns="http://SampleSchema/AWContacts">
Imports <xmlns:s="sample-output">
Imports <xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo">
Imports <xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
 
Public Class Recipe2
 
 Public Function GetPhoneList(ByVal numberList As IEnumerable(Of XElement)) As XElement
    Return <s:PhoneNumberList>
             <%= From number In numberList _
                 Let contact = number.Ancestors(GetXmlNamespace() + "Contact") _
                 Select <s:PhoneNumber>
                          <s:Number type=<%= number.Name.LocalName %>>
                            <%= number.<act:number>.Value %>
                          </s:Number>
                          <s:Owner><%= contact.<FirstName>.Value & " " & _
                                       contact.<LastName>.Value %></s:Owner>
                      </s:PhoneNumber> _
             %>
           </s:PhoneNumberList>
 End Function
End Class
 
XSLT中,同样的传输是这样的:
 
<?xmlversion='1.0'?>
<xsl:stylesheet
 version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:aw="http://SampleSchema/AWContacts"
 xmlns:s="sample-output"
 xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo"
 xmlns:crm="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactRecord"
 xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
<xsl:outputmethod="xml"indent="yes" />
 
 <xsl:templatematch="aw:Contacts">
    <s:PhoneNumberList>
      <xsl:apply-templatesselect="aw:Contact//act:telephoneNumber" />
    </s:PhoneNumberList>
 </xsl:template>
 
 <xsl:templatematch="aw:Contact//act:telephoneNumber">
    <s:PhoneNumber>
      <s:Number>
        <xsl:attributename="type">
          <xsl:value-ofselect="name()" />
        </xsl:attribute>
        <xsl:value-ofselect="./act:number"/>
      </s:Number>
      <s:Owner>
        <xsl:value-ofselect="ancestor::aw:Contact/aw:FirstName"/>
        <xsl:text> </xsl:text>
        <xsl:value-ofselect="ancestor::aw:Contact/aw:LastName"/>
      </s:Owner>
    </s:PhoneNumber>
 </xsl:template>
</xsl:stylesheet>

posted on 2008-10-28 14:16:47 by VBCTI  评论(3) 阅读(2465)

VB XML手册3:身份转换

[原文链接]VB XML Cookbook, Recipe 3: Identity Transforms         
 
   XSLT中的身份转换顾名思义就是将一种元素或属性的格式替换为另一种新的格式。尤其当处理一些结构中比较灵活的XML标识时,格式转换就显得特别关键。就像新闻的文章结构,不管标题在文章的什么地方被提及,都会被突显为斜体。但是,在XML样式并不用为文章指定斜体格式而是指定表明标题类型的词语,这样不同的转换就可以用自己的方式来处理词语。当你想要将这样的XML文档转换成易读的格式(如HTML),你除了要完整地保留原文内容,还需要保持突显的词语周围的文本结构。就比如说:
<Paragraph sequenceID=”1”>This week a remastered version of the movie <title type=”movie”>Raider’s of the Lost Ark</title> was released.</Paragraph>
转换为:
<p>本周,重灌版电影 <i>夺宝奇兵</i>将公映。</p>
于是你在浏览器中将看到:
本周,重灌版电影夺宝奇兵将公映。
(注意:其实我也不知道夺宝奇兵这电影是否真的会在这周公映,我只是最近比较关注这事。)
你可以用Visual Basic XML Literals进行一个格式转换,结合XML Axis 属性LINQXML对象的ReplaceWith方法。
   让我们再来看看一个相对更完整的例子。在我们前一节使用过的AdventureWorks源文档中(你可以从秘籍一下载它的XML文档和相关的注释),有个<AdditionalContactInfo>元素,它包含了contactphone numbers, shipping billing addresses信息。为了简单,我们在这就只看<eMail>元素。<eMail>这个元显示在Contacts中所有<AdditionalContactInfo>元素出现的地方这样,你就可以用在秘籍二中讨论过的XML Descendant axis 属性来查看到所有对<eMail>引用。接着,你对这个查询结果反复使用替换方法,将每个<eMail>元素替代为一个新的身份。例如:
 Dim emails = (From email In _
    xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:eMail>).ToList()
 
 For Each email In emails
    TransformEmail(email)
 Next
   在这个例子里,输出的文档格式为HTML其中的<eMail>元素被替代为一个固定的指向E-mail地址的发送邮件的链接元素在上面的那段代码中可以看到它将<eMail> Xelement 作为参数传递给了这个做替换的TransformEmail函数
 Private Sub TransformEmail(ByVal email As XElement)
    Dim emailHtml = <div class="Email">
                      <a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>
                        <%= email.<act:eMailAddress>.Value %>
                      </a>
                    </div>
 
    email.ReplaceWith(emailHtml)
 End Sub
 
下面我学以致用,来创建一个HTML文档。
Imports <xmlns="http://SampleSchema/AWContacts">
Imports <xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo">
Imports <xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
 
Public Class Recipe3
 
 Public Function GetContactsHtml(ByVal xmlDoc As XDocument) As XElement
 
' 用发送邮件的链接来替换E-Mail地址
    Dim emails = (From email In xmlDoc.<Contacts>.<Contact>.<aci:AdditionalContactInfo>...<act:eMail>).ToList()
 
    For Each email In emails
      TransformEmail(email)
    Next
 
' 创建HTML文档
    Return <html>
             <body>
               <table border="1">
                 <%= From contact In xmlDoc.<Contacts>.<Contact> _
                     Select <tr>
                              <td valign="top">
                                <%= contact.<FirstName>.Value & " " & contact.<LastName>.Value %>
                              </td>
                              <td valign="top">
                                <%= contact.<aci:AdditionalContactInfo> %>
                              </td>
                            </tr> _
                 %>
               </table>
             </body>
           </html>
 End Function
 
 
 Private Sub TransformEmail(ByVal email As XElement)
    Dim emailHtml = <div class="Email">
                      <a href=<%= "mailto:" & email.<act:eMailAddress>.Value %>>
                        <%= email.<act:eMailAddress>.Value %>
                      </a>
                    </div>
    email.ReplaceWith(emailHtml)
 End Sub
End Class
XSLT中,相同的转换如下:
<?xmlversion='1.0'?>
<xsl:stylesheet
 version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
 xmlns:aw="http://SampleSchema/AWContacts"
 xmlns:aci="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactInfo"
 xmlns:act="http://schemas.microsoft.com/sqlserver/2004/07/adventure-works/ContactTypes">
 <xsl:outputmethod="html"indent="yes"/>
 <xsl:templatematch="aw:Contacts">
    <html>
       <body>
         <tableborder="1">
           <xsl:apply-templatesselect="aw:Contact" />
         </table>
      </body>
    </html>
 </xsl:template>
 
 <xsl:templatematch="aw:Contact">
    <tr>
      <tdvalign="top">
        <xsl:value-ofselect="aw:FirstName"/>
        <xsl:text> </xsl:text>
        <xsl:value-ofselect="aw:LastName"/>
      </td>
      <tdvalign="top">
        <xsl:apply-templatesselect="aci:AdditionalContactInfo" />
      </td>
    </tr>
 </xsl:template>
 
 <xsl:templatematch="aci:AdditionalContactInfo">
    <divclass="AdditionalInfo">
      <xsl:copy>
        <xsl:apply-templatesselect="@* | node()" />
      </xsl:copy>
    </div>
 </xsl:template>
 
 <xsl:templatematch="act:eMail">
      <divclass="Email">
        <a>
          <xsl:attributename="href">
            <xsl:text>mailto:</xsl:text>
            <xsl:value-ofselect="act:eMailAddress" />
          </xsl:attribute>
          <xsl:value-ofselect="act:eMailAddress" />
        </a>
      </div>
 </xsl:template>
</xsl:stylesheet>
 
按元素类型分类:
   在Visual Basic里它可以按照元素的类型将转换分类。例如,我们已经在上面的例子中使用过定义好了一个address、一个e-mail和一个phone number类型的ContactTypes.xsd文件。那个文件还能识别三种不同的address类型名和五种不同的phone number类型名。XSLT中,你可以为每一个address类型创建一个模板为了直观表示,三个不同的address名字以三种不同的并且能表达相关信息的模板结尾,保持一个原则:格式统一。在Visual Basic中,你可以很容易的创建一个关于所有不同的address phone number元素的查询,然后将其传到作为你XSLT模板的那个VB替换函数里。例如:
    Dim addresses = (From addr In info...<act:homePostalAddress>).Union( _
                     From addr In info...<act:physicalDeliveryOfficeName>).Union( _
                     From addr In info...<act:registeredAddress>).ToList()
 
    For Each address In addresses
      TransformAddress(address)
    Next
 
    ...
 
    Private Sub TransformAddress(ByVal address As XElement)
      Dim addressHtml = _
        <div class="Address">
          <%= address.<act:Street>.Value %><br/>
          <%= address.<act:City>.Value & ", " %>&#32;
          <%= address.<act:StateProvince>.Value %>&#32;
          <%= address.<act:PostalCode>.Value %><br/>
          <%= address.<act:CountryRegion>.Value %><br/>
          <%= _
            GetSpecialInstructions(address.<act:SpecialInstructions>.ToList()) %>
        </div>
 
      address.ReplaceWith(addressHtml)
    End Sub

posted on 2008-10-28 14:15:10 by VBCTI  评论(5) 阅读(2345)

LINQ在VB中是如何对Dataset工作的

 [原文作者]Jonathan Aneja

[原文链接]How LINQ to Dataset works in VB

   LINQ的核心要求任何数据源能够被查询,这基本上意味着它必须实现IEnumerable接口(实际情况要更复杂一点,详细的说明请参见Visual Basic 9.0语言规范之11.21.2一节,<Visual Basic 9.0 Language Specification >)。现在,当使用LINQDataset进行工作时我们就有了一个问题:DataTable没有实现IEnumerable,那么我们该如何来对它进行查询?
   如我们昨天<yesterday>所见,Visual Studio 2008中包含有一个名为 System.Data.DataSetExtensions.dllassembly,它定义了一个叫做AsEnumberable()的扩展方法<extension method>。这里是这个方法看上去的样子:
    <Extension()> _
    Public Function AsEnumerable(source As DataTable) As EnumerableRowCollection(Of DataRow)
   基本上,该方法接收一个DataTable,然后返回实现了IEnumberable(Of T)某个东西,然后LINQ就可以用它来构造标准的查询操作符了。因此,你需要做的是 import System.Data(默认情况下项目模板就会把这个做好),然后你就可以通过调用AsEnumberable()来使用LINQ对一个Dataset进行查询了:
   Dim customers = TestDS.Tables("Customers")
 
   Dim franceCustomers = From cust In customers.AsEnumerable() _
                          Where cust!Country = "France" _
                          Select cust
 
   现在,关于LINQVB中的工作方式的有趣事情之一是:你其实并不真正需要为上面的代码去显式的调用AsEnumberable()以通过编译。即使DataTable没有实现IEnumerable,编译器会通过自动查找某些可以将之转换成某种可被查询的东西的方法来帮助你解决这个问题。当VB编译器碰到一个要对某个未实现IEnumberableIEnumerable(Of T)IQueryable、或IQueryable(Of T)的类型进行工作的LINQ查询时,它会依次执行以下工作:
1. 检查该类型上是否有一个标准的Select方法可用;
2. 检查该类型上是否有一个名为AsQueryable的实例方法,该方法应能返回一个可查询的类型;
3. 检查该类型上是否有一个在作用期内的扩展方法AsQueryable,该方法应能返回一个可查询的类型;
4. 检查该类型上是否有一个名为AsEnumerable的实例方法,该方法应能返回一个可查询的类型;
5. 检查该类型上是否有一个在作用期内的扩展方法AsEnumerable,该方法应能返回一个可查询的类型;
 
    如果编译器在任一步中找到了一个匹配的方法,它就会插入一个对该方法的调用。当System.Data这个namespaceImport了以后,编译器在第5步为DataTable找到了一个匹配的,然后为你插入对AsEnumerable的调用。结果,你就可以象这个从101 LINQ Sampleshttp://msdn.microsoft.com/en-us/vbasic/bb688088.aspx)中拿来的示例那样编写对DataSet操作的LINQ:
Dim customers = TestDS.Tables("Customers")
 
Dim franceCustomers = From cust In customers _
                              Where cust!Country = "France" _
                              Select cust
   这里的意思,是你可以通过提供一个能够返回某种可查询的东西的AsEnumerable扩展方法来使得你的类型可以被LINQ使用。
   注意,对于强类型的Dataset,你不需要去调用AsEnumerable,因为它们继承自TypedTableBase(Of T),而后者则实现了IEnumerable。这里VS2008中的一个新类型;在VS2005中,Dataset Designer将会生成从DataTable继承的代码,然后自己去显示的实现IEnumerable

posted on 2008-10-28 14:10:20 by VBCTI  评论(5) 阅读(2428)

LINQ 菜谱, 菜单4: 查找给定程序集中所有复杂类型 (Kit George)

[原文作者]Kit George

[原文链接]LINQ Cookbook, Recipe 4: Find all complex types in a given assembly (Kit George)

 

准备材料:

-        Visual Studio 2008 (Beta2 或更高版本)

        一个需要分析的程序集 (在这个例子中, 我们使用了用来存储字符串的mscorlib.dll 程序集)

-         一个 复杂类型的字义.  在这个例子中, 复杂类型包含10以上public方法,而且至少有一个方法具有3个以上的参数.

 

类别: LINQ-To-Objects, LINQ and types, LINQ and WinForms

 

制作方法:

-         打开 Visual Studio 2008,  点击菜单文件/新建项目”. 找到并双击 ”Windows 窗体应用程序  图标.

-        拖放一个 Listbox 到窗体上,调整Listbox的高度. 再拖放一个按钮到窗体上

       双击这个按钮, 并将以下代码添加到按钮的事件处理函数中:

 

Dim q = From type In System.Reflection.Assembly.GetAssembly( _

                  GetType(String)).GetTypes(), _

                  m In type.GetMethods() _

        Where type.IsPublic _

                  AndAlso type.GetMethods.Length > 10 _

                  AndAlso m.GetParameters.Length > 3 _

        Select type Distinct 

 

ListBox1.Items.AddRange(q.ToArray)

posted on 2008-01-08 11:00:00 by VBCTI  评论(3) 阅读(3906)

LINQ 菜谱, 菜单3: 在给定的范围内找到所有的素数(Jonathan Aneja)

[原文作者]Jonathan Aneja

[原文链接]LINQ Cookbook, Recipe 3: Find all the prime numbers in a given range (Jonathan Aneja)

 

准备材料:

-         Visual Studio 2008 (Beta2 或更高版本

 

类别: LINQ-To-Objects 

 

制作方法:

-         打开 Visual Studio 2008,  点击菜单 文件/新建项目”.  找到并双击 控制台应用程序  图标.

-         添加以下代码

     

Module Module1

 

    Sub Main()

        Dim primes = GetPrimesInRange(30, 50)

 

        For Each n In primes

            Console.WriteLine(n)

        Next

        Console.ReadLine()

    End Sub

 

    Function GetPrimesInRange(ByVal low, ByVal high) As IEnumerable(Of Integer)

        Dim range = Enumerable.Range(1, high)

        Return From num In range _

               From num2 In range _

               Where num Mod num2 = 0 _

               Group By num Into Count() _

               Where Count = 2 And num > low _

               Select num

    End Function

 

End Module

 

-         这里用到的grouping (分组), 只是为了选择那些有且仅有两个因子的数字. (例如: 它们是素数)

posted on 2008-01-07 10:29:00 by VBCTI  评论(1) 阅读(3870)

LINQ 菜谱, 菜单2: 查找短语中所有大写字母开头的单词并按长度排序 (然后是按字母顺序) (Kit George)

[原文作者]Kit George

[原文链接]LINQ Cookbook, Recipe 2: Find all capitalized words in a phrase and sort by length (then alphabetically) (Kit George)

 

准备材料:

        Visual Studio 2008 (Beta2 或更高版本)

        一些需要搜索的字符串

 

类别:  LINQ-To-Objects, LINQ and string, LINQ and WinForms

 

制作方法:

        打开Visual Studio 2008,点击菜单文件/新建项目”,找到并双击 ”Windows 窗体应用程序图标

-         拖放一个Listbox 到窗体上,调整的高度,然后拖放一个按钮到窗体上

        双击这个按钮,并将下面的代码添加到按钮的事件处理函数中:

 

        Dim text = "Good morning everyone. I'd like to welcome " & _

                   "you to today's presentation on LINQ. My " & _

                   "name is Kit George and I'm a Program Manager " & _

                   "for Microsoft, on the Visual Basic team. You " & _

                   "might be wondering where my accent is from? " & _

                   "Well, I hail from a small country called New " & _

                   "Zealand but it sure is great to be here in  " & _

                   "Atlanta today!"

 

        Dim capitalWords = _

                      From word In text.Split( _

                      New Char() {",", ".", "!", " "}, _

                      StringSplitOptions.RemoveEmptyEntries) _

                      Where word(0) = Char.ToUpper(word(0)) _

                      Order By word.Length, word

 

        ListBox1.Items.AddRange(capitalWords.ToArray())

 

  修改你要搜索的字符串。如果字符串保存在一个文件中,  可以使用 My.Computer.FileSystem.ReadAllText 获取并保存到字符串变量中。

posted on 2007-12-26 11:20:00 by VBCTI  评论(1) 阅读(4403)

LINQ 菜谱, 菜单1: 改变Windows 窗体上所有标签的字体

[原文作者]: Kit George

[原文链接]: LINQ Cookbook, Recipe 1: Change the font for all labels on a windows form

 

我们打算启动一个LINQ菜谱项目,并逐步将它完成。这个项目的目的是为你在编程和用查询时遇到的一些特殊情况提供解决的方案。我再也想不出一个更好的方式,来关联你能利用LINQ所做的每件事情了。

 

这个系列项目主要是为您展示LINQ可以做到的各式各样的事情。像任何一本好菜谱一样,我们将为这本菜谱做分类,那样你之后就可以很容易地查询。但是不要希望一个特定的菜谱会有大量的描述,尽管它会包含重现步骤。请尽管向我们要求任何你想要看到的菜谱,而我们将尽可能多地创造新的。

 

材料:

-      Visual Studio 2008 (Beta2 或更高版本)

 

类别:

-      LINQ-To-ObjectsLINQ with Windows FormsLINQ with controlsLabel

 

制作方法:

-      打开 Visual Studio 2008,点击文件/新建项目找到并双击Windows窗体应用程序图标。

-      增加窗体的大小,使其足够容纳一定数量的控件。

-      从工具箱中拖放几个控件到窗体上,确保至少添加3-4个标签控件。

      o  我个人比较喜欢的是两个分组控件(GroupBox)在文本框(TextBox)前放两个标签(Label)(标签在分组控件之上)并在每个分组控件中放一对单选框(RadioButton)控件。这样可以确保测试递归代码。

-      添加一个按钮到窗体上并把它的Text属性改为Go双击这个按钮,然后修改事件处理代码并添加下面的方法:

 

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

        For Each label In GetLabels(Me)

            label.Font = New Font("Comic Sans MS", 12, _

                                  FontStyle.Bold Or FontStyle.Underline)

        Next

    End Sub

 

    Private Function GetLabels(ByVal sourceControl As Control) _

                               As IEnumerable(Of Control)

        If sourceControl.Controls.Count > 0 Then

            Dim labels = From cont As Control In sourceControl.Controls _

                         Where TypeOf cont Is Label _

                         Select cont

 

            For Each c As Control In sourceControl.Controls

                labels = labels.Union(GetLabels(c))

            Next

 

            Return labels

        End If

 

        Return New List(Of Control)

    End Function

 

-     修改New Font所在的那行代码为你想要的字体样式。

posted on 2007-09-26 10:39:00 by VBCTI  评论(11) 阅读(4573)

Powered by: Joycode.MVC引擎 0.5.2.0