在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
 
 

基于WPF LINQ的动态数据模型

[原文作者]:Beth Massi

[原文链接]:Dynamic Data Entry with WPF and LINQ

   上一节我讲到用XML编写动态WPF UI, 尽管这里的UI是动态生成的,但仍有一处不尽人意的地方,就是我们采用的是一个具体的对象customer(来源于LINQ to SQL classes)。 如果想要我们的应用程序既能够动态生成WPF UI 又能动态地编辑处理数据库里的任何表数据,就需要进一步参数化程序代码 ——而不仅限于customer。 这样只需要修改数据库表的定义而不用更新对象模块和重新编译代码。

   实现方法是试图在运行时加载,处理一个简单的无类型的(或者称作通用类型)DataTable,并运用XML literals特性 –为SqlDataAdapter生成SELECT和UPDATE语句。要注意一件事情,这个过程必须依赖数据库验证规则。因此这种方式只能用于处理非常简单的表(缩略语.维护表)。

 
   我需要创建一个强类型(指定类型)的DataTable包含表的定义,用来取代LINQ to SQL模板。这个datatable存储了字段的元数据(字段名,类型,等等)以便我们对表的操作。右键点击项目工程,添加新模板,选择DataSet template。我将它命名为TableSchemaDataSet,然后拖拽Server Explorer-〉Data Connection –〉Northwind Database下的存储过程GetTableSchema(这个已经在前面章节中生成好了)到DataSet 设计器上,自动为我们生成所需的DataTable,重命名为TableSchema,并保存。

 
   接下来精彩的部分。因为我们并不想知道当前表的定义,我们会加载一个通用类型的DataTable生成一个动态的WPF 窗口。DataTable和DataSet都建好了,还有两个步骤需要手动设置。

   首先定义一个公共属性(Public Property)存放表名,同时设置默认值为“Shippers”,然后创建一批私有变量引用所需的ADO.NET对象。

Imports <xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation">

Imports <xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

Imports System.Windows.Markup

Imports System.Data.SqlClient

Imports System.Data

 

Partial Public Class Window2

    ‘This is the metadata table we created in the DataSet Designer

    Private TableSchema As New TableSchemaDataSet.TableSchemaDataTable

    ‘ADO.NET objects used to load and save the table we’re editing

    Private TableDataAdapter As New SqlDataAdapter

    Private TableConnection As New SqlConnection(My.Settings.NorthwindConnectionString)

    Private Table As DataTable

    ‘This is the key field used in searching for a row in this example

    Private PKField As TableSchemaDataSet.TableSchemaRow

 

    ‘This property can be set before the Form.Show() to edit any table

    Private m_tableName As String = "Shippers"

    Public Property TableName() As String

        Get

            Return m_tableName

        End Get

        Set(ByVal value As String)

            m_tableName = value

        End Set

    End Property

 


    现在UI定义好了,然后设置主键字段(TableSchemaDataRow 对象)以便我们点击Find按钮时运用到Update和Select操作。

Private Sub Window1_Loaded() Handles MyBase.Loaded

    Try

        ‘Get the schema of the database table we want to edit

        Dim taSchema As New TableSchemaDataSetTableAdapters.TableSchemaTableAdapter

        taSchema.Fill(Me.TableSchema, Me.TableName)

 

        ‘Create the DataTable that will hold the record we’re editing

        Me.Table = New DataTable(Me.TableName)

        Me.Title = Me.TableName

        Me.LoadUI()

        Me.SetPrimaryKey()

        Me.SetUpdateCommand()        

    Catch ex As Exception
        MsgBox(ex.ToString)
        Me.Close()
    End Try
End Sub
 
Private Sub LoadUI()
 
    Dim UI = <Grid xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 Name="Grid1">
                 <Grid.ColumnDefinitions>
                     <ColumnDefinition Width="100*"/>
                     <ColumnDefinition Width="200*"/>
                 </Grid.ColumnDefinitions>
                 <StackPanel Name="StackLabels" Margin="3">
                     <%= From column In Me.TableSchema _
                         Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _
                         Select <Label
                                    Height="28"
                                    Name=<%= column.ColumnName & "Label" %>
                                    HorizontalContentAlignment="Right">
                                    <%= column.ColumnName %>:</Label> %>
                 </StackPanel>
                 <StackPanel Grid.Column="1" Name="StackFields" Margin="3">
                     <%= From column In Me.TableSchema _
                         Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _
                         Select GetUIElement(column) %>
                 </StackPanel>
             </Grid>

     Me.DynamicContent.Content = XamlReader.Load(UI.CreateReader())
 
End Sub
 
Private Function GetUIElement(ByVal columnInfo As TableSchemaDataSet.TableSchemaRow) As XElement
    Select Case columnInfo.DataType.ToLower
        Case "datetime", "int", "smallint", "money"
            Return <TextBox
                       Height="28"
                       Name=<%= "txt" & columnInfo.ColumnName %>
                       Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/>
        Case "bit"
            Return <CheckBox
                       HorizontalContentAlignment="Left"
                       Name=<%= "chk" & columnInfo.ColumnName %>
                       IsChecked=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>>
                       <%= columnInfo.ColumnName %>
                   </CheckBox>
        Case Else
            Return <TextBox
                       Height="28"
                       Name=<%= "txt" & columnInfo.ColumnName %>
                       MaxLength=<%= columnInfo.MaxLength %>
                       Text=<%= "{Binding Path=" & columnInfo.ColumnName & "}" %>/>
    End Select
End Function

 

Private Sub SetPrimaryKey()    这样我们就可以通过设置TableName,动态生成UI,通过Find找到想要的数据,并可以修改保存数据。而且可以不用重新编译修改数据库表结构。
 

    ‘Grab the Primary Key column of the table we want to edit so we can use it in the search

    Me.PKField = (From column In Me.TableSchema Where column.IsPrimaryKey = 1).FirstOrDefault()

End Sub

Private Sub btnFind_Click() Handles btnFind.Click

    If Me.txtSearch.Text <> "" Then

        Try

            ‘Create the SELECT command

            Dim cmdText = <s>

                          SELECT * FROM <%= Me.TableName %>

                          WHERE <%= Me.PKField.ColumnName %> =

                                <%= If(Me.PKField.DataType.Contains("char"), _

                                    "’" & Me.txtSearch.Text & "’", _

                                    Me.txtSearch.Text) %>

                          </s>.Value

 

            Dim cmd As New SqlCommand(cmdText, Me.TableConnection)

            Me.Table.Clear()

            Me.TableDataAdapter.SelectCommand = cmd

            Me.TableDataAdapter.Fill(Me.Table)

 

            Me.DataContext = Me.Table

            Dim view = CollectionViewSource.GetDefaultView(Me.Table)

            view.MoveCurrentToFirst()

 

        Catch ex As Exception

            MsgBox(ex.ToString)

            Me.DataContext = Nothing

        End Try

    Else

        Me.DataContext = Nothing

    End If

End Sub

 

Private Sub SetUpdateCommand()

    ‘Set the UpdateCommand so that we can save edited records in the table

    Dim cmdText = <s>

                  UPDATE <%= Me.TableName %>

                  SET <%= From column In Me.TableSchema _

                          Where column.IsPrimaryKey = 0 AndAlso column.DataType <> "timestamp" _

                          Select <c>

                                     <%= column.ColumnName %> = @<%= column.ColumnName %>

                                     <%= If(Me.TableSchema.Rows.IndexOf(column) < _

                                            Me.TableSchema.Rows.Count – 1, ", ", "") %>

                                 </c>.Value %>

                  WHERE <%= Me.PKField.ColumnName %> = @<%= Me.PKField.ColumnName %>

                        <%= From column In Me.TableSchema _

                            Where column.IsPrimaryKey = 0 AndAlso column.DataType = "timestamp" _

                            Select <c>

                                     AND <%= column.ColumnName %> = @<%= column.ColumnName %>

                                   </c>.Value %>

                  </s>.Value

 

    Dim cmd As New SqlCommand(cmdText, Me.TableConnection)

    Dim p As SqlParameter

 

    For Each column In Me.TableSchema

        If column.IsPrimaryKey = 0 AndAlso column.DataType = "timestamp" Then

            ‘Note: It’s recommended to use a TimeStamp column in your tables for concurrency checking

            p = New SqlParameter("@" & column.ColumnName, SqlDbType.Timestamp)

            p.SourceVersion = DataRowVersion.Original

            p.SourceColumn = column.ColumnName

            cmd.Parameters.Add(p)

        Else

            p = New SqlParameter("@" & column.ColumnName, _

                                 CType([Enum].Parse(GetType(SqlDbType), column.DataType, True), SqlDbType))

            p.SourceColumn = column.ColumnName

            p.SourceVersion = DataRowVersion.Current

            cmd.Parameters.Add(p)

        End If

    Next

 

    Me.TableDataAdapter.UpdateCommand = cmd

End Sub

 

    然后我们可以在Loaded事件处理函数中编写逻辑代码,加载元数据,创建和加载XAML显示UI,再然后就是处理TableDataAdaper上的UpdateCommand。

Northwind遇到虚拟地球——利用Linq生成VE(虚拟地球)地图

[原文作者]: Beth Massi

[原文链接]: Northwind Meets Virtual Earth – Generate VE Maps with LINQ

利用VB9Linq,你可以很容易地从多个数据源之间创建XML文件,这些数据源包括关系型数据、 XML文件和其它任何可查询对象,因为绝大多数流行的系统之间的交互都是以XML的格式进行的,所以应用是很广泛的。在VB9SOAPXAMLHTMLRSS都能够很容易地通过Linq To XML创建。比如, 如果我们想在一个VE生成的地图中显示所有Northwind库中的Customers信息,我们需要怎么做?

VE允许我们传递给它一个RSS文档,文档里面的每个item指定了纬度和经度以便能够绘制出他们在地球上的每个位置;这里有两种不同的格式可以传递,其中之一就是GeoRSS标准,我们需要做的就是通过获取customers表中包含的地址信息中的经纬度来创建这个XML,并且将这个GeoRSS传递给虚拟地球;我们可以利用http://geocoder.us 中的服务来获取在美国的Customers经纬度信息,这个服务能够从许多格式的美国地址(包括Restful RDF)中返回一组对应的经纬度信息,为了从Northwind中的customers表中创建GeoRss,我们可以在Linq查询中使用这个服务。

假设我们在Server Explorer中已经有一个到Northwind库的连接(或者其它的含有地址的数据库也可以),首先添加一个“LINQ TO SQL classes”到你的项目中,将它命名为Northwind.dbml ,并且将Customers表从Server Explorer中拖拉到设计器中,下一步就是在代码文件的头部引入Geo命名空间,因为geo命名空间的http://geocoder.us 服务将返回XML文件,我们将使用到XML文件里的地址信息。 

Imports <xmlns:geo=http://www.w3.org/2003/01/geo/wgs84_pos#>

现在我们可以写一个查询来为customers创建GeoRSS,因为Northwind库中包含的地址绝大多数是编造的,你可以将这些地址改成真实的,或者我们可以选择居住在Oregon (OR)Customers,因为那有一些有效的地址。

Dim db As New NorthwindDataContext

Dim geoRSS = _

<rss xmlns:geo=http://www.w3.org/2003/01/geo/wgs84_pos#>

    <channel>

        <title>Northwind Customer Locations</title>

        <link></link>

        <%= From Customer In db.Customers _

            Let Desc = Customer.Address & “, “ & Customer.City _

            Let Address = Customer.Address & “,” & Customer.PostalCode _

            Where Customer.Country = “USA” AndAlso Customer.Region = “OR” _

            Select <item>

                       <title><%= Customer.ContactName %></title>

                       <description><%= Desc %></description>

                       <%= GetGeoCode(Address).Descendants %>

                   </item> %>

    </channel>

</rss>

在这个查询中我们建立了GeoRSS并且调用了一个使用者,里面定义了函数GetGeoCode,该函数接受customer的地址参数,返回它的经纬度;注意,我们在查询中使用了Let关键字,目的是为descriptionaddress创建查询变量,这些变量在下面建立<item>时将会使用到。如果有结果,函数GetGeoCode将返回一个包含位置信息的XElementXElement的子方法在查询中被回调,为的是在GeoRSS中安放<geo:lat> <geo:long>节点。 

Function GetGeoCode(ByVal address As String) As XElement

    Dim url = “http://geocoder.us/service/rest/?address=” & Server.UrlEncode(address)

    Try

        Dim geo = XElement.Load(url) 

        Return <location>

                   <%= geo.<geo:Point>.<geo:long> %>

                   <%= geo.<geo:Point>.<geo:lat> %>

               </location> 

    Catch ex As Exception

        Return <location></location>

    End Try 

End Function

现在我们已经拥有了GeoRSS,可以将它传递给虚拟地球,以利用它来创建地图。例如,我们可以仅仅创建一个简单的ASP.net应用,并且将创建好的GeoRSS保存到一个Session变量中。默认页面需要有JavaScript代码,我们将利用它把GeoRSS传递到虚拟地球,并且发送一个ID”mymap”<div>块,块中定义了在页面上安放地图的区域。(想了解更多的API信息,可以看一下虚拟地球文档 

<%@ Page Language=”vb” AutoEventWireup=”false”

CodeBehind=”Default.aspx.vb” Inherits=”NorthwindVirtualEarth._Default” %>

<!DOCTYPE html PUBLIC “-//W3C//DTD XHTML 1.0 Transitional//EN”

“http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd”>

<html xmlns=”http://www.w3.org/1999/xhtml” >

<head runat=”server”>

    <title>Northwind Customers on Virtual Earth</title>

    <link href=”style.css” rel=”stylesheet” type=”text/css” />

    <script type=”text/javascript”

        src=”http://dev.virtualearth.net/mapcontrol/mapcontrol.ashx?v=5″>

    </script>

    <script type=”text/javascript”>

        var map = null;

        var layerid=1;

        function GetMap()

        {

            map = new VEMap(‘myMap’);

            map.LoadMap();  

            var l = new VEShapeLayer();

            var veLayerSpec = new VEShapeSourceSpecification(VEDataType.GeoRSS, “georss.aspx”, l);

            map.ImportShapeLayerData(veLayerSpec, null);

        }

     </script>

</head>

<body id=”body” runat=”server” >

   <form id=”form1″ runat=”server”>

   <h1>Northwind Customers on Virtual Earth</h1>

   <div id=’myMap’ style=”position: relative; width: 800px; height: 400px;”>

         <asp:Label ID=”lblStatus” runat=”server” Text=”No items found” Visible=”False”></asp:Label>

   </div>

   </form>

</body>

</html>

Default.aspxVB后台代码只是简单地做了检查,看看是否有任何从geoRSS查询中返回的<item>元素,如果有,则动态地在bodyonload事件里添加调用GetMapjavascript函数代码。 

If geoRSS…<item>.Count > 0 Then

    Session(“georss”) = geoRSS 

    Me.body.Attributes.Add(“onload”, String.Format(“GetMap()”))

Else

    Me.lblStatus.Visible = True

    Session(“georss”) = <rss></rss>

End If

另外一个页面是GeoRss.aspx,是一个空白页,仅仅返回存储在session变量中的GeoRSS,正是javascript方法需要调用的内容。 

Public Partial Class GeoRSS

    Inherits System.Web.UI.Page 

    Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load 

        Dim georss As XElement = CType(Session(“georss”), XElement) 

        Response.ContentType = “text/xml”

        Response.Write(georss.ToString()) 

    End Sub

End Class

这里关键的地方在于一个Linq查询语句中跨越了多个数据源(Northwind库和geocoder.us服务),而且只创建了一个单一的XML文档,它符合GeoRSS标准,将它传递到虚拟地球,用来产生我们的地图。正如你在这个RSS示例中所看到的,利用LinqVB9从多数据源中创建XML是非常简单地一件事。代码附件

如果你对利用WPF和你的数据创建动态地图感兴趣,不要忘记参阅WisniewskiDevCenter featured 中的文章 Create Dynamic Maps with Visual Basic 9.0 and WPF.

从 SQL 到 LINQ, 第7部分: 合并, TOP, 子查询 (Bill Horst)

[原文作者]Bill Horst
[原文地址]Converting SQL to LINQ, Part 7: UNION, TOP, Subqueries (Bill Horst)

    本文假设您已阅读了本系列中此前发表的文章:

          SQL LINQ, Part 1: 基础(Bill Horst)

          SQL LINQ, Part 2: FROM SELECT(Bill Horst)

          Converting SQL to LINQ, Part 3: DISTINCT, WHERE, ORDER BY and Operators

          Converting SQL to LINQ, Part 4: Functions

Converting SQL to LINQ, Part 5: GROUP BY and HAVING

Converting SQL to LINQ, Part 6: Joins

 

    本文中我们将讨论一下合并, TOP 和子查询. 下个星期, 我打算更深入地介绍一下LEFT, RIGHT FULL OUTER JOIN. 如果你想了解关于转换SQLLINQ的其他问题, 请回复本文.

 

合并(UNION)

 

SQL, 一个UNION子句合并两个SELECT查询结果为一个数据集. VB LINQ, 可以通过在一个查询中调用Union方法, 并把第二个查询传递给这个方法可以得到相同的结果. Intersect方法也一样可以这样调用, 它返回的是两个查询的交集. Except方法返回的结果只包括那些在第一个查询中出现但不在第二个查询中出现记录.

 

SQL

SELECT CustomerID ID FROM CustomerTable

UNION

SELECT OrderID ID From OrderTable

 

 

VB

(From Contact In CustomerTable _

 Select ID = Contact.CustomerID).Union(From Shipment In OrderTable _

                                       Select ID = Shipment.OrderID)

 

 

TOP

 

SQLTOP运算符返回一个查询结果的前n. VB LINQ中的Take子句可以达到同样效果. 下面的例子详细描述了Take的功能, 同时使用了一些其他相关的子句.

 

SQL

SELECT TOP 10 * FROM CustomerTable ORDER BY CustomerID

 

 

VB

From Contact In CustomerTable Order By Contact.CustomerID Take 10

 

 

Take/Skip/While

 

    Take子句作用于位于它前面的子句, 并指定一个表示要 或返回多少条结果的数字. 查询结果中其他的记录会被丢弃.

 

    Skip子句指定了一个数字, 表示要忽略查询结果中 多少条记录. 当前面子句的结果被传递给Skip子句后, 它只返回除了前n条记录外的其他结果.

 

    Take While子句指定一个条件, 并从一个查询结果的开头获取记录, 直到这个条件为False. 

 

    Skip While子句指定一个条件, 并从一个查询结果的开头获取记录, 它会忽略前面的记录, 直到这个条件为False.

 

   举几个具体的例子, 下面的几个查询返回如下的结果:

 

VB

 

Dim digits = New Integer() {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

 

Dim AllDigits = From int In digits

 

返回结果:

        0        3        6        9

        1        4        7

        2        5        8

 

 

SKIP

 

Dim SkipFirstTwo = From int In digits Skip 2

 

返回结果:

        2        5        8

        3        6        9

        4        7

 

 

TAKE

 

Dim TakeFirstTwo = From int In digits Take 2

 

返回结果:

        0

        1

 

 

SKIP and TAKE together

 

Dim SkipTwoTakeFive = From int In digits Skip 2 Take 5

 

返回结果:

        2        5

        3        6

        4

 

 

 

SKIP WHILE

 

Dim SkipUntilFour = From int In digits Skip While int Mod 5 <> 4

 

返回结果:

        4        7

        5        8

        6        9

 

 

TAKE WHILE

 

Dim TakeUntilThree = From int In digits _

                     Take While int = 0 OrElse int Mod 3 <> 0

 

返回结果:

        0        2

        1

 

 

子查询

 

    SQLSELECT语句可以包含子查询, 就是指一个查询使用了另一个查询的结果. VB LINQ, 子查询可以出现在任何允许使用LINQ表达式的地方, SQL子查询一样, 你可以使用括号来避免语法中的歧义.

 

SQL

SELECT OrderID, OrderDate

FROM OrderTable

WHERE CustomerID = (SELECT CustomerID

                    FROM CustomerTable

                    WHERE City = “Seattle”)

 

 

VB

From Shipment In OrderTable _

Where (From Contact In CustomerTable _

       Where Contact.City = “Seattle” _

       Select Contact.CustomerID).Contains(Shipment.CustomerID) _

Select Shipment.OrderID, Shipment.OrderDate

 

 

    还要注意的是, 因为一个查询返回的是IEnumerable, 所以你还可以对查询结果进行查询:

 

VB

Dim SeattleOrders = From Contact In CustomerTable _

                    Join Shipment In OrderTable _

                    On Contact.CustomerID Equals Shipment.CustomerID _

                    Where Contact.City = “Seattle”

 

Dim FilteredOrders = From Shipment In SeattleOrders _

                     Select Shipment.OrderID, Shipment.OrderDate

 

 

我很有兴趣知道你们对主题的看法. 下次, 我会仔细地讨论一下LEFT/RIGHT/FULL OUTER JOIN.

      Bill Horst, VB IDE Test

从 SQL 到 LINQ, 第6部分: 联接 (Bill Horst)

[原文作者]Bill Horst

[原文链接]Converting SQL to LINQ, Part 6: Joins (Bill Horst)

 

本文假设您已阅读了本系列中此前发表的文章:

          SQL LINQ, Part 1: 基础(Bill Horst)

          SQL LINQ, Part 2: FROM SELECT(Bill Horst)

          Converting SQL to LINQ, Part 3: DISTINCT, WHERE, ORDER BY and Operators

          Converting SQL to LINQ, Part 4: Functions

          Converting SQL to LINQ, Part 5: GROUP BY and HAVING

 

本文将讨论一下交叉联接Cross Join, 内联接Inner Join, 自然联接Natural Join 和外(/)联接 Outer (Left/Right) Join.

 

联接

 

在一个SQL SELECT语句中查询一个以上的数据集(例如:)是比较常见的. 把多个表中的信息集中在一起就叫做联接, 而在SQLLINQ中有好几种不同的联接.

 

交叉联接

 

最简单的连接是交叉联接, 又叫笛卡尔积, 是针对两个数据集合的多对多的联接, 一个数据集中的每一条记录与另一个数据集中的每条记录都进行连接. 在一个SQL SELECT语句中, 要实现交叉联接只要在FROM子句中指定一个以上的表就可以了. VB LINQ, 可以用相同的方式实现, 如下所示:

 

SQL

SELECT CustomerTable.Name, OrderTable.OrderDate

FROM CustomerTable, OrderTable

 

 

VB

From Contact In CustomerTable, Shipment In OrderTable _

Select Contact.Name, Shipment.OrderDate

 

 

内联接

 

内联接是一对一的联接, 一个数据集中的记录根据两个数据集中特定的相同字段与另一个数据集中的记录做匹配. 在一个SQL SELECT语句中, 第二个数据集在INNER JOIN子句中指定, 匹配条件在ON子句中指定. 与此类似, VB LINQ联接中, 第二个数据集在Join中指定, On子句则用来指定Equals运算符与哪个字段相匹配.

 

SQL

SELECT Contact.Name, Shipment.OrderID

FROM CustomerTable Contact

INNER JOIN OrderTable Shipment

ON Contact.CustomerID = Shipment.CustomerID

AND Contact.Zip = Shipment.ShippingZip

 

 

VB

From Contact In CustomerTable

Join Shipment In OrderTable _

On Contact.CustomerID Equals Shipment.CustomerID _

And Contact.Zip Equals Shipment.ShippingZip _

Select Contact.Name, Shipment.OrderID

 

 

上面的例子是一个对等联接, 意味着用一个相等运算符联接两个表的信息. 这是On子句中唯一允许使用的运算符, 所以要想实现其他的联接运算符(例如:小于), 你需要使用一个带Where子句的交叉联接.

 

SQL

SELECT Contact.Name, Shipment.OrderID

FROM CustomerTable Contact

INNER JOIN OrderTable Shipment

ON Contact.CustomerID < Shipment.CustomerID

 

 

VB

From Contact In CustomerTable, Shipment In OrderTable _

Where Contact.CustomerID < Shipment.CustomerID

Select Contact.Name, Shipment.OrderID

 

 

自然联接

 

自然联接也是一对一的联接, 在自然联接中一个数据集中的记录依据两个数据集中所有的相同字段(具有相同的名称)与另一个表中的记录做匹配. 在一个SQL SELECT语句中, 第二个数据集可以在NATURAL JOIN子句中指定, 而用来联接的匹配条件是隐含的. VB LINQ中没有和自然联接直接对应的语法, 所以要实现它的最好方法是创建一个内联接并在On子句中手动指定所有相同字段的相等关系. SQL的自然联接相比, 这显得有点冗长, 但理解起来比较容易.

 

SQL

SELECT * FROM CustomerTable

NATURAL JOIN OrderTable

 

 

VB

From Contact In CustomerTable _

Join Shipment In OrderTable _

On Contact.CustomerID Equals Shipment.CustomerID _
And Contact.Phone Equals Shipment.Phone

 

 

(/)联接

 

外联接(也被称为左联接或右联接), 是一种一对多的联接, 一个数据集中的每条记录根据两个数据集的相同字段可以与另一个数据集中的多条记录相匹配. 在一个SQL SELECT语句中, 第二个数据集在LEFT JOINRIGHT JOIN子句中指定, 用来联接两个数据集的匹配条件在ON 子句中指定. LEFT JOIN, 第一个()数据集中的每条记录根据匹配条件与第二个表中所有满足匹配条件的记录相联接. 第一个数据集中的所有记录都会出现在结果集中, 不论第二个数据集中有没有与之匹配的记录. 这与RIGHT JOIN相反, 在外联接中所有第二个()数据集中的记录都会出现在结果集中, 并尽可能与第一个数据集中的任何匹配的记录相联接.

 

VB LINQ, 与外连接最相似的结构是分组联接. 在分组联接中, Group Join子句指定了第二个数据集, 并在On子句中提供匹配条件, 这与本文前面描述的联接(内联接)很相像. VB LINQ中还有一个Into子句, 它可以用来指定对每个分组执行什么样的集合运算, 就像是在一个像前面Group Join那样的子句中.

 

SQL LEFT JOIN类似, 分组连接中第一个数据集的每个成员, 与第二个数据集中所有和它相匹配的记录相联接. 再次提醒一下, 如果第二个数据集中没有任何记录与第一个数据集中的某条记录相匹配, 这条记录仍然会出现在结果集中.

 

SQL

SELECT CustomerTable.Name, SUM(OrderTable.Cost) Sum

FROM CustomerTable

LEFT JOIN OrderTable

ON CustomerTable.CustomerID = OrderTable.CustomerID

GROUP BY CustomerTable.Name

 

 

VB

From Contact In CustomerTable _

Group Join Shipment In OrderTable _

On Contact.CustomerID Equals Shipment.CustomerID _

Into Sum(Shipment.Cost) _

Select Contact.Name, Sum

 

 

实现RIGHT JOIN的最好方式是利用这个Group Join子句, 但需要颠倒其中的表(把后一个表作为第一个).

 

一个比较常见的情况是, 需要使用外联接但不需要进行集合运算. 像上面的示例那样使用Group关键字, 会导致结果被分为一连串大小不等的分组, 下面这个例子可能是VB LINQ中与不需要集合运算的外联接最类似的语法了.

 

SQL

SELECT *

FROM CustomerTable Contact

LEFT JOIN OrderTable Shipment

ON Contact.CustomerID = Shipment.CustomerID

 

 

VB

From Contact In CustomerTable _

Group Join Shipment In OrderTable _

On Contact.CustomerID Equals Shipment.CustomerID _

Into Group

 

 

这些分组联接的例子可能没有涵盖你可能会遇到的LEFT JOIN RIGHT JOIN 的所有情况, 但相信它们会给你一个创建与现有SQL查询相对应的LINQ查询的基本思路.

 

下个星期, 我打算做一个百宝袋”, 主题包括合并(UNION), TOP 和子查询.

 

如果你有任何其他的SQL SELECT语句想要转换到LINQ, 请为本文添加一个关于它回复.

 

      Bill Horst, VB IDE 测试