首页 > 开发 > 综合 > 正文

使用 SQL pass-through

2024-07-21 02:10:25
字体:
来源:转载
供稿:网友

第四节 使用 sql pass-through

与使用视图相比的另一个选择是单独地使用 sql pass-through。这就意味着你发送 sql 语句到后端并明白地告诉它要做什么。如果你想添加一条记录可发送一个 insert。要保存记录可发送一个 update。显然这比使用视图要做更多的工作。但是它允许你完全地控制发生什么和什么时候将会发生。

载入表单

表单 membexec.scx 是一个与上面的表单相似的表单,它只是使用 sql pass-through 而不是视图。以下是表单的 load 方法代码。
open database library
thisform.nhandle = sqlconnect('cnlibrary')
if thisform.nhandle < 0
  thisform.showerror
  thisform.lconnected = .f.
  else
  lcsql = "select member.member_no, lastname, firstname, " + ;
          "  middleinitial, street, city, state, zip, " + ;
          "  phone_no, expr_date, birth_date = null, " + ;
          "  adult_member_no = null " + ;
          "from member, adult " + ;
          "where member.member_no = -99 "
  if sqlexec(thisform.nhandle, lcsql, "c_member") < 0
    thisform.showerror
    thisform.lconnected = .f.
  endif
    = cursorsetprop("buffering", db_bufoptrecord, 'c_member')
endif

在表单载入时,sqlconnect() 用于使用保存在 visual foxpro library 数据库中的 cnlibrary 连接来建立到 sql server 的连接。如果 sqlconnect() 失败,除了退出和回家外,你没有别的事可做。
如果连接正常,以发送到 sql server 一个搜索成员号 -99 的 select 语句来创建了一个空的游标。即使没有返回记录,visual foxpro 也会创建该游标。然后设置该游标的开放式缓存。这样做的原因是可以在该表单上使用缓存了的游标。这反映了一些视图的易用性。

定位一个成员

当在表单上使用视图时,定位一个成员只需简单地为视图参数赋值并重新获取值(requery)。当使用 sql pass-through 时要稍稍复杂一些。要获取成员信息,要创建一条 select 语句并发送到服务器。在下面的代码中你可以看到这是一个 union select。如果成员存在于 member 表和 adult 或 juvenile 表中。
lcsql = "select member.member_no, lastname, firstname, " + ;
        "  middleinitial, street, city, state, zip, " + ;
        "  phone_no, expr_date, birth_date = null, " + ;
        "  adult_member_no = null " + ;
        "from member, adult " + ;
        "where member.member_no = adult.member_no " + ;
        "  and member.member_no = " + ;
        "    alltrim(thisform.txtmemberid.value) + " " + ;
        "union " + ;
        "select member.member_no, lastname, firstname, " + ;
        "  middleinitial, street, city, state, zip, " + ;
        "  phone_no, expr_date, birth_date, " + ;
        "  adult_member_no " + ;
        "from member, adult, juvenile " + ;
        "where member.member_no = juvenile.member_no " + ;
        "  and adult.member_no = " + ;
        "    juvenile.adult_member_no " + ;
        "  and member.member_no = " + ;
        "    alltrim(thisform.txtmemberid.value)
  
if sqlexec(thisform.nhandle, lcsql, "c_member") < 0
<略去的代码>

如果 c_member 游标为空则没有输入的 id 成员存在。否则所有成员信息都在游标中。游标设置了行缓存,且表单控件用游标中的成员信息填充。
union 允许你发送一个 select 到服务器并获取该成员的所有信息。在早期的示例中,需要对两个或三个视图进行重查询(requery) 而不是象这里一样只需要一个 sqlexec()。注意,如果你使用视图设计器,你不能创建一个带 union 的远程视图。但你可以用 create sql view 命令来创建这种视图。

添加一个成人

当用户按下 add 按钮时,一条空白的记录添加到 c_member 游标中。这与早期的视图示例不同。
select c_member
= tablerevert()
append blank

仅出于可读性的原因,添加新成员的代码位于表单的 addmember 方法中。因为添加一个成员包括向两个表添加记录,因此开始一个事务处理。只在使用视图的表单中,使用 sqlsetprop() 函数来开始事务处理。
= sqlsetprop(thisform.nhandle, "transactions", 2)

当用视图来访问远程数据时,你可以依靠 visual foxpro 来为你处理大多数的后台工作。例如,在早期的表单中,你看到了要发送一个 insert 或 update 到服务器,你只需要发布一条 tableupdate()。insert 或 update 的语法都由 visual foxpro 为你做了。
这里的表单合作 sqlexec() 函数来发送 sql 语句到服务器。这就意味着你必须自己构造 sql 语句。在开始事务处理后,构造一个 insert 语句来添加新记录到 member 表中。
* 添加新成员到 member 表
lcsql = "insert member (lastname, firstname, " + ;
                       "middleinitial, photograph) " + ;
        "values ('" + ;
             alltrim(thisform.txtfirstname.value) + ;
             "', '" + ;
             alltrim(thisform.txtlastname.value) + ;
             "', '" + ;
             alltrim(thisform.txtmiddleinitial.value) + ;
             "', " + "null)"
if sqlexec(thisform.nhandle, lcsql) < 0
  thisform.showerror
  * rollback the transaction
  = sqlrollback(thisform.nhandle)
  return
endif

你现在需要知道 sql server 为新成员指定的 member_no。代码与早期表单相似。
* 找出新成员的 member_no
if sqlexec(thisform.nhandle, "select @@identity") < 0
  <略去的代码>

nnewmemberid = sqlresult.exp

然后构造一个 insert 来添加新记录到 adult 表。从服务器获取的 @@identity 值用于该 insert 中来正确地连接 adult 和 member 中的记录。
* 添加新成员到 adult 表
lcsql = "insert adult (member_no, street, city, state, " + ;
                      "zip, phone_no, expr_date) " + ;
        "values (" + alltrim(str(nnewmemberid)) + ", '" +;
             alltrim(thisform.txtstreet.value) + ;
             "', '" + ;
             alltrim(thisform.txtcity.value) + ;
             "', '" + ;
             alltrim(thisform.txtstate.value) + ;
             "', '" + ;
             alltrim(thisform.txtzip.value) + ;
             "', '" + ;
             alltrim(thisform.txtphonenumber.value) + ;
             "', "'" + ;
             ttoc(dtot(gomonth(date(),12))) + "' )"
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代码>

如前所述,如果一切正常,则提交事务处理否则回滚。

保存修改


保存已存在的成员的信息的代码在表单的 updatemember 方法中。要保存信息,发送一条 update 语句到服务器。表单中的更新语句如下:

update <table> set <column1> = <value1>,
                   <column2> = <value2>,
                   等等...

这相当地直截了当,虽然构造一个要发送到服务器的 update 语句看起来有些庞大。你知道表的字段名和表单上的控件中的值。你可以一个字段接一个字段地生成 update 的 set 部分。但是,象上面这样书写可以使程序更清晰。你也可能不想浪费 sql server 的时间来更新没有修改的字段。这里的代码使用了 oldval() 函数,使用游标缓存使这样做变为可能,要将游标中各字段的值与原始的值进行比较。只有当它被修改了时才让它成员 update 语句的一部分。另外,远程视图自动地这样做了。
lcsql = ""
* 更新该成员到 member 表
if c_member.firstname <> oldval("c_member.firstname")
  lcsql = lcsql + "firstname = '" + ;
          alltrim(thisform.txtfirstname.value) + "', "
endif
if c_member.lastname <> oldval("c_member.lastname")
  lcsql = lcsql + "lastname = '" +;
          alltrim(thisform.txtlastname.value) + "', "
endif
<略去的代码>

如果 member 表中没有字段被修改,变量 lcsql 中将没有内容且不会保存信息到表中。如果有要保存的数据,构造剩下的 update 语句并发送到服务器。
if len(lcsql) > 0
  * 添加 update,去掉最后一个逗号,
  * 添加一个 where 子句
  lcsql = "update member set " + ;
          left(lcsql, len(lcsql) - 2) + ;
          "where member_no = " + ;
          alltrim(thisform.txtmemberid.value)
  if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代码>

然后对 adult 进行与上面相同的处理。接着要做的是我们非常熟悉的事。如果一切正常提交事务处理否则回滚。

删除一个成员

使用 sql pass-through 相对于远程视图的一个优点是,你增加了对发生什么和何时发生的控制。当用户单击 delete 按钮时运行的代码就是一个好的例子。
有很多理由可能使你不能删除一个成员。如果成员有相关的 juveniles 或成员有未结清的借书时任何 delete 都会失败。可以很容易地通过发送一个 select 到服务器来检查这一点。这里的代码使用 sqlexec() 来检查这两个条件。如果两者都为真,会显示一个友好的信息且不会进一步发生什么情况。
* 首先检查是否有一个活动的 juveniles
lcsql = "select member_no from juvenile " + ;
        "where adult_member_no = " + ;
        thisform.txtmemberid.value
if sqlexec(thisform.nhandle, lcsql) < 0
  thisform.showerror
  return
else
  if reccount("sqlresult") <> 0  
    lcmessage = "该成员不能删除. " + ;
                "他是一个活动的少年的成年。"
    = messagebox(lcmessage, mb_iconinformation)
    return
  endif  
endif

* 现在检查成员还有活动的借书
lcsql = "select member_no from loan " + ;
        "where member_no = " + ;
        thisform.txtmemberid.value
if sqlexec(thisform.nhandle, lcsql) < 0
  thisform.showerror
  return
else
  if reccount("sqlresult") <> 0  
    lcmessage = "该成员不能删除。" + ;
                "他还有活动的借书。"
    = messagebox(lcmessage, mb_iconinformation)
    return
  endif  
endif

如果还需要执行额外的检查,代码可以放在以上代码的后面。你可以完全控制要检查的内容和顺序。如果所有的检查都成功且成员可以删除,则开始一个事务处理。
在 member 表和 loanhist 及 reservation 表单定义了关系。每一次借书和还书都在 loanhist 表中有一条记录。成员预约的每一本书在 reservation 表中有一条记录。如果成员被删除,需要删除这两个表中的相关信息。需要首先删除它们,否则会违犯参照完整性。
* 删除该成员的借书情况(loan history)记录
lcsql = "delete loanhist where member_no = " + ;
        alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代码>

* 删除该成员的借书预约(loan reservation)记录
lcsql = "delete reservation where member_no = " + ;
        alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代码>
要删除一个成人成员必须首先删除 adult 表中的记录然后才能删除 member 表中的记录。这也是事务处理的一部分,因此如何有任何错误发生则回滚所有处理。
* 删除成员
lcsql = "delete adult where member_no = " + ;
        alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代码>

lcsql = "delete member where member_no = " + ;
        alltrim(thisform.txtmemberid.value)
if sqlexec(thisform.nhandle, lcsql) < 0
<略去的代码>

如果所有删除都没有问题则提交整个事务处理且成员被删除。然后用户会看到一个空的屏幕,因此添加一条空记录到 c_member 并刷新表单显示。

问题

如何折衷地使用远程视图和 sql pass-through? 

这是更多的工作

显然,使用 sql pass-through 比使用远程视图需要做更多的工作。视图为你做了大量的工作,维护与服务器的通信和传递 inserts,updates 和 deletes。视图是易于设置和使用的。

你拥有更多的控制

你看到了两种观点的示例,使用远程视图的一个问题是在 visual foxpro 和后端间的通信上你只有较少的控制。对于多数据库方案,这不是一个问题。但是,在这里使用的方案中出现了问题。问题用每一个表使用一个视图得到了缓解但任然存在。当视图不能给你以所需的数据输入能力时,问题会更为突出。
当你使用 sql pass-through 时,你可以完全控制 visual foxpro 与后端的通信。你构造 sql 语句并用 sqlexec() 来发送它们到服务器。如果需要执行数据验证和检查商业规则,你可以决定在什么时候和如何做。

错误信息可以更友好

因为你在控制什么发生,并且你手动的进行数据验证,因此你可以控制错误信息。在可以预见一些事发生时,你可以截取 sql server 错误信息并显示给用户一个可以理解的信息。由于不可预料的事件你也会遇到 sqlexec() 失败的问题,如网络故障。对于这些,你可以决定是否分解信息或以原始方式显示它们。

一方面它提供了较少的互用性

该方法的一个问题是从某种程度上牺牲了互用性。通过 sqlexec() 发送到后端的 sql 语句是用后端语言编写。在本示例中是 sql server。当转换到 oracle 时需要重写多少代码? 
虽然不同后端的基本的 select,insert,update 或 delete 格式没有太大的不同。因此这里的示例可以很容易 转移到其它后端。但是,重要的一点是取决于你使用的 sql 语句的复杂性,这可能限制了你交换后端的能力。当然,如果应用程序是为一种后端且只用一种后端,这将不是大的问题。

另一方面它提供了更多的互用性

考虑如果你使用远程视图而且你试着删除一个没有结清借书的成员会发生什么。在服务器上定义的参照完整性会防止删除。但是,由 oracle 送回的错误信息将与 sql server 送回的错误信息不同。你可以分解错误信息并将它们转换到友好的格式,但是你将不得不分解不同的后端的信息。这就限制了你的互用性,因为你不得不为不同的服务器创建分解例程。
使用 sql pass-through 方法,你会发送一个 select 语句到后端在 loan 表中搜索成员。如果 select 找到一条记录则成员不能删除。显示给用户的信息是相同的,而不论在 sqlexec() 中发送了什么。这种服务增了你的互用性,假定不同后端的 selects,inserts,updates 和 deletes 格式没有多大变化,这是一种通情达理的假设。

发表评论 共有条评论
用户名: 密码:
验证码: 匿名发表