Получение и обновление данных с помощью CursorAdapters

Дуг Хенниг

Новый базовый класс 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 из этой статьи.

Автор публикации

не в сети 4 недели

Михаил Леменёв

Комментарии: 0Публикации: 7Регистрация: 02-10-2022
Материалы по теме
Оставить комментарий
//////////////// ///////////////
Авторизация
*
*
Генерация пароля