Visual FoxPro (VFP) 通過 ODBC 封裝提供兩種訪問遠程異構資料庫(SQL Server、Oracle、DB2 等)的技術:遠程視圖與SQL Pass-Through (SPT)。遠程視圖是 SPT 的可視化子集,操作簡單但功能受限;SPT 雖無圖形界面、需手工管理連接,卻擁有多游標返回、非 SELECT 執行、存儲過程調用、遠程函數執行、客戶端事務控制五大核心優勢,是構建健壯 C/S 架構的關鍵利器。本文以 SQL Server Northwind 庫為例,系統講解 SPT 核心操作模式。
* 設置環境:從不顯示 ODBC 登錄對話框
SQLSETPROP(0, "DispLogin", 3)
* 建立連接字符串(DSN-less 推薦,避免客戶端配置 DSN)
LOCAL lcConnStr, hConn
lcConnStr = "Driver=SQL Server;Server=BOE;Uid=sa;Pwd=;Database=Northwind;"
* 同步連接,返回連接句柄(>0 成功,<0 失敗)
hConn = SQLSTRINGCONNECT(lcConnStr)
IF hConn <= 0
MESSAGEBOX("連接失敗:" + SQLERRMSG(), 16, "VFP SPT Demo")
RETURN
ELSE
MESSAGEBOX("連接成功,句柄:" + TRANSFORM(hConn), 64, "VFP SPT Demo")
ENDIF
* 斷開連接(程序結束前必須調用,防止連接泄漏)
SQLDISCONNECT(hConn)
最佳實踐: 連接句柄hConn應作為全局/會話級變量持有,避免重複連接開銷。異常分支務必TRY...CATCH...FINALLY SQLDISCONNECT(hConn)保證釋放。
LOCAL lcSQL, lnCursorCount
lcSQL = "SELECT * FROM Employees;" + CHR(10) + ;
"SELECT * FROM Customers;" + CHR(10) + ;
"SELECT * FROM Products;"
* SQLEXEC 返回游標數量,默認命名 TEMP, TEMP1, TEMP2...
lnCursorCount = SQLEXEC(hConn, lcSQL, "TEMP")
? "返回游標數:" + TRANSFORM(lnCursorCount) && 3
* 使用游標
SELECT TEMP
LIST FIRST 5 FIELDS EmployeeID, LastName, FirstName
SELECT TEMP1
LIST FIRST 5 FIELDS CustomerID, CompanyName
SELECT TEMP2
LIST FIRST 5 FIELDS ProductID, ProductName
* 釋放游標
USE IN SELECT("TEMP")
USE IN SELECT("TEMP1")
USE IN SELECT("TEMP2")
LOCAL lcSQL, lnResult
lcSQL = "IF EXISTS (SELECT * FROM Customers WHERE CustomerID = 'TEST') " + ;
"DELETE FROM Customers WHERE CustomerID = 'TEST'; " + ;
"ELSE " + ;
"INSERT INTO Customers (CustomerID, CompanyName) VALUES ('TEST', '這是一個測試!');"
lnResult = SQLEXEC(hConn, lcSQL)
IF lnResult < 0
MESSAGEBOX("執行失敗:" + SQLERRMSG(), 16, "SPT DML")
ELSE
MESSAGEBOX("執行成功,影響行數:" + TRANSFORM(SQLROWSETCOUNT(hConn)), 64, "SPT DML")
ENDIF
* 方案一:字符串拼接(需嚴格轉義,不推薦生產用)
LOCAL lcCustID
lcCustID = 'TEST'
lcSQL = "IF EXISTS (SELECT * FROM Customers WHERE CustomerID = '" + lcCustID + "') ..."
* 方案二:SPT 標準參數標記「?」(推薦,驅動層處理類型轉換與轉義)
lcSQL = "IF EXISTS (SELECT * FROM Customers WHERE CustomerID = ?lcCustID) " + ;
"DELETE FROM Customers WHERE CustomerID = ?lcCustID; " + ;
"ELSE INSERT INTO Customers (CustomerID, CompanyName) VALUES (?lcCustID, '測試');"
SQLEXEC(hConn, lcSQL) && VFP 自動綁定內存變量 lcCustID
* 調用帶返回值的存儲過程
LOCAL lcSQL, lnResult
lcSQL = "{? = CALL GetCustomerOrderCount('ALFKI')}" && ODBC CALL 語法
lnResult = SQLEXEC(hConn, lcSQL, "spResult")
IF lnResult > 0
SELECT spResult
? "ALFKI 訂單數:" + TRANSFORM(spResult.ReturnValue)
USE IN SELECT("spResult")
ENDIF
* 獲取服務器時間(演示特殊函數調用)
SQLEXEC(hConn, "SELECT GETDATE() AS ServerDateTime", "tmpTime")
SELECT tmpTime
? "服務器當前時間:" + TRANSFORM(tmpTime.ServerDateTime)
USE IN SELECT("tmpTime")
VFP SPT 事務綁定於連接句柄,該連接上所有 SQLEXEC 執行的 DML 直至 SQLCOMMIT/SQLROLLBACK 形成原子單元。
* 開啟手工事務模式(2=手工,1=自動提交默認)
SQLSETPROP(hConn, "Transactions", 2)
LOCAL lcSQL, lnExec
lcSQL = "DELETE FROM Customers WHERE CustomerID = 'BLAUS';" + CHR(10) + ;
"INSERT INTO Customers (CustomerID, CompanyName) VALUES ('TEST1', '測試事務');"
lnExec = SQLEXEC(hConn, lcSQL)
IF lnExec > 0
SQLCOMMIT(hConn) && 提交事務
? "事務提交成功"
ELSE
SQLROLLBACK(hConn) && 回滾事務
? "事務回滾:" + SQLERRMSG()
ENDIF
* 恢復自動提交模式,防止後續操作誤入未知事務
SQLSETPROP(hConn, "Transactions", 1)
關鍵點:
- 事務範圍 = 連接句柄。同一連接上所有 DML 自動納入。
- 異常分支必須
SQLROLLBACK,否則鎖持有至連接斷開,阻塞其他會話。- 完成業務單元後立即
SQLSETPROP(..., "Transactions", 1)重置模式,避免「殭屍事務」。
| 劣勢 | 影響 | 規避策略 |
|---|---|---|
| 無圖形界面 | 開發調試依賴代碼、PRINT、DEBUGGER | 封裝通用 SPT 類庫、統一錯誤處理、日誌記錄 |
| 手工維護連接 | 易泄漏、並發控制複雜 | 連接池模式(單例/多例)、RAII 風格封裝、超時檢查釋放 |
| 默認只讀游標 | 需額外設置 CursorAdapter 或 SQLSETPROP(..., "FetchMemo", .T.) 才可更新 | 僅讀場景用默認;需更新時顯式 SQLSETPROP(hConn, "CursorType", 1) 或使用 CursorAdapter |