VFP和VB.Net的数据管理
原著: Les Pinter
翻译: CY 出处:网络
注:这里是摘抄自Les Pinter 的白皮书《从VFP 迁移到VB.Net》,这将会在本月稍晚的时候在Montreal 的DevTeach
上演讲。
VB.NET 里没有哪一个方面会比它处理数据更令人失望的。在某些小数据量下,数据处理机制的效率还是可以接
受的。带宽通常是所关心的事,并且在数据处理机制里的大多数变量是根据需要与小数据一样在线路上移动。
数据可以是来自MS SQLServer 或是其他任何地方。SQLServer(或者,MSDE,支持以开发和测试为目的的少量连接的
开发工具)在VS 文档里被大大地宣传,这是可以理解的。难以容忍的是不再能够方便的使用非SQL 数据源。其实,这
和使用SQL 是一样的困难。
因为这样,我们可以推荐给客户的成本优势的事实-- FoxPro 的数据访问对程序而言是低廉的,已经不在了。你使用
OLEDB 数据源所获得的仅有优势是你不再需要支付许可费给MS。但是OLEDB 的性能与纯FoxPro 的数据访问相比是
糟糕的。于是这个优势也不在了。其实,我很难见到在.NET 应用里使用FoxPro 表的理由。这里表达了你最好还是去做
的事;它是拙劣的,所以我在这里不会使用它。
FoxPro
这里是你在FoxPro 里怎么处理数据的:
USE
有问题吗?
事实上,有些小问题。为了在多用户环境下使用数据,你必须设置SET EXCLUSIVE OFF 和 SET MULTILOCKS ON,
然后在修改前发出CursorSetProp('Buffering',5),并使用TableUpdate(.T.) 或 TableRevert(.T.)来分别保存或放弃更改,
然后再设置缓冲为1。但是,这是对应于DBF 访问,除非你想去检查是否有其他用户已经修改了你所编辑的用户记录,
在大多数情况下这是百万分之一的机会。
SQLServer(或MSDE)引起另一个问题。从一个SQL 表获取数据需要建立一个连接,执行一个SELECT 语句,然后关闭
连接。这里是代码:
Handle = SQLStringconnect( ;
"Driver={SQL Server};Server=(local);Database=NorthWind;UID=sa;PWD=;")
SQLExec(Handle,"SELECT * FROM CUSTOMERS", "MyCursor" )
在
FOXTALK 2.0 7
SQLDisconnect(0)
这把返回的记录保存在一个名为“MyCursor”的游标里。使用VFP8 新的CursorAdapter 类,你可以不需要代码来
完成同样的事。
回传更改会更复杂些。SQL 所寻找的是一个INSERT 或 UPDATE 命令。下面是写给你的两个过程:
列表1:创建你自己的SQL INSERT 和 UPDATE 命令
PROCEDURE SaveRecord
PARAMETERS pTable, pKeyField, pAdding
IF pAdding
THIS.InsertRecord ( pTable, pKeyField )
ELSE
THIS.UpdateRecord ( pTable, pKeyField )
ENDIF
ENDPROC
PROCEDURE InsertRecord
LPARAMETERS pTable, pKeyField
cExpr = THIS.BuildInsertCommand ( pTable, pKeyField )
DO CASE
CASE THIS.AccessMethod = [SQL]
lr = SQLExec ( THIS.Handle, cExpr )
IF lr < 0
msg = [Unable to insert record; command follows:] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
ENDCASE
ENDPROC
PROCEDURE UpdateRecord
LPARAMETERS pTable, pKeyField
cExpr = THIS.BuildUpdateCommand ( pTable, pKeyField )
_ClipText = cExpr
DO CASE
CASE THIS.AccessMethod = [SQL]
lr = SQLExec ( THIS.Handle, cExpr )
IF lr < 0
msg = [Unable to update record; command follows:] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
FOXTALK 2.0 8
ENDIF
ENDCASE
ENDPROC
FUNCTION BuildInsertCommand
PARAMETERS pTable, pKeyField
Cmd = [INSERT ] + pTable + [ ( ]
FOR I = 1 TO FCOUNT()
Fld = UPPER(FIELD(I))
* Can't deal with GENERAL fields, so ignore them
IF TYPE ( Fld ) = [G]
LOOP
ENDIF
Cmd = Cmd + Fld + [, ]
ENDFOR
Cmd = LEFT(Cmd,LEN(Cmd)-2) + [ } valueS ( ]
FOR I = 1 TO FCOUNT()
Fld = FIELD(I)
IF TYPE ( Fld ) = [G]
LOOP
ENDIF
Dta = ALLTRIM(TRANSFORM ( &Fld ))
Dta = CHRTRAN ( Dta, CHR(39), CHR(146) ) && get rid of single quotes in the data
Dta = IIF ( Dta = [/ /], [], Dta )
Dta = IIF ( Dta = [.F.], [0], Dta )
Dta = IIF ( Dta = [.T.], [1], Dta )
Dlm = IIF ( TYPE ( Fld ) $ [CM],['],;
IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], [])))
Cmd = Cmd + Dlm + Dta + Dlm + [, ]
ENDFOR
Cmd = LEFT ( Cmd, LEN(Cmd) -2) + [ )] && Remove ", " add " )"
RETURN Cmd
ENDFUNC
FUNCTION BuildUpdateCommand
PARAMETERS pTable, pKeyField
Cmd = [UPDATE ] + pTable + [ SET ]
FOXTALK 2.0 9
FOR I = 1 TO FCOUNT()
Fld = UPPER(FIELD(I))
IF Fld = UPPER(pKeyField)
LOOP
ENDIF
IF TYPE ( Fld ) = [G]
LOOP
ENDIF
Dta = ALLTRIM(TRANSFORM ( &Fld ))
IF Dta = [.NULL.]
DO CASE
CASE TYPE ( Fld ) $ [CMDT]
Dta = []
CASE TYPE ( Fld ) $ [INL]
Dta = [0]
ENDCASE
ENDIF
Dta = CHRTRAN ( Dta, CHR(39), CHR(146) ) && get rid of single quotes in the data
Dta = IIF ( Dta = [/ /], [], Dta )
Dta = IIF ( Dta = [.F.], [0], Dta )
Dta = IIF ( Dta = [.T.], [1], Dta )
Dlm = IIF ( TYPE ( Fld ) $ [CM],['],;
IIF ( TYPE ( Fld ) $ [DT],['],;
IIF ( TYPE ( Fld ) $ [IN],[], [])))
Cmd = Cmd + Fld + [=] + Dlm + Dta + Dlm + [, ]
ENDFOR
Dlm = IIF ( TYPE ( pKeyField ) = [C], ['], [] )
Cmd = LEFT ( Cmd, LEN(Cmd) -2 ) ;
+ [ WHERE ] + pKeyField + [=] ;
+ + Dlm + TRANSFORM(EVALUATE(pKeyField)) + Dlm
RETURN Cmd
ENDFUNC
PROCEDURE DeleteRecord
LPARAMETERS pTable, pKeyField
Keyvalue = EVALUATE ( pTable + [.] + pKeyField )
Dlm = IIF ( TYPE ( pKeyField ) = [C], ['], [] )
cExpr = [DELETE ] + pTable ;
+ [ WHERE ] + pKeyField + [=] ;
+ Dlm + TRANSFORM ( m.Keyvalue ) + Dlm
lr = SQLExec ( THIS.Handle, cExpr )
IF lr < 0
Msg = [Unable to delete record] + CHR(13) + cExpr
MESSAGEBOX( Msg, 16, [SQL error] )
ENDIF
ENDPROC
这些过程与VB.NET DataAdapter 所做的完全一样。VB 使用CommandBuilder 来从SELECT 命令里推断出Update、
Insert 和Delete 命令和表的主键。这就是那些代码所做的。
通过互联网访问数据,给FoxPro 开发者引入最头痛的问题。MS 推荐创建XML WebService DLL。这很容易创建,
并且“消费”(使用)代码是由VFP8 的Web Service 的Toolbox 为你编写的。还有更容易的吗?
WebConnection 是很容易的。大约是一个早上的报酬,你就可以获得一个完整、全面的解决方案工具包,它是由Rick
Strahl 编写的,他是一个比我们任何一个都聪明得多的人。在Www.west-wind.com 上,只是购买是糟透的事。
VB.NET
第一次我见到.NET 处理数据时,我震惊了。我完全懵了,要不是我找到了一个运行的示例,我还无从下手。这个改
进超过了FoxPro?我感到惊讶。除非你是MS,它提高了他们的收入。
你当在VB.NET 里使用与FoxPro 开发者不太一样的方法来处理数据,这实际上是很简单的事情。首先,是基础。
在.NET 里的四个数据来源是:Oracle、ODBC、OLE DB 和 SQL。SQL 是MS 的SQLServer,或MSDE,它与单线程
的SQLServer 一样工作。OLEDB 可以用于几乎其他全部。你也可以使用ODBC 访问(ODBC 和OLEDB 的连接串是不
同的,详www.ConnectionStrings.com),虽然OLEDB 会更好些。Oracle 是有钱人的当然选择。
MS 已经使得访问SQLServer 和访问本地表一样容易了。程序员没有动机来使用除SQL 外的任何其他东西。 我们
可以在开发环境使用MSDE,当然,MS 不在乎我们的钱。他们在乎我们客户的钱。
你所决定的可以用来存取数据的驱动程序,性能有好有坏。如果你想找与FoxPro 一样快的东东,你在.NET 里是找
不到的。现在有一个叫做VistaDB 的数据库产品没落了,它是免费的。这意味着MS 将会买下它并杀死它,让Anthony
Carrabino(开发者)成为富翁。但是,它也许会在一段时间内用在一两个项目上。参见我的网页上的发展远景。
从DataAdapter 开始
在.NET 里的访问是从连接开始的。连接串是特定的驱动程序www.connectionstrings.com 是一个寻找主意的好地
方。在下面示例中,我将建立一个连接并返回一个数据集里的部分数据。
Imports System.Data.SQLClient
...
Dim ConnStr as string = "Server=localhost;Database=NorthWind;UID=sa;PWD=;"
FOXTALK 2.0 11
Dim da as New SQLDataAdapter("SELECT * FROM CUSTOMERS",ConnStr)
Dim ds as New Dataset
Da.Fill(ds)
DataGrid1.DataSource = ds.Tables(0)
这是你可以用来获取数据的几行代码。你所获得是数据集,它是在内存中的包含有一个或多个表的文本字符,它可
以用来做为表格的数据源,正如我在上面代码的末行里所做。(基于VB 的古怪的计数方案,Table(0)-表号0-在数据集是
第一个也只有一个。)在.NET 里创建对象的一个理由是,重载New 构造函数数方法 (如,Init)将极易导致混乱。
DataAdapter 的New 方法有多个重载。其中之一要求你创建一个SQLConnection 对象,传递给它ConnectionString,并
且调用它的Open 方法,然后再把SQLConnection 作为第二个参数来传递,如下 :
Imports System.Data.SQLClient
...
Dim ConnStr as string = "Server=localhost;Database=NorthWind;UID=sa;PWD=;"
Dim Conn as New SQLConnection ( ConnStr )
Conn.Open()
Dim da as New SQLDataAdapter( "SELECT * FROM CUSTOMERS", Conn )
Dim ds as New Dataset
Da.Fill(ds)
Conn.Close()
DataGrid1.DataSource = ds.Tables(0)
因为SQL DataAdapter 构造函数有多个重载,你也可以使用连接串作为第二个参数并清除三行的代码。
Datasets
DataAdapter 的Fill 方法从SQL 里的Customers 表提取数据,并放入到XML 数据集。DataSet 实际上保存有多个
表;它更象当作FoxPro 表单的数据环境来考虑。这就是为什么表格的数据源是ds.Tables(0)。Tables(0)是数据集里的第
一个表(在这里也只有一个)。你也可以通过它的名来引用,比如,ds.Tables("Customers"),因为Tables()是在重载时使
用表号或是表名。
我想说XML 是MS 用来替代DBF 的。Dataset 是用来代替多表DBF 的。你甚至可以编写一个应用程序来从磁盘文
件里直接使用XML。它没有索引,因此你将会在你的表达到相当大小后,不得不要把它升迁到SQL。
你可能会认为类似于SKIP 的指针移动命令也适用于数据集,但并不是如此。对于数据集没有“当前行”的概念。
表单有一个对象叫作BindingContext 可以处理这些事。它简单的以数据集表作为参数来重载表单。
在表单的数据集表里的SKIP 命令如下:
BindingContext(ds.Tables(0)).Position += 1
SKIP –1 如下:
BindingContext(ds.Tables(0)).Position -= 1
FOXTALK 2.0 12
GOTP 如下:
BindingContext(ds.Tables(0)).Position = 0
GO BOTTOM 如下:
BindingContext(ds.Tables(0)).Position = ds.Tables(0).Rows.count-1
然而,TableUpdate() 和TableRevert()是数据集的方法,只不过它们叫作AcceptChanges() 和 RejectChanges()。但
是它们只接受或拒绝对数据集的更改。它并没有返回你的数据到它的来源处。
在.NET 里的数据管理是基于非联机的数据集。你可以打开连接,获取数据,然后再关闭连接。数据的存储,就象是
你的住在疗养院里的伯祖父Bob,不记得你是从哪儿来的。为了应用你的更改,你需要构造INSERT、 UPDATE 和
DELETE 语句,并发送它们到通常使用DataAdapter 来存储数据的地方。
DataAdapters
你可以使用Command 对象或 DataAdapter 对象来返回数据集。我现在将略过OLEDBCommand 和
SQLCommand, OLEDBDataAdapter 和 SQLDataAdapter 的差别;只是知道是两回事。
如果你使用DataAdapter,并且表有主键,你可以使用CommandBuilder 和 SELECT 语句来建立UPDATE、
INSERT 和 DELETE 命令。在你的表单里的每个表你都需要一个DataAdapter。如果你设计一个包含所有记录的列表,
或者是符合查询条件的所有记录,而且当前又有一个记录可供查看和编辑,那么为同一个表你就需要两个DataAdapter。
你可以使用向导来创建每一个DataAdapter 。从表单设计器里的工具栏里拖动一个SQL DataAdapter 。一个
SQLConnection 控件和SQLDataAdapters 控件将会加入到设计器底部的托盘里。连接需要一个ConnectionString,还有
Open 和 Close 的方法。你需要创建连接然后再打开它。
向导的第一页是询问连接。你可能多半是在项目的开始时就创建数据库和连接的。第二页是询问是否使用SQL 语句
或存储过程。如果你不需要更新表,选择“高级”并取消“生成Insert, Update 和 Delete 语句”选项。由向导创建的
SQLDataAdapter 有INSERT, UPDATE 和 DELETE 方法;向导的最后一页是确认并创建。
如果你不使用向导, 你仍然可以利用这个性能。仅需要创建一个SQLCommandBuilder 对象,传递给它你的
SQLDataAdapter 的名。确信在你提供SELECT 语句给DataAdapter 后再创建它,要么或者你可以浪费一个小时来试图
找出它为什么不能工作,就如我在写这篇时所发生的那样。
下面是我写的最短代码,是关于创建你自己的DataAdapter,并填充它和准备以INSERT, UPDATE 和 DELETE
命令来用于以你的用户所做的更改来更新后台数据。(注意,如果连接串,SELECT 命令和表名是属性,你可以把这个代
码做得更容易些。去试试吧。)
在表单类的Declarations(申明)里:
Public ConnStr As String = "Server=localhost;Database=Northwind;UID=sa;"
Public Conn As SqlConnection
Public da As SqlDataAdapter
Public cb As SqlCommandBuilder
Public ds As New DataSet
FOXTALK 2.0 13
在表单的Load(装载) 事件里:
Conn = New SqlConnection(ConnStr)
da = New SqlDataAdapter("SELECT * FROM CUSTOMERS", Conn)
Conn.Open()
da = New SqlDataAdapter("SELECT * FROM Customers", Conn)
cb = New SqlCommandBuilder(da) ' creates the Insert, Update and Delete commands
da.Fill(ds, "Customers")
Conn.Close()
DataGrid1.DataSource = ds.Tables(0)
在表单的Update(更新)控钮的Click(单击)事件里:
Conn.Open()
da.Update(ds, "Customers")
Conn.Close()
你可以使用任何的SELECT 语句来检索数据。可是,如果你想用向导来自动生成INSERT, UPDATE 和 DELETE
语句,你所选择的表必须要有主键。
如果你当需要的是从表里读取部分数据,你可以在你的代码里实例化一个DataAdapter,指定一个SELECT 语句,
并使用它来返回一个数据集,所有的代码如下:
Dim da as New SQLDataAdapter ( "SELECT * FROM CUSTOMERS", "Server=localhost;…")
Dim ds as New Dataset Da.Fill(ds)
模板数据集
数据集可以作为任何控件的数据源(从技术上说,任何控件都支持IBindable 接口,关于接口的更多内容后谈)。表
格可以显示数据集的内容,仅需要把数据集指定给连接的相应指针。当你打开在属性表单里的+复合属性(DataBuildings),
然后在Text 属性旁边输入表名.列名,你并没有提及数据集。当你从表单项设计器的底部托盘的DataAdapter 里生成一个
数据集时,IDE(集成开发环境)会为你编写一个VB 程序。
点击Solution Explorer 里的 “ Show All Files ( 显示所有文件)” 图标, 你将会见到我所说的。
Customers.CompanyName 并不是 表名.列名,在FoxPro 下它是的;它是 对象名.属性名。这个对象名对象是基于IDE
为你编写的那个类。属性名是在同一个程序里取得的属性。你可以自行打开它查看。我可以等着。
很吃惊?我是如此的。在FoxPro 里非常简单明了的事在.NET 却是这么繁琐?相信这是有原因的,但是我并不关心
这个。
没有什么是免费的,并且先前的关于免费的告诫还仍然存在。多个公司,包括RapTier, Visible Developer 和
DeKlarit,销售的创建模板数据集的生成器都有附加功能。这些天已经在互联网上快要免费了的。就如你所知,你自己可
以编写一个更好的模板数据集生成器。我真的,真的希望MS 能够在不远的将来排除这样的需要。我们在Xbase 世界已经
20 年都没有它,所以我知道它并不是必要的。
FOXTALK 2.0 14
使用DataView 来排序和过滤数据
FoxPro 有两项令人惊奇的性能,SET FILTER 和 SET ORDER,可以让我们限制用户可以见到的记录和他们所见
到的记录顺序。数据集也有同样的性能—排序。告诉我是不是它让你觉得有点棘手?
数据集可以包括有多个表。它们类似于表的集合,比如,dsCusts1.Tables(0) 或 dsCusts1.Tables("Customers")
对于表是没有排序和过滤性能。然而,每个表都有一个DefaultView(默认视图),它却是有的。于是,为了使用这些,
你将不得不创建一个本地DataView(数据视图)对象。
加入两个标题分别为“Filter(过滤)”和“Sort(排序)”的命令按钮到你刚刚创建的表单,并使用下面的代码来作快速
演示。
Private Sub Form1_Load( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles MyBase.Load
Me.OdbcDataAdapter1.Fill(DsCusts1)
End Sub
Private Sub cmdFilter_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles cmdfilter.Click
Dim dv As DataView
dv = DsCusts1.Tables(0).DefaultView
dv.RowFilter = "CompanyName Like 'A%'"
DataGrid1.DataSource = dv
MessageBox.Show("Only A's")
dv.RowFilter = ""
End Sub
Private Sub cmdSort_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles cmdSort.Click
Dim dv As DataView
dv = DsCusts1.Tables(0).DefaultView
dv.Sort = "CompanyName DESC"
MessageBox.Show("Descending...")
dv.Sort = "CompanyName"
MessageBox.Show("Asscending...")
End Sub
正如你所见,在以.NET 处理数据时是个大大的问题。我有说到它慢吗?然而,现在开始试验.NET 却是个好主意,
FOXTALK 2.0 15
如果你还没有开始,因为我非常确信它在不久的将来会成为我们职业的一部分。
再见。
[返回] |