一对多(主从)关系表的LINQ to SQL 实现

[原文作者]: Beth Massi

[原文链接]: One-To-Many (Master-Detail) Forms with LINQ to SQL

  前面的博客中演示了两种不同情况下如何运用Combobox Data Binding组件和LINQ to SQL对象。(分别可见Linq1Linq2.)今天我将继续讲解怎样创建一对多关系的winform表单(同时还将用到2combobox lookup list,我还会展示怎样正确的插入、更新和删除Hierarchical Data 以及怎样利用存储过程实现上述操作。

数据库模块

  在下面的例子里我将创建新的数据库而不用Northwind,这因为Northwind并不是很有代表性的数据库特别地涉及到关系完整性。我要创建的表必须包含非空外键同时又含有timestamp 字段作并发检查。

这儿是我们将要建立的数据关系图:

  我已经为每个数据表指定Update, InsertDelete存储过程。为确保数据库安全而对它做一点限制,取消Update, Insert, Delete语句的执行权限。我们只需要赋予SELECT EXECUTE 权限。 这是一种比较规范的做法,通过阻止除存储过程方式以外的任何更新操作达到有效防止恶意代码运行于数据库端的目的。

  在开始创建表单前,我要先添加一个叫“LINQ to SQL Classes”的模板文件到我的工程里来,它可以打开O/R designer让我们可以从Server Explorer Tab中拖拽数据库里的表到这个模块设计器上。那么我先把前面已经创建好的四个表拖进来,并把它命名为“OMS.dbml”,因为我建的数据库名为OMS。它会用来创建我们的LINQ to SQL类和数据库关联的引用。我还会把所有的存储过程拖拽到Method面板上。

  下一步我们想要将各种存储过程或方法关联到updateinsertdelete对应的行为上。选中一个类,然后你们可以从属性窗口中看到DeleteInsertUpdate这三个属性值都被设为“Use Runtime”。选中每个属性你就可以为每个行为指定过程或方法。你也可以简单地右键单击类对象,选中“Configure Behavior”(行为设置)。在这一屏中,你可为每个类选定相应的行为,然后选定对应的方法。

   在你建立好所有这些之后,保存模板,然后生成所有的LINQ to SQL 类和用来管理数据库连接和交互的类DataContext

数据源和表单数据绑定

   接下来我们需要把这些信息都加到Data Sources Window里以便我们快速设计好表单。这在以前的章节里我已经用Northwind演示过了,这次我想要创建一个主细表单分别是Orders和与之关联的OrderDetails并且我想把CustomerProduct这两个表以查询列表的方式出现。首先在Data菜单中选择Add New DataSource,然后选择Object Data Source Type,选择Order类后单击Finish。一系列操作完成后,Data Sources window会产生Order对象,同时还关联着OrderDetails。因为有查询功能我们还需要添加CustomerProduct

   当检查Data Sources window中类的属性时,你会同时发现受关联的父子对象。比如,如果你展开Order,可以看到Customer父表和OrderDetails子表在一起。这是因为被关联的父对象Customer是指向Order对象的。为了显示所有Customer信息我必须把Customer drop control设置成“None”,改变CustomerIDCombobox样式。同时我不想显示Modified字段,所以把它也设成“None”。然后设置Order drop control成“Details”样式。

  拖拽Order表到Form上,生成datagridview控件以及BindingNavigatorBindingSource。然后从Data Source Window上拖拽Customer表到CustomerID Combobox上,为它建立Customer数据源列表。在Combobox属性页中设置ValueMember=CustomerIDDisplayMember=LastName 以完成Customer列表的设置。

  然后拖拽Order列下的OrderDetailsForm上生成一个DataGridView。你会发现会把父对象也拖上来,这里有两个父对象Product Order。必须编辑字段列,移除掉这样的列以及Modified列。对于此例,我会只显示OrderDetailIDOrderID字段因为这些字段的值会在我们保存数据以后自动生成。同时还要更改ProductID列的类型为DataGridViewComBoxColumn,并填选Product(从Other Data Sources -> Project Data Sources -> Product.)作为数据源(DataSource)。该行为会在Form设计器的组件面板(tray)中生成ProductBindingSource组件。

同样我们需要指定 DisplayMember = NameValueMember = ProductID

LINQ to SQL 对象填充数据

  现在我们设计好了Form接口,并建立了数据绑定,已经准备好创建数据对象了,并用它们装载数据。这不像DataSetsForms应用,当我们应用LINQ to SQL类时,Form设计器不会为我们生成任何加载和保存数据的代码。但是我们可以编写更直接的LINQ 查询语句限制结果集。本例中我将包含所有OrdersCustomersProducts信息到我们的lookup列表中,但是请记住当你的数据库有成千上百条记录时,这种设计很不可取的,那就必须要查找框了。

  那么首先我们需要在Form加载事件响应处理中设置BindingSourceDataSources成我们的表对象数据。先装载Order表的所有orders记录。

Public Class Form1 

    Dim db As New OMSDataContext 

    Private Sub Form1_Load() Handles MyBase.Load 

        Me.OrderBindingSource.DataSource = db.Orders 

  然后要装载Customers列表,我们可以简单地利用Linq query得到所有的Customer name并以“LastNameFirstName”方式。

Me.CustomerBindingSource.DataSource = From c In db.Customers _

                                              Let LastName = c.LastName & “, “ & c.FirstName _

                                              Select LastName, c.CustomerID _

                                              Order By LastName

  最后在我们选择Products列表时,有一个小窍门可以在列表的顶端设置一条“空”记录提示用户选择一条Products记录。而且可以通过检查ProductID0验证是否做了选择。

Dim emptyProduct As Product() = _

                {New Product With {.Name = “<Select a product>”, .ProductID = 0}} 

        Me.ProductBindingSource.DataSource = (From Empty In emptyProduct).Union( _

                                              From Product In db.Products _

                                              Order By Product.Name) 

End Sub

  你们可能会想为什么没有设置OrderDetailBindingSourceDataSource。这是因为只有当我们访问Order实例下的OrderDetails集合时OrderDetails才会被自动从数据库加载。这个事件是在OrderBindingSource改变position时发生,OrderDetailsBinding需要跟随Order显示相对应的OrderDetail对象。如果想看到这些的T-SQL语句实现,只要在加载数据时调用db.Log = Console.Out就会在Debug Output 窗口显示命令语句。只是不要忘了在release编译时将其删除。 

保存Hierarchical Data

  接着我们需要在click事件中加入代码保存数据。显示如下:

Private Sub OrderBindingNavigatorSaveItem_Click() _
        Handles OrderBindingNavigatorSaveItem.Click 
        Me.Validate()
        Me.OrderBindingSource.EndEdit()
        Me.OrderDetailsBindingSource.EndEdit() 
        Try
            db.SubmitChanges() 
            MsgBox("Your data was saved."
        Catch ex As Exception
           MsgBox(ex.ToString)
        End Try 
End Sub 
  好了,现在我们可以尝试运行一下程序,开启一个form实例,更改、添加和保存Orders表,一切运行正常。主键字段的插入和约束关系的执行都没问题。但是,如果我们想删除一条OrderDetail(子表)记录,就会报错:

System.InvalidOperationException: An attempt was made to remove a relationship between a Order and a OrderDetail. However, one of the relationship’s foreign keys (OrderDetail.OrderID) cannot be set to null.

  要解决这个问题我们要指明在OrderID被设为null的时候也可以删除OrderDetail记录。不过在O/R 设计面板上无法实现设置,必须打开它的XML编辑。而且一旦你更改了,设计器就不会消除更改除非你删除整个类。打开dbmlxml编辑页(只需要右击它选”Open with…”),里面都是关于OrderDetail类的描述。注意名为OrderDetailTable字段下的Association字段)

<Table Name="dbo.OrderDetail" Member="OrderDetails">
  <Type Name="OrderDetail">
    <Column Name="OrderDetailID" Type="System.Int32" DbType="Int NOT NULL IDENTITY" 
           
IsPrimaryKey="true" IsDbGenerated="true" CanBeNull="false" />
    <Column Name="OrderID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
    <Column Name="ProductID" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
    <Column Name="Quantity" Type="System.Int32" DbType="Int NOT NULL" CanBeNull="false" />
    <Column Name="Price" Type="System.Decimal" DbType="Money" CanBeNull="true" />
    <Column Name="Modified" Type="System.Data.Linq.Binary" DbType="rowversion NOT NULL" 
           
CanBeNull="false" IsVersion="true" />
    <Association Name="Order_OrderDetail" Member="Order" ThisKey="OrderID" 
                
Type="Order" IsForeignKey="true"/>
    <Association Name="Product_OrderDetail" Member="Product" ThisKey="ProductID"
                
Type="Product" IsForeignKey="true" />
  </Type> 

  我们需要增加一个叫做DeleteOnNull的属性,设置值为true,这样就能在调用SubmitChanges()方法上传时不受关系约束地删除一条子表的记录了。

<Association Name=Order_OrderDetail Member=Order ThisKey=OrderID Type=Order
            
IsForeignKey=trueDeleteOnNull=true“/>        

  另一个解决办法是修改数据库中关联的Delete规则,设置它为"Cascade"。这样设计器会确保将DeleteOnNull属性引用至Association中。

  好了,我们再次执行form,然后尝试删除一条OrderDetail子表的记录,并点保存,保存无误。但是如果你删除一整条order记录时,保存仍然会报错:

System.Data.SqlClient.SqlException: The DELETE statement conflicted with the REFERENCE constraint “FK_OrderDetail_Orders”. The conflict occurred in database “OMS”, table “dbo.OrderDetail”, column ‘OrderID’.
The statement has been terminated.

  这是因为和DataSets不同,当删除一条父表记录时,O/R Designer不会级联删除子表的记录。所以我们需要手写代码实现这种方式。可以有很多不同的方法达到,而且有一种方法是我以前的文章里演示过了的。

    Private Sub BindingNavigatorDeleteItem_Click() _
        Handles BindingNavigatorDeleteItem.Click 
        If Me.OrderBindingSource.Position > -1 Then
            'Grab a reference to the currently selected order
            Dim order As Order = CType(Me.OrderBindingSource.Current, Order)            
            'Ensure that children are deleted when the parent is deleted
            For Each detail In order.OrderDetails
                db.OrderDetails.DeleteOnSubmit(detail)
            Next
        End If
    End Sub 

  现在可以运行form,并尝试UpdateInsertDelete各种数据操作了,一切都会顺利的完成。如果你开启了DataContextLogging功能或者利用数据库的SQL profiler工具,你就可以监控存储过程的调用情况。

 

    下次我会教你如何通过创建business基类来继承,而且使用IdataErrorInfo接口和ErrorProviderLINQ to SQL类添加简单的验证。

    更新:我把代码放在a Code Gallery project,你可以试试。

Enjoy!

用VB追踪书籍信息(Matt Gertz)

[原文作者]: Matt Gertz

[原文链接]: Keeping Track of Books Using VB (Matt Gertz)

  首先声明,我不是数据库开发人员。在我的职业生涯中我编写过各种各样的代码,但奇怪的是我从没有直接参与过编写数据库相关的程序。每当我的代码需要某种形式的数据缓存机制时,我总是倾向于自己动手丰衣足食这句俗语所说的那样做。因此,当我们一开始就从事VS2008Linq的开发工作时 ,我相当兴奋,因为这是一个能够真正学到数据库编程相关详细知识的好机会。凑巧的是,在一个数周的日程安排中,我从编译器开发组长提升为软件开发经理,所以尽管我极力挽留这项热门的技术,但却苦于没有充足的时间真正去挖掘它的内容。

  幸运地是,对我来说用SQL Server精简版和LINQ开发一个数据库应用程序的确十分的简单,尽管我没有直接使用它们这样做过。我决定在这个周未做一个尝试来处理我日益见长的藏书。如果说我有一个不良嗜好的话,那就一定是购买了太多的书。从纯文学,哲学,物理一直到科幻小说,我只是无法在走出书店时不带上一大堆的书。我甚至再也不能避开书店了,这要感谢网上书店无处不在,而这样的结果就是,在我的家里书就像家具一样到处都是。

  伴随大量藏书而来的最大问题就在于我有时会忘记哪些书我已经有了。回想在80年代的时候,我使用专门针对这个问题而制做的Hypercard Stack程序来记录书籍信息,但是我的苹果机Centirs650早已经被回收了,在后来的Windows机器上我从没有这样做过。所以,在装备了VS2008后,我终于决定在这个周未做次类似的尝试。

创建数据库

  我必须说,我真的、真的很喜欢SQL精简版,它在VS2008中默认被安装而且也可以从这个站点免费下载。对于我所做的很多工作来说它很好用,例如它能够把所有的东西存储到一个本地文件里,而且这些数据能够很好的转移到其它的设备上。如果你没有一个现成的数据库,自己来建一个吧,真的十分简单,你甚至可以使用Visual Studio来完成:

  1. 运行Visual Studio 2008
  2. 选择“View”(查看),然后选择“Server Explorer”。
  3. Server Explorer窗口中,点击右键然后选中“Add Connection”(添加连接)命令。
  4. 这时你会看到一个选择数据源对话框。
    1. 选择“SQL Server Compact 3.5(.NET Framework)”和“My Computer”。
    2. 在“Connection Properties(连接属性)区域,点击“Create(创建) 按钮来创建一个新的数据库。
    3. 弹出另一个对话框,你可以给数据库文件命名(例如:“GertzLibrary”),并且指定一个你喜欢的密码。(我的书籍数据库并不是私人物品,因此我忽略了这步。)把这些都完成后,按“OK(确定),你的数据库就已经创建好了。
    4. 回到原先的对话框,数据库有密码时,需要在按下“OK(确定)之前填写密码。否则,只需按下“OK”(确定),数据库连接就建立好了。
  5. 你的数据库现在将出现在Server Explorer窗口。它只是一个空的数据库,现在让我们为它添加些数据表:
    1. 展开你的数据库节点,再右键点击“tables”,选择“Create Table( 创建表)
    2. 在弹出的对话框中,给新的表格名命(我的是“Books”),然后添加列信息。下面是我选择的:

                                          i.    ISBN – nvarchar 16, primary key(主键)。(primary key (主键)是一条记录的唯一标识。)

                                         ii.    Title – nvarchar 128

                                        iii.    Full Title – nvarchar 256

                                        iv.    Publisher – nvarchar 128

                                         v.    Owned – int, not null(不允许为空值)。这个域将告诉我是否已经拥有一本书或者是否想要一本书。在编码时我会把它做为一个布尔值。

   一个数据库就这样创建好了,现在它里面还没有任何记录,不过等会儿就有了。

使用数据库

  数据库现在已经被Visual Studio所感知,但是我们还没有真正的用到它。那现在让我们开始创建Windows ApplicationWindows 应用程序) 我的项目名称是”VBLibrary”。你在Solution Explorer附近看见一个tool window(工具窗口)叫做”Data Sources”(数据源)。(如果你找不到它,你可以用“Data”(数据)/Show Data Sources”(显示数据源)命令让它显示出来)。在这个tool window(工具窗口)的中部,你应当可以看到一个叫做“Add New Data Source”(添加新数据源)的链接 - 把指针放到上面点击它。(这个命令在“Data”(数据)菜单中也可以找到。) 

你将进入到“Add Data Source”(添加新数据源)向导,这个向导十分容易操作:

  1. 在第一页,确定“Database”(数据库)对象已经被选中,随后点击“Next”(下一步)。
  2. 在第二页,确定你的数据库已经在下拉菜单中被选中,随后再次点击“Next”(下一步)。这时会提示你可以将数据库文件添加到项目中;我选择的是“No”(否),因为我不需要将我的数据库和工程一起发布,同时还由于我只喜欢在机器上只有一份数据库的文件。
  3. 在第三页,确定是否把“Connection String”(连接字符串)保存到工程中。
  4. 在最后一页,选中“Books”数据表,点击”Finish”(完成) 

  这时数据源窗口中应当会有之前创建的“Books”表格。要在窗体中将数据显示出来同样简单- 只需把数据源中的表格拖放到“Form(窗体)上!但是在做这个之前,你可能想做些改变。例如,我喜欢用标准的UI控件去替代DataGrid(后者是被默认使用),所以我选中“Books”表,点击之后出现的下拉按钮,选择“Details”。在做完这些之后,你会注意到“Books”表旁的小图标从原先类似于网格的图案变为类似于窗体的图案。下一步,右键点击“Books”表,选择“Edit Dataset with Designer”。这时“Designer”将会出现,在“Designer”里还会有一个用来表示你的表格的Datatable – 它允许你自定义的数据在应用程序里被使用。

  1. Datatable中选中“Title”,在属性栏里将“NullValue”的值更改为“(Empty)” - 这样做是为了在没有与“Title”对应的数据时,把title做为一个空字符串来对待,以此替换抛出异常。对“Full Title”, “Author” “Publisher”采取相同的做法。因为我不在意记录中的这些字段是否都填写了。
  2. Datatable中选中“Owned”,在属性栏里做下列改变:
    1. 将“DataType”的值更改为“System.Boolean”。(一旦你这样做了,你会看见Data Sources窗口中的“Owned”已经变成了checkbox类型。
    2. 将“DefaultValue”的值更改为“True” - 你输入数据库的大部分记录会被标记为你已经拥有,因此应当设定一个默认值。
    3. 将“AllowDBNull”的值更改为“False” - 这个字段不允许为空,因为你只能拥有一本书或者并不拥有它。 

  现在,你能关掉这个Designer。在Data Sources窗口中重新选中“Detail, 并从中拖拽“Books”表到你的窗体上,瞧!你已经把所有的数据控件建立好了,而且与每个控件相绑定的代码也都会自动生成。甚至还有个为你自动生成的导航栏,能够让你操纵你以后要添加和访问的记录。 

清理窗体

  你能在窗体上随意拖放这些控件,也可以像调整其它的控件一样调整它们。你甚至能删除一些你决定不需要的控件。举个例子来说,我把“Checkbox1”前的“Owned”标签控件删除了,并把checkbox控件的文本值改为“Owned” - 就我个人来说,我不会同时使用labelcheckbox控件。 

  为使用户能方便的输入数据,你应该选中每一个字段对应的Textbox控件,然后在属性栏把控件的MaxLength属性更改为该字段的值所对应的长度,这样用户就不会在未知的情况下输入比这个字段在数据库里所能存放的最大值还长的数据- 在创建控件时,代码生成并不会自动做这些事情。 

  我也喜欢阻止用户保存虚假记录的尝试。在这里,如果一条记录没有ISBN编号,那它就是假的记录(因为在建立数据表时,我们已经定义了它是必需的)。因此添加下列代码到窗体中(右键点击窗体,选择“View Code”(查看代码)) 

    Private Sub ISBNTextBox_TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) _

Handles ISBNTextBox.TextChanged, BindingNavigatorAddNewItem.MouseUp

        Dim enableButtons As Boolean = (ISBNTextBox.TextLength <> 0)

        Me.BindingNavigatorAddNewItem.Enabled = enableButtons

        Me.BindingNavigatorCountItem.Enabled = enableButtons

        Me.BindingNavigatorMoveFirstItem.Enabled = enableButtons

        Me.BindingNavigatorMoveNextItem.Enabled = enableButtons

        Me.BindingNavigatorMovePreviousItem.Enabled = enableButtons

        Me.BindingNavigatorPositionItem.Enabled = enableButtons

        Me.BooksBindingNavigatorSaveItem.Enabled = enableButtons

    End Sub 

  这是十分基本的。这仅仅判断ISBN编号的值是否为空的,同时在导航栏上禁用除删除控件外的所有其它控件- 如果ISBN编号不为空的话,启用全部控件。代码中Handles子句指定只要是添加新记录或是改变字段的值就对上述情况进行检查。如果我不禁用那些控件,用户可以在没有完成记录填写的情况下保存记录或转去其它记录,从而导致异常。(我能够处理异常,但是我更喜欢一开始就让用户避免麻烦)。

  按”F5”。你的程序就运行起来了。你能够添加记录(使用看起来像加号的那个按钮),保存记录,也可以浏览记录。你的数据库会在按下工具栏里的“Save”按钮后被更新。非常酷!

失踪的Linq

  我前面承诺过在这个应用程序里会用到少许的Linq代码,因此我准备用Linq来生成一个报告。这个想法是这样的,我要创建一个我感兴趣却还没有到手的书籍列表,并且保存成文件以便我在线查找的时候浏览或者打印下来随身携带。 

  让我们添加一个名为“Generate Report”button控件到窗体中。然后双击这个button进入click事件的编辑。我们首先要做的事情是在数据库里查询我们感兴趣的书籍:

 

Dim unownedBooks = From book In GertzLibraryDataSet.Books _

Where book.Owned = False _

Select book.Author, book.Title

 

  在这一行,有两件有趣的事情。第一,VB会自动生成(在运行中)一个能容纳两个字符串(“Author”和”Title”,它们的类型与在数据库中值的类型是相同的)的对象类型(就是我们所知的“anonymous type”)。第二,VB会生成一个符合“Owned”=False”条件的可枚举的记录集合。 

  然后我就能枚举出上面所提到的那些对象,并转换成用户可读的数据。

 

Dim reportResults As New List(Of String)

Dim reportLine As String

For Each unownedBook In unownedBooks

reportLine = unownedBook.Author & “, “”” & unownedBook.Title & “”””

reportResults.Add(reportLine)

Next 

  我想把报告内的信息保存到一份文件中,因此我需要一个文件对话框来达到这个目的。从“Toolbox”(工具栏)中,拖放一个“SaveFileDialog”到窗体上 - 它会出现在已经在底部的其它非窗体的控件旁。选中它并在对应的属性栏做下列修改:

 

  • Name名称)-> SaveReportDlg
  • DefaultExt(默认文件扩展名)-> txt(在必要的情况下会为文件名自动添加”.txt”扩展名)
  • FileName(文件名)-> MyReport(默认创建的文件名称,用户可更改)
  • Filter(过滤)-> Text files(*.txt)|*.txt()(在对话框的文件类型下拉菜单中弹出)
  • Title(标题)-> Save Report(出现在对话框的标题栏) 

  现在,回到“Generate Report”按钮的处理程序添加下列代码: 

        If Me.SaveReportDlg.ShowDialog = Windows.Forms.DialogResult.OK Then

My.Computer.FileSystem.WriteAllText(Me.SaveReportDlg.FileName, _

“Books to Buy” & vbCrLf, False)

My.Computer.FileSystem.WriteAllText(Me.SaveReportDlg.FileName, _

“——————“ & vbCrLf, True)

For Each unownedBook In unownedBooks

My.Computer.FileSystem.WriteAllText(Me.SaveReportDlg.FileName, _

unownedBook.Author & “, “”” & unownedBook.Title & “””” & vbCrLf

Next

        End If 

  都做好了!如果我点击F5然后按下“Generate Report”按钮, 我就会得到一份保存到硬盘的报告,内容看起来和下面的有些类似 (假设我已经添加了一些书籍信息到数据库里):

 

Books to Buy

——————————————

Paul Vick, “The Visual Basic .NET Programming Language”

The Unicode Consortium, “The Unicode Standard, Version 5.0”

Matt Gertz, “The Novel I Will Really, Truly, Honestly Write One of These Days (I Mean It)” 

其它

  自从开始这个业余的项目,我已经知道通过一个ISBN编号Web services能返回你所需要的书籍信息。这使得数据入口十分简单,因为你只需要输入书后的ISBN编号,并点击某些按钮来完成剩下的工作。我已经准备好下次的尝试,如果我能花时间让它运行起来,我会回来写些更详细的内容。 

  下次继续

–Matt–*

Visual Studio 2008中的新元件TableAdapterManager

[原文作者]: Beth Massi

[原文链接]: The New TableAdapterManager in Visual Studio 2008

        在我以前的TableAdapters and Transactions文章中,我展示了一套关于如何在多层数据表结构的数据集中完成事务内部的分等级更新。我在这里给出的例子演示了Visual Studio 2005如何用数据库事务或者TransactionScope去更新数据。这需要一些代码来管理行的正确更新顺序,TableAdapters上的连接和处理,以及在回滚的情况下,你希望保持数据集中原有的变化。

        幸运的是,在Visual Studio 2008DataSet生成器有了新的提高,它创建了一个新的类通过只有几行代码自动处理所有这些事情!进入TableAdapterManager

        在这篇文章中,我将要借用以前的应用程序同时把它升级到Visual Studio 2008,要清楚的一点是,我将不会去升级它的目标框架,因为我希望程序能继续在NET Framework 2.0上运行。(Visual Studio 2008 可以通过一个叫做Multi-Targeting的特性去开发支持NET 2.0, 3.0 and 3.5 Frameworks的应用程序。关于这方面的更多使用方法,请看这里

    当你在 Visual Studio 2008中打开NorthwindTransaction.sln文件,它将会启动一个升级项目向导,使sln项目文件兼容于Visual Studio 2008. 启动以后,在资源管理器中双击OrdersDataSet 以打开数DataSet Designer。在属性视窗中会看到“分等级的更新” 选择True并保存. 这表示告诉数据集发生器去生成了TableAdapterManager . 注意每当你创建新的数据集时,默认的就是True

 

    现在,展开OrderForm以打开Windows Form设计器,删除工具箱顶部的Dropdown控件(TransactionToolStripDropDownButton)因为我们现在只要有一个简单的Save() 方法。右键点击窗体察看代码,在OrdersBindingNavigatorSaveItemclick事件中移掉Case语句,用Me.Save()代替:

Private Sub OrdersBindingNavigatorSaveItem_Click() Handles OrdersBindingNavigatorSaveItem.Click

    Me.Validate()

    ‘Commit all data to the OrdersDataSet

    Me.OrdersBindingSource.EndEdit()

    Me.Order_DetailsBindingSource.EndEdit()

    If Not Me.OrdersDataSet.HasErrors Then

        If Me.OrdersDataSet.HasChanges Then 

            If Me.Save() Then

                MessageBox.Show(“Your changed have been saved.”, Me.Text, _

                                MessageBoxButtons.OK, MessageBoxIcon.Information)

            Else

                MessageBox.Show(“Your changes could not be saved!”, Me.Text, _

                                MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

            End If

        Else

            MessageBox.Show(“Please make changes first.”, Me.Text, _

                            MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

        End If

    Else

        MessageBox.Show(“Please correct the errors with this data first.”, Me.Text, _

                        MessageBoxButtons.OK, MessageBoxIcon.Exclamation)

    End If

End Sub

    现在到了很有趣的部分。我们将删除所有的Savexxx()方法并且改写成使用新的TableAdapterManager简单的Save()方法。这个代码和以前用的SaveInDatabaseTransaction() 方法的效果是一样的。

''' <summary>
''' Performs an ordered save so that keys are properly updated in
''' the child table and so that deleted child rows are submitted
''' to the database first inside a database transaction automatically. 
''' VS 2008 generates code that takes care of the saving in the proper
''' order in a database transaction.
''' </summary>
''' <returns></returns>
''' <remarks></remarks>
Private Function Save() As Boolean
    Dim saved As Boolean = False 
    If Me.OrdersDataSet.HasChanges Then
        Try
            Dim manager As New OrdersDataSetTableAdapters.TableAdapterManager 
            'Back up the dataset so that if the transaction fails, then the entire 
            ' dataset is restored to it's original state.
            manager.BackupDataSetBeforeUpdate = True 
            manager.Order_DetailsTableAdapter = Me.Order_DetailsTableAdapter
            manager.OrdersTableAdapter = Me.OrdersTableAdapter 
            saved = (manager.UpdateAll(Me.OrdersDataSet) > 0) 
        Catch ex As Exception
            MsgBox(ex.ToString)
        End Try
    End If 
    Return saved
End Function

    发现没有,现在的代码简洁多了。以前我们必须去管理Order的更新,连接和事务处理,以及回卷的状态。现在所有这些都照顾到了。既然我们不再需要用到它,我们也可以在form中移掉MergeAfterSave方法以及TableAdapter partial 类中的AssignConnection方法。注意,如果我们已经创建了一个新的窗体,那么它就已经为我们创建了TableAdapterManager实例,预置了TableAdapter实例。

    运行应用程序选择一个客户的订单,然后在窗体上添加、更新和删除父子行。当我们点击保存按钮,这些行将会被更新和插入到parent-child order中,而在child-parent order删除,所有的这些都在数据库事务内部完成。一旦你打开SQL Server Profiler,我们可以看到:

       你也可以通过设置UpdateOrder属性来指示TableAdapterManager你想先处理更新过程。更多信息请查看TableAdapterManager文档

       TableAdapterManager为我们省去了很多代码,但是你仍需要确认你已经完全地设置好你的数据集去处理foreign-key约束,就像我在这片文章中提到的,TableAdapters按特定的顺序调用Fill方法。更多详细信息请阅读这篇文章,关于在你的数据集中使用foreign-key约束,这样能够通过TableAdapterManager完全更新。我已经附上更新好的程序.

Enjoy!

Attachment(s):NorthwindTransaction2008.zip

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.

VB 2008做的就是比VB 2005好!(Lisa Feigenbaum)

[原文作者]:Lisa Feigenbaum

[原文链接]:VB2008 Outperforms VB2005! (Lisa Feigenbaum)

寻找其他的方面升级到2008?对,是性能!为了使你的任务在版本更新中执行的更快,我们可以为你节省时间而并因而使你更有产能的方式, 就是使产品执行的速度更快些!这是我们整个VB组的目标,为此我们建立了一个有效的组用来做Orcas的发布。让我们看一下下面的数据,你会发现一些有趣的提高:

[**不承诺声明:这里所粘贴的数据是我个人通过观察在内部实验室的2Run比较而得来的。这些是我使用了在一台特定的机上运行2个大的客户端解决方案的观察报告。有些人有快些的系统,也有些人可能有慢些的。同样,工作需要每个操作也都是基于你的解决方案的。因而,你的结果可能好些,也可能差些。有趣的事情是在同样的硬件环境中来看不同的版本之间的差异。我这里仅仅是记录了这些数据,希望能在这些改变上给你一个大概的印象但是这些数据并不是意味着可以用作工业标准。

如果对这些有兴趣,这里是解决方案和所使用的机器的一些数据一个解决方案包含了29个工程项目和2772个文件,且在所有的方法和类型上都有XML的注释。另一个解决方案包含了783个文件。机器的配置为:Dual Core Pentium-D – 3.0GHz, 1GB RAM, 10K RPM HD. OS=XP.这些是我们内部执性能测试的机器规格的一种。

Scenario VB2005 Time (ms) VB2008 Time (ms) VB2008 is x times faster than VB2005 (2005 time/ 2008 time) VB2008 takes x% as much time as VB2005 (100% * 2008 time/ 2005 time)
Build a large project (using background compilation) 222206.25 1352.88 164.25 0.61%
Build a large multi-project solution (explicit build operation) 1618604.75 57542.75 28.13 3.56%
Build a large multi-project solution (using background compilation) 222925.50 19861.88 11.22 8.91%
Responsiveness after adding a member to a class 327.00 36.50 8.96 11.16%
Responsiveness after opening a project 255551.25 38769.38 6.59 15.17%
Invoke Intellisense to see a list of types (first time) 1192.50 530.5 2.25 44.49%
Edit-and-Continue in a solution with xml comments (first time) 441.25 210.5 2.10 47.71%
Responsiveness after changing a method statement 390.25 236.38 1.65 60.57%
10 Steps in the debugger (subsequent times) 1850.75 1167.13 1.59 63.06%
Invoke Intellisense to see a list of types (subsequent times) 79.25 51.5 1.54 64.98%
F5 when the solution is already built (subsequent times) 385.20 278.7 1.38 72.35%
Item gets added to the Error List after making an error 531.25 394.5 1.35 74.26%
10 Steps in the debugger (first time) 1336.50 1150 1.16 86.05%
Responsiveness while background compiling on an open solution 4803.00 4284.75 1.12 89.21%
Load a large solution (subsequent times) 13667.5 12407.25 1.10 90.78%

**所有时间都是依据在执行客户端解决方案的操作表现测定的。难道你不希望我们使用你们的解决方案来优化我们的时间吗? 发送它给我们以致于我们可以为你来做优化!(vbperf@microsoft.com)我们总是很希望能得到更多的客户端工程来做测试。 

你可以使用正常的操作继续体验产品性能的情节,这样的你感兴趣的情节有什么在上面没有看到的吗?

为了使我们也可以看一看,请发送重现的操作步骤给我们!(vbperf@microsoft.com)

其他的有趣的VB2008性能内容如下:

在第9电视频道,VB性能开发者Cameron McColl 谈论并示范若干在OrcasVB性能的改进:

http://channel9.msdn.com/ShowPost.aspx?PostID=328382#328382.分级,第一时间的感觉,任务列表的错误,编辑框的相应,大的解决方案和web应用程序是一些我们在VB2008关注提高性能的部分。

Cameron 也表明了这些改进也可在他的网页上看到:http://blogs.msdn.com/vbteam/archive/2007/07/24/today-s-performance-webcast-slide-deck-and-channel9-interview-beth-massi.aspx