Дуг Хенниг
Новый базовый класс CursorAdapter, добавленный в VFP 8, обеспечивает простой в использовании и согласованный интерфейс для данных. В статье этого месяца показано, как использовать CursorAdapter для доступа к локальным и удаленным данным через ODBC, ADO и XML, а также обсуждаются конкретные требования и «подводные камни» для каждого из них.
Как я уже говорил в статье «Адаптируйте свои курсоры к CursorAdapters», одной из самых важных и интересных новых возможностей VFP 8 является новый базовый класс CursorAdapter. В прошлом месяце мы рассмотрели свойства, события и методы CursorAdapter и обсудили его преимущества по сравнению с использованием удаленных представлений, сквозной передачи SQL (SPT), ADO и XML.
Прежде чем вы начнете использовать CursorAdapter, вы должны знать об особых требованиях класса, когда вы используете его для доступа к собственным данным или удаленным данным через ODBC, ADO и XML. В статье этого месяца приводятся подробные сведения о каждом типе источника данных.
Использование собственных данных
Хотя ясно, что CursorAdapter предназначен для стандартизации и упрощения доступа к данным, не относящимся к VFP, вы можете использовать его вместо Cursor, установив для свойства DataSourceType значение «Native». Зачем тебе это? В основном, как взгляд в будущее, когда ваше приложение может быть увеличено; просто изменив DataSourceType на один из других вариантов (и, вероятно, изменив несколько других свойств, таких как настройка информации о соединении), вы можете легко переключиться на другое ядро базы данных, такое как SQL Server.
Когда для DataSourceType установлено значение «Native», VFP игнорирует свойство DataSource. SelectCmd должен быть оператором SQL SELECT, а не командой или выражением USE, поэтому это означает, что вы всегда работаете с эквивалентом локального представления, а не непосредственно с таблицей. Вы несете ответственность за то, чтобы VFP мог найти любые таблицы, на которые ссылается оператор SELECT, поэтому, если таблицы не находятся в текущем каталоге, вам нужно либо указать путь, либо открыть базу данных, к которой принадлежат таблицы. Как обычно, если вы хотите, чтобы курсор был обновляемым, обязательно установите свойства обновления (KeyFieldList, Tables, UpdatableFieldList и UpdateNameList).
В следующем примере (NativeExample.prg, включенном в загрузку для подписчиков в этом месяце) создается обновляемый курсор из таблицы Customer в образце базы данных TestData VFP:
local loCursor as CursorAdapter, ; laErrors[1] open database (_samples + 'data\testdata') loCursor = createobject('CursorAdapter') with loCursor .Alias = 'customercursor' .DataSourceType = 'Native' .SelectCmd = ; "select CUST_ID, COMPANY, CONTACT from CUSTOMER " + ; "where COUNTRY = 'Brazil'" .KeyFieldList = 'CUST_ID' .Tables = 'CUSTOMER' .UpdatableFieldList = 'CUST_ID, COMPANY, CONTACT' .UpdateNameList = 'CUST_ID CUSTOMER.CUST_ID, ' + ; 'COMPANY CUSTOMER.COMPANY, CONTACT CUSTOMER.CONTACT' if .CursorFill() browse tableupdate(1) else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill() endwith close databases all
Использование ODBC
ODBC на самом деле является самой простой из четырех настроек DataSourceType. Вы устанавливаете DataSource в открытый дескриптор соединения ODBC, устанавливаете обычные свойства и вызываете CursorFill для извлечения данных. Если вы заполните KeyFieldList, Tables, UpdatableFieldList и UpdateNameList, VFP автоматически сгенерирует соответствующие операторы UPDATE, INSERT и DELETE для обновления серверной части с учетом любых изменений. Если вместо этого вы хотите использовать хранимую процедуру, установите свойства *Cmd, *CmdDataSource и *CmdDataSourceType соответствующим образом (замените «*» на «Delete», «Insert» и «Update»).
Вот пример, взятый из ODBCExample.prg, который вызывает хранимую процедуру CustOrderHist в базе данных Northwind, поставляемой с SQL Server, для получения общего количества проданных единиц продукта для конкретного клиента:
local lcConnString, ; loCursor as CursorAdapter, ; laErrors[1] lcConnString = 'driver=SQL Server;server=(local); ' + ; 'database=Northwind;uid=sa;pwd=;trusted_connection=no' * измените пароль на соответствующее значение для вашей базы данных loCursor = createobject('CursorAdapter') with loCursor .Alias = 'CustomerHistory' .DataSourceType = 'ODBC' .DataSource = sqlstringconnect(lcConnString) .SelectCmd = "exec CustOrderHist 'ALFKI'" if .CursorFill() browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill() endwith
Использование ADO
Использование ADO в качестве механизма доступа к данным с CursorAdapter имеет несколько больше проблем, чем использование ODBC:
- DataSource должен быть установлен в ADO RecordSet, в котором свойство ActiveConnection установлено на открытый объект ADO Connection.
- Если вы хотите использовать параметризованный запрос (что, скорее всего, является обычным случаем, а не получением всех записей), вы должны передать объект ADO Command, свойство ActiveConnection которого установлено на открытый объект ADO Connection в качестве четвертого параметра CursorFill. VFP позаботится о заполнении коллекции Parameters объекта Command (он анализирует SelectCmd, чтобы найти параметры) для вас, но, конечно, переменные, содержащие значения параметров, должны быть в области видимости.
- Использовать один CursorAdapter с ADO в DataEnvironment очень просто: вы можете установить для UseDEDataSource значение .T., при желании установите свойства DataSource и DataSourceType для DataEnvironment, как если бы вы использовали CursorAdapter. Однако это не работает, если в DataEnvironment имеется более одного CursorAdapter. Причина в том, что набор записей ADO, на который ссылается DataEnvironment.DataSource, может содержать только данные одного CursorAdapter; когда вы вызываете CursorFill для второго CursorAdapter, вы получаете ошибку «RecordSet уже открыт». Таким образом, если в вашем DataEnvironment имеется более одного CursorAdapter, вы должны установить для UseDEDataSource значение .F и самостоятельно управлять свойствами DataSource и DataSourceType каждого CursorAdapter (или, возможно, использовать подкласс DataEnvironment, который управляет этим за вас).
В приведенном ниже примере кода, взятом из ADOExample.prg, показано, как получить данные с помощью параметризованного запроса с помощью объекта ADO Command. В этом примере также показано использование новых структурированных функций обработки ошибок в VFP 8; вызов метода ADO Connection Open заключен в оператор TRY ... CATCH ... ENDTRY для перехвата ошибки COM, которую метод выдаст в случае сбоя.
local loConn as ADODB.Connection, ; loCommand as ADODB.Command, ; loException as Exception, ; loCursor as CursorAdapter, ; lcCountry, ; laErrors[1] loConn = createobject('ADODB.Connection') with loConn .ConnectionString = ; 'provider=SQLOLEDB.1;data source=(local);' + ; 'initial catalog=Northwind;uid=sa;pwd=;' + ; 'trusted_connection=no' * изменить пароль на соответствующее значение для вашей базы данных try .Open() catch to loException messagebox(loException.Message) cancel endtry endwith loCommand = createobject('ADODB.Command') loCursor = createobject('CursorAdapter') with loCursor .Alias = 'Customers' .DataSourceType = 'ADO' .DataSource = createobject('ADODB.RecordSet') .SelectCmd = ; 'select * from customers where country=?lcCountry' lcCountry = 'Brazil' .DataSource.ActiveConnection = loConn loCommand.ActiveConnection = loConn if .CursorFill(.F., .F., 0, loCommand) browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill(.F., .F., 0, loCommand) endwith
Использование XML
Использование XML с CursorAdapters требует некоторых дополнительных вещей. Вот проблемы:
- Свойство DataSource игнорируется.
- Свойство CursorSchema должно быть заполнено, даже если вы передаете .F. в качестве первого параметра метода CursorFill, иначе вы получите сообщение об ошибке.
- Для свойства SelectCmd должно быть задано выражение, такое как определяемая пользователем функция (UDF) или имя метода объекта, которое возвращает XML для курсора.
- Изменения, внесенные в курсор, преобразуются в DiffGram, который представляет собой XML, содержащий значения до и после измененных полей и записей, и помещаются в свойство DiffGram, когда требуется обновление.
- Чтобы записать изменения обратно в источник данных, для параметра UpdateCmdDataSourceType должно быть установлено значение «XML», а для параметра UpdateCmd должно быть задано выражение (опять же, вероятно, UDF или метод объекта), которое обрабатывает обновление. Вы, вероятно, захотите передать «This.DiffGram» в UDF, чтобы он мог отправить изменения в источник данных.
Источник XML для курсора может поступать из разных мест. Например, вы можете вызвать UDF, который преобразует курсор VFP в XML с помощью CURSORTOXML() и возвращает результаты:
use CUSTOMERS cursortoxml('customers', 'lcXML', 1, 8, 0, '1') return lcXML
Пользовательская функция может вызывать веб-службу, которая возвращает набор результатов в виде XML. Вот пример, который IntelliSense сгенерировал для меня из веб-службы, которую я создал и зарегистрировал в своей собственной системе (детали не важны, это просто пример веб-службы).
loWS = newobject('Wsclient', ; home() + 'ffc\_webservices.vcx') loWS.cWSName = 'dataserver web service' loWS = loWS.SetupClient('http://localhost/' + ; 'SQDataServer/dataserver.WSDL', 'dataserver', ; 'dataserverSoapPort') lcXML = loWS.GetCustomers() return lcXML
Он может использовать SQLXML 3.0 для выполнения запроса SQL Server 2000, хранящегося в файле шаблона на веб-сервере (для получения дополнительной информации о SQLXML перейдите на http://msdn.microsoft.com и выполните поиск SQLXML). В следующем коде объект MSXML2.XMLHTTP используется для получения всех записей из таблицы клиентов Northwind через HTTP; это будет объяснено более подробно позже.
local loXML as MSXML2.XMLHTTP loXML = createobject('MSXML2.XMLHTTP') loXML.open('POST', 'http://localhost/northwind/ ' + ; 'template/getallcustomers.xml, .F.)' loXML.setRequestHeader('Content-type', 'text/xml') loXML.send() return loXML.responseText
Обработка обновлений более сложная. Источник данных должен либо быть способен принимать и использовать DiffGram (как в случае с SQL Server 2000), либо вам придется самостоятельно выяснять изменения и выполнять ряд операторов SQL (UPDATE, INSERT и DELETE), чтобы выполнить обновления.
Вот пример (XMLExample.prg), в котором CursorAdapter используется с источником данных XML. Обратите внимание, что и SelectCmd, и UpdateCmd вызывают пользовательские функции. В случае SelectCmd извлекаемый идентификатор клиента передается пользовательской функции GetNWCustomers, которую мы рассмотрим чуть позже. Для UpdateCmd VFP передал свойство DiffGram в SendNWXML, которое мы также рассмотрим позже.
local loCustomers as CursorAdapter, ; laErrors[1] loCustomers = createobject('CursorAdapter') with loCustomers .Alias = 'Customers' .CursorSchema = ; 'CUSTOMERID C(5), COMPANYNAME C(40), ' + ; 'CONTACTNAME C(30), CONTACTTITLE C(30), ' + ; 'ADDRESS C(60), CITY C(15), REGION C(15), ' + ; 'POSTALCODE C(10), COUNTRY C(15), ' + ; 'PHONE C(24), FAX C(24)' .DataSourceType = 'XML' .KeyFieldList = 'CUSTOMERID' .SelectCmd = 'GetNWCustomers([ALFKI])' .Tables = 'CUSTOMERS' .UpdatableFieldList = ; 'CUSTOMERID, COMPANYNAME, CONTACTNAME, ' + ; 'CONTACTTITLE, ADDRESS, CITY, REGION, ' + ; 'POSTALCODE, COUNTRY, PHONE, FAX' .UpdateCmdDataSourceType = 'XML' .UpdateCmd = 'SendNWXML(This.DiffGram)' .UpdateNameList = ; 'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ; 'COMPANYNAME CUSTOMERS.COMPANYNAME, ' + ; 'CONTACTNAME CUSTOMERS.CONTACTNAME, ' + ; 'CONTACTTITLE CUSTOMERS.CONTACTTITLE, ' + ; 'ADDRESS CUSTOMERS.ADDRESS, ' + ; 'CITY CUSTOMERS.CITY, ' + ; 'REGION CUSTOMERS.REGION, ' + ; 'POSTALCODE CUSTOMERS.POSTALCODE, ' + ; 'COUNTRY CUSTOMERS.COUNTRY, ' + ; 'PHONE CUSTOMERS.PHONE, ' + ; 'FAX CUSTOMERS.FAX' if .CursorFill(.T.) browse else aerror(laErrors) messagebox(laErrors[2]) endif .CursorFill(.T.) endwith
Вот код для GetNWCustomers. Он использует объект MSXML2.XMLHTTP для доступа к XML-шаблону SQL Server 2000 с именем CustomersByID.xml на веб-сервере и возвращает результаты. Идентификатор клиента, который необходимо получить, передается в качестве параметра этому коду.
lparameters tcCustID local loXML as MSXML2.XMLHTTP loXML = createobject('MSXML2.XMLHTTP') loXML.open('POST', 'http://localhost/northwind/' + ; 'template/customersbyid.xml?customerid=' + tcCustID, ; .F.) loXML.setRequestHeader('Content-type', 'text/xml') loXML.send() return loXML.responseText
Обработка обновлений более сложная. Источник данных должен либо быть способен принимать и использовать DiffGram (как в случае с SQL Server 2000), либо вам придется самостоятельно выяснять изменения и выполнять ряд операторов SQL (UPDATE, INSERT и DELETE), чтобы выполнить обновления.
Вот пример (XMLExample.prg), в котором CursorAdapter используется с источником данных XML. Обратите внимание, что и SelectCmd, и UpdateCmd вызывают пользовательские функции. В случае SelectCmd извлекаемый идентификатор клиента передается пользовательской функции GetNWCustomers, которую мы рассмотрим чуть позже. Для UpdateCmd VFP передал свойство DiffGram в SendNWXML, которое мы также рассмотрим позже.
local loCustomers as CursorAdapter, ;
laErrors[1]
loCustomers = createobject('CursorAdapter')
with loCustomers
.Alias = 'Customers'
.CursorSchema = ;
'CUSTOMERID C(5), COMPANYNAME C(40), ' + ;
'CONTACTNAME C(30), CONTACTTITLE C(30), ' + ;
'ADDRESS C(60), CITY C(15), REGION C(15), ' + ;
'POSTALCODE C(10), COUNTRY C(15), ' + ;
'PHONE C(24), FAX C(24)'
.DataSourceType = 'XML'
.KeyFieldList = 'CUSTOMERID'
.SelectCmd = 'GetNWCustomers([ALFKI])'
.Tables = 'CUSTOMERS'
.UpdatableFieldList = ;
'CUSTOMERID, COMPANYNAME, CONTACTNAME, ' + ;
'CONTACTTITLE, ADDRESS, CITY, REGION, ' + ;
'POSTALCODE, COUNTRY, PHONE, FAX'
.UpdateCmdDataSourceType = 'XML'
.UpdateCmd = 'SendNWXML(This.DiffGram)'
.UpdateNameList = ;
'CUSTOMERID CUSTOMERS.CUSTOMERID, ' + ;
'COMPANYNAME CUSTOMERS.COMPANYNAME, ' + ;
'CONTACTNAME CUSTOMERS.CONTACTNAME, ' + ;
'CONTACTTITLE CUSTOMERS.CONTACTTITLE, ' + ;
'ADDRESS CUSTOMERS.ADDRESS, ' + ;
'CITY CUSTOMERS.CITY, ' + ;
'REGION CUSTOMERS.REGION, ' + ;
'POSTALCODE CUSTOMERS.POSTALCODE, ' + ;
'COUNTRY CUSTOMERS.COUNTRY, ' + ;
'PHONE CUSTOMERS.PHONE, ' + ;
'FAX CUSTOMERS.FAX'
if .CursorFill(.T.)
browse
else
aerror(laErrors)
messagebox(laErrors[2])
endif .CursorFill(.T.)
endwith
Вот код для GetNWCustomers. Он использует объект MSXML2.XMLHTTP для доступа к XML-шаблону SQL Server 2000 с именем CustomersByID.xml на веб-сервере и возвращает результаты. Идентификатор клиента, который необходимо получить, передается в качестве параметра этому коду.
lparameters tcCustID
local loXML as MSXML2.XMLHTTP
loXML = createobject('MSXML2.XMLHTTP')
loXML.open('POST', 'http://localhost/northwind/' + ;
'template/customersbyid.xml?customerid=' + tcCustID, ;
.F.)
loXML.setRequestHeader('Content-type', 'text/xml')
loXML.send()
return loXML.responseText
Шаблон XML, на который ссылается этот код, CustomersByID.XML, выглядит следующим образом:
<root xmlns:sql="urn:schemas-microsoft-com:xml-sql"> <sql:header> <sql:param name="customerid"> </sql:param> </sql:header> <sql:query client-side-xml="0"> SELECT * FROM Customers WHERE CustomerID = @customerid FOR XML AUTO </sql:query> </root>
Поместите этот файл в виртуальный каталог базы данных Northwind (см. врезку «Настройка SQL Server 2000 XML Access» для получения подробной информации о настройке IIS для работы с SQL Server, а также конкретных сведений, необходимых для этой статьи).
SendNWXML похож на GetNWCustomers, за исключением того, что он ожидает передачи DiffGram, загружает DiffGram в объект MSXML2.DOMDocument и передает этот объект веб-серверу, который, в свою очередь, передает его через SQLXML в SQL Server 2000 для обработки.
lparameters tcDiffGram local loDOM as MSXML2.DOMDocument, ; loXML as MSXML2.XMLHTTP loDOM = createobject('MSXML2.DOMDocument') loDOM.async = .F. loDOM.loadXML(tcDiffGram) loXML = createobject('MSXML2.XMLHTTP') loXML.open('POST', 'http://localhost/northwind/', .F.) loXML.setRequestHeader('Content-type', 'text/xml') loXML.send(loDOM)
Чтобы увидеть, как это работает, запустите XMLExample.prg. Вы должны увидеть одну запись (клиент ALFKI) в окне просмотра. Измените значение в каком-либо поле, затем закройте окно и снова запустите PRG. Вы должны увидеть, что ваше изменение было записано в бэкэнд.
Хотя базовый класс CursorAdapter обеспечивает согласованный интерфейс для удаленных данных независимо от того, используете ли вы ODBC, ADO или XML, существуют некоторые различия в настройке CursorAdapter в зависимости от выбранного механизма доступа к данным. Эти различия обусловлены требованиями самих механизмов доступа к данным.
В следующем месяце мы завершим изучение CursorAdapter, создав несколько повторно используемых классов данных и обсудив, как использовать CursorAdapter в отчетах.
Дуг Хенниг является партнером Stonefield Systems Group Inc. Он является автором отмеченных наградами инструментов Stonefield Database Toolkit (SDT) и Stonefield Query, соавтором книг «Что нового в Visual FoxPro 7.0» и «Руководства хакера по Visual FoxPro». 7.0», как от Hentzenwerke Publishing, так и от автора «Словаря данных Visual FoxPro» в серии Pros Talk Visual FoxPro от Pinnacle Publishing. Он был техническим редактором «Руководства хакера по Visual FoxPro 6.0» и «Основы» издательства Hentzenwerke Publishing. Дуг выступал на каждой конференции разработчиков Microsoft FoxPro (DevCon) с 1997 года, а также на пользовательских группах и конференциях разработчиков по всей Северной Америке. Он является Microsoft Most Valuable Professional (MVP) и Certified Professional (MCP). Интернет: www.stonefield.com и www.stonefieldquery.com Электронная почта: dhennig@stonefield.com
Настройка XML-доступа к SQL Server 2000
Чтобы получить доступ к SQL Server 2000 с помощью URL-адреса в браузере или другом HTTP-клиенте, вам нужно сделать несколько вещей. Во-первых, вам необходимо загрузить и установить SQLXML 3.0 с веб-сайта MSDN (http://msdn.microsoft.com; выполните поиск «SQLXML», а затем выберите ссылку для загрузки).
Далее вам нужно настроить виртуальный каталог IIS. Для этого выберите ярлык «Настроить поддержку IIS» в папке SQLXML 3.0 в разделе «Пуск», «Программы» на панели задач Windows. Разверните узел своего сервера, выберите веб-узел для работы, затем щелкните правой кнопкой мыши и выберите «Создать», «Виртуальный каталог». На вкладке Общие в появившемся диалоговом окне введите имя виртуального каталога и его физический путь. В этой статье используйте «Борей» в качестве имени виртуального каталога и «NorthwindTemplates» в качестве физического каталога. С помощью проводника Windows создайте физический каталог где-нибудь в вашей системе и создайте в нем подкаталог под названием «Шаблон» (мы будем использовать этот подкаталог чуть позже). Скопируйте два файла шаблонов GetAllCustomers.xml и CustomersByID.xml, включенные в загрузку для подписчиков в этом месяце, в подкаталог Template.
На вкладке «Безопасность» введите соответствующую информацию для доступа к SQL Server, например имя пользователя и пароль или конкретный механизм проверки подлинности, который вы хотите использовать. На вкладке Источник данных выберите SQL Server и, при желании, базу данных для использования. В качестве примера для этой статьи выберите базу данных Northwind. На вкладке «Настройки» выберите нужные параметры, но как минимум включите «Разрешить запросы шаблонов» и «Разрешить POST».
На вкладке «Виртуальные имена» выберите «шаблон» в поле со списком «Тип» и введите виртуальное имя (в этой статье используйте «шаблон») и физический путь (который должен быть подкаталогом виртуального каталога; в этой статье используйте «Шаблон ») для использования в шаблонах. Нажмите «ОК».
Чтобы убедиться, что все настроено правильно, мы получим доступ к SQL Server, используя файл шаблона GetAllCustomers.xml, который вы скопировали в подкаталог Template. Он имеет следующее содержание:
<root xmlns:sql="urn:schemas-microsoft-com:xml-sql">
<sql:query client-side-xml="0">
SELECT *
FROM Customers
FOR XML AUTO
</sql:query>
</root>
Чтобы проверить это, откройте браузер и введите следующий URL-адрес: http://localhost/northwind/template/getallcustomers.xml. Вы должны увидеть содержимое таблицы клиентов Northwind в виде XML в своем браузере. Вот первая часть того, что вы должны увидеть:
<root xmlns:sql="urn:schemas-microsoft-com:xml-sql">
<Customers CustomerID="ALFKI" CompanyName="Alfreds
Futterkiste" ContactName="Maria Anders"
ContactTitle="Sales Representative"
Address="Obere Str. 57" City="Berlin" PostalCode="12209"
Country="Germany" Phone="030-0074321"
Fax="999-999-9999" />
Теперь вы готовы запустить примеры SQLXML из этой статьи.