Как в VFP 7.0 и выше обратиться к Хранимой Процедуре VFP-базы данных через OLE DB |
> Попробовал через OLE DB обратиться к ХП VFP-базы данных,
> однако получаю ошибки... Можно это как-то побороть?
Признаться у меня тоже получилось не сразу :-) ... И я наткнулся по крайней мере на три проблемы:
Обратите также внимание на то, что поскольку функция вместе со значениями параметров передаётся как команда в виде единой строки, то для параметров типа строки (если такие имеются) возникает проблема выбора ограничителей строки в зависимости от значений в таких строковых параметров... :-(
Ниже два примера кода, показывающие как можно осуществить вызов процедуры RemainingCredit() из демонстрационного примера TasTrade.
1. Пример кода на JScript:
//////////// Save as file: SP_test.wsf <package> <job> <script id="main" language="jscript"> var gcCntStr = "Provider=VFPOLEDB.1;Data Source=c:\\program files\\microsoft visual foxpro 7\\samples\\tastrade\\data\\tastrade.dbc;"; var goCnt = null; // As ADODB.Connection var goCmd = null; // As ADODB.Command var goRS = null; // As ADODB.Recordset var gvRetVal = 0; // As Variant var gErrCode = 1; var adStateOpen = 1; var adStateClosed = 0; var adCmdText = 1; try { goCnt = WScript.CreateObject("ADODB.Connection"); goCnt.ConnectionString = gcCntStr; goCnt.Open(); if (goCnt.State == adStateOpen) { goCmd = WScript.CreateObject("ADODB.Command"); goCmd.CommandText = "RemainingCredit('ALFKI')"; goCmd.CommandType = adCmdText; goCmd.ActiveConnection = goCnt; goRS = goCmd.Execute; gvRetVal = goRS.Fields(0).Value; goCnt.Close(); gErrCode = 0; } } catch(err) { WScript.Echo("Error occurred" + "\nCode: " + hex(err.number) + "\nDescription: " + err.description); gErrCode = 1; } finally { if (goCnt != null && goCnt.State == adStateOpen) { goCnt.Close(); } goRS = null; goCmd = null; goCnt = null; if (gErrCode == 0) { WScript.Echo("RemainingCredit('ALFKI') = " + gvRetVal); } WScript.Quit(gErrCode); } ////////////////////////// function hex(nmb) { if (nmb > 0) return nmb.toString(16); else return (nmb + 0x100000000).toString(16); } </script> </job> </package> //////////// The end of file: SP_test.wsf
2. Пример кода на CSharp
//////////// File: SP_test.cs using System; using System.Data; using System.Data.OleDb; namespace ConsoleApp { ////// Summary description for MyCls. /// class MyCls { ////// The main entry point for the application. /// [STAThread] static void Main(string[] args) { const string strCntStr = @"Provider=VFPOLEDB.1;Data Source=c:\program files\microsoft visual foxpro 7\samples\tastrade\data\tastrade.dbc;"; const string strCmd = @"RemainingCredit('ALFKI')"; OleDbConnection oleDbConnection = null; OleDbCommand oleDbCommand = null; object objRetVal = null; try { oleDbConnection = new OleDbConnection(); oleDbConnection.ConnectionString = strCntStr; oleDbConnection.Open(); if (oleDbConnection.State == ConnectionState.Open) { oleDbCommand = oleDbConnection.CreateCommand(); oleDbCommand.CommandType = CommandType.Text; oleDbCommand.CommandText = strCmd; objRetVal = oleDbCommand.ExecuteScalar(); oleDbCommand.Cancel(); if (objRetVal != null) { System.Console.WriteLine(strCmd + " = " + objRetVal); } } } catch(OleDbException e) { string Msg = e.Message; System.Console.WriteLine(Msg); } finally { if (oleDbCommand != null) { oleDbCommand.Dispose(); oleDbCommand = null; } if (oleDbConnection != null) { if (oleDbConnection.State != ConnectionState.Closed) { oleDbConnection.Close(); } oleDbCommand = null; } } } } } //////////// The end of file: SP_test.cs
см. также: Q299820 Knowledge Base Articles HOWTO: Execute a Stored Procedure in a VFP Database with the VFP OLE DB Provider
Начиная с версии 8.0 возможно использование коллекции параметров, так ниже приведён C#-код, показывающий как можно выполнить параметризованный запрос 'Employee listing' из VFP-базы данных Tastrade.dbc. Чтобы получить определяющее выражение SQL-SELECT параметризованного View, нужно добавить в VFP-базу данных Tastrade.dbc хранимую процедуру __spDbGetProp(), являющуюся обёрткой для обращения к VFP-функции BDGETPROP().
FUNCTION __spDbGetProp LPARAMETERS tcName, tcType, tcProperty RETURN DBGETPROP(tcName, tcType, tcProperty) ENDFUNCдалее код, демонстрирующий выполнение этого параметризованного View, может быть таким:
using System; using System.Data; using System.Data.OleDb; namespace VfpPrmView { ///Как видите, здесь применено решение типа: "если гора не идёт к Магомету, то Магомет идёт к годе"... :-) здесь также коллекция параметров (OleDbCommand.Parameters) использована дважды: первый раз - для определения значений параметров у хранимой процедуры __spDbGetProp(tcName, tcType, tcProperty), второй - для задания значения (m_strPrmValue = "Sales") параметра (m_strPrmView = "?cTitle") в выражении SQL-SELECT у View 'Employee listing'. Обратите внимание, что если у вас нестандартная установка примеров, поставляемых с VFP 8 (и/или не 8), то перед попыткой выполнения приведённого выше кода, вам необходимо подкорректировать константу m_strCntStr соответствующим образом./// Summary description for VfpPrmView /// class testVfpOleDb { ////// The main entry point for the application. /// [STAThread] static void Main(string[] args) { VfpOleDb vfpOleDb = new VfpOleDb(); if (vfpOleDb.GetVfpViewData()) { System.Console.WriteLine("Ok!"); } else { System.Console.WriteLine("Error"); } } } class VfpOleDb { static string m_strCntStr = "Provider=VFPOLEDB.1;Data Source=C:\\PROGRAM FILES\\MICROSOFT VISUAL FOXPRO 8\\SAMPLES\\TASTRADE\\DATA\\TASTRADE.DBC;Mode=Share Deny None;Extended Properties=\"\";User ID=\"\";Mask Password=False;Cache Authentication=False;Encrypt Password=False;Collating Sequence=MACHINE;DSN=\"\""; static string m_strCmdProp = "__spDbGetProp(?,?,?)"; // SP to call VFP-function DBGETPROP() static string [] m_asPrmName = new string [] {"tcName", "tcType", "tcProperty"}; // args names in __spDbGetProp() static string [] m_asPrmValues = new string [] {"Employee listing", "view", "SQL"}; // args values to call __spDbGetProp() static string m_strPrmView = "?cTitle"; // parameter in VFP-view: 'Employee listing' static string m_strPrmValue = "Sales"; // parameter value System.Data.OleDb.OleDbConnection m_cnt; public VfpOleDb(){} public bool GetVfpViewData() { bool bRetVal = false; if (!CreateConnect()) { return bRetVal; } try { ///////////////////////////////////////////////// // (1) Call SP to get SQL-SELECT from VFP-database using (OleDbCommand cmdProp = m_cnt.CreateCommand()) { cmdProp.CommandText = m_strCmdProp; cmdProp.CommandType = CommandType.Text; for (int nInd = 0; nInd < m_asPrmName.Length; nInd++) { cmdProp.Parameters.Add(m_asPrmName[nInd], OleDbType.Char, m_asPrmValues[nInd].Length); cmdProp.Parameters[m_asPrmName[nInd]].Value = m_asPrmValues[nInd]; } if (OpenConnect()) { object objView = cmdProp.ExecuteScalar(); if (objView != null) { // Ok! SET ANSI OFF using (OleDbCommand cmdSet = m_cnt.CreateCommand()) { cmdSet.CommandText = "SET ANSI OFF"; if(OpenConnect()) { cmdSet.ExecuteNonQuery(); } } ///////////////////////////////////////////////// // (2) Replace parameter '?MyParamName' to '?' string strSrcSelectView = objView.ToString(); int nPrmPos = strSrcSelectView.IndexOf(m_strPrmView); if (nPrmPos != (-1)) { string strCmdSelectView = strSrcSelectView.Substring(0, nPrmPos) + "?" + strSrcSelectView.Substring(nPrmPos + m_strPrmView.Length); ///////////////////////////////////////////////// // (3) Execute SQL-SELECT using (OleDbCommand cmdView = m_cnt.CreateCommand()) { cmdView.CommandText = strCmdSelectView; cmdView.CommandType = CommandType.Text; // Set value to parameter cmdView.Parameters.Add("0", OleDbType.Char, m_strPrmValue.Length); cmdView.Parameters["0"].Value = m_strPrmValue; if (OpenConnect()) { // Execute SQL-SELECT OleDbDataReader oRdr = cmdView.ExecuteReader(CommandBehavior.CloseConnection); if (oRdr != null) { ///////////////////////////////////////////////// // (4) Show results int nRowCount = 0; int nFld = 0; string sFldName = null; while (oRdr.Read()) { System.Console.Write("\n--- row: {0}", nRowCount.ToString()); for (nFld = 0; nFld < oRdr.FieldCount; nFld++) { sFldName = oRdr.GetName(nFld); System.Console.Write("\n{0}", sFldName); System.Console.Write(" = {0}",oRdr.GetValue(oRdr.GetOrdinal(sFldName))); } nRowCount++; } oRdr.Close(); bRetVal = (nRowCount > 0); if (bRetVal) { System.Console.Write("\n===\n"); } } } } } } } } } catch (Exception e) { ShowException(e); } finally { CloseData(); } return bRetVal; } bool CreateConnect() { CloseData(); try { m_cnt = new OleDbConnection(m_strCntStr); } catch (Exception e) { ShowException(e); } return (m_cnt != null); } bool OpenConnect() { bool bRetVal = false; if (m_cnt != null) { if (m_cnt.State != System.Data.ConnectionState.Open) { m_cnt.Open(); } bRetVal = (m_cnt.State == System.Data.ConnectionState.Open); } return bRetVal; } void CloseData() { if (m_cnt != null) { try { if (m_cnt.State != System.Data.ConnectionState.Closed) { m_cnt.Close(); } } catch (NullReferenceException e) { string strSkipMsg = e.Message; } finally { m_cnt = null; } } } ~VfpOleDb() { CloseData(); } void ShowException(Exception e) { System.Console.WriteLine("Message: {0}", e.Message); System.Console.WriteLine("Source: {0}", e.Source); } } }
Как пример C#-приложения, использующего данные из VFP-базы данных, можно посмотреть TestUrls-приложение в csurltst.zip (151KB) (MS VS.NET 2003 MS Framework 1.1 SP1), позволяющее выполнять тестирование внешних html-ссылок, там же приведён код ViewResult-приложения для просмотра результатов, получаемых через VFP-SP gettestresult().
Было также установлена проблема Code Page при обращении к хранимым процедурам через MS VFP OLE DB Provider (не из-под VFP-приложений). В том смысле, что на втором и последующих обращениях к хранимой процедуре значение CPCURRENT() перестаёт быть 1251, а изменяется на 1252. Никакие ухищрения с файлом Config.fpw, содержащим строку CODEPAGE=1251 и подложенным во все мыслемые места не помогают. Ситуация может быть исправлена прямым изменением файла vfpoledb.dll (см. в каталоге C:\Program Files\Common Files\System\Ole DB\). Ниже представлена информация где и что следует изменить:
Файл | Адрес | Что на что менять |
vfpoledb.dll (8.0.0.3006) | 0004af7e | E4->E3 |
vfpoledb.dll (8.0.0.3117) | 0004b43c | E4->E3 |
У vfpoledb.dll версий 8.0.0.2521 и 9.0.0.2412 проблемы не обнаружено. См. также адреса для исправлений аналогичной проблемы в vfpXt.dll в статье Как "работает" multi-threaded COM компонента под MS MTS/Component Services?
Было обнаружено, что при попытке использовать VFP 8.0 OLEDB Provider из-под MS IIS 6.0 (OS Windows 2003 Server) получаем: System.Data.OleDb.OleDbException: No error information available: REGDB_E_CLASSNOTREG(0x80040154). Однако, если создать обычное Windows-приложение (например на C# с доступом через System.Data.OleDb), то всё работает без вопросов.
Это означает, что вы пытаетесь использовать версию 8.0.0.3006 и мне известны три варианта преодоления этой проблемы: