12 шагов по переводу приложения Visual FoxPro в архитектуру клиент-сервер, часть 2

Джим Фалино (Jim Falino)

В прошлом номере Джим опубликовал первую из двух статей, посвященных процессу перевода приложений Visual FoxPro в архитектуру клиент-сервер. В этом номере мы рассмотрим последние шесть шагов и завершим обсуждение демонстрацией формы, способной обращаться как к таблицам Visual FoxPro, так и к базе данных SQL Server.

 

На всякий случай хочу предупредить, что все примеры используют БД Northwind Traders, которая создается при установке SQL Server 7.0.

Шаг 7: Используйте искусственные первичные ключи для всех таблиц

Еще до того как версия 3.0 предоставила нам возможность создавать первичные ключи для таблиц БД, использование одного или группы полей для уникальной идентификации записи считалось правильным подходом.

Тем не менее, при работе с приложениями, обращающимися за данными на файл-сервер, реальной необходимости в первичных ключах не было. Когда вы начинаете работать с представлениями, ситуация меняется.

Для того чтобы иметь возможность модифицировать данные, полученные средствами удаленного представления, в таблице, использованной для создания представления, необходимо иметь первичный ключ (я редко использую модификации для представлений, построенных на основе более чем одной таблицы). Причина такого изменения в том, что представление создает локальный курсор, когда вы открываете его командой USE <имя представления> и вам нужен какой-то механизм, чтобы отыскать соответствующие записи в базе данных сервера, когда изменения записываются назад. При работе с файл-сервером это не всегда обязательно, так как запись можно редактировать непосредственно.

Чтобы получить возможность модификации записей на сервере вам следует либо указать первичный ключ при создании представления в Конструкторе, либо описать его средствами функции DBSETPROP(“Viewname.KeyField1”, “Field”, “KeyField”, .T.), либо назначить у активного курсора посредством свойства KeyFieldList с использованием функции CURSORSETPROP(“KeyFieldList”, <список полей, составляющих первичный ключ>).

В любом случае, представление будет использовать указанный первичный ключ для поиска записи при модификации или удалении. Ключи используются также для распознавания конфликтов модификаций - вы получаете сообщение об ошибке, если кто-то модифицировал первичный ключ или удалил запись, которую вы хотите модифицировать. Помимо того, что вы сможете модифицировать таблицы через представления, наличие первичного ключа также облегчает поиск индивидуальных записей. При работе в системах клиент-сервер, вам часто понадобится такая возможность.

Аргументы искусственного ключа

Если первичный ключ можно построить только на основании набора полей, я рекомендую использовать искусственные ключи, создаваемые системой. Использование такого ключа, обычно используется число, дает вам возможность получить уникальный идентификатор записи, вещь весьма полезная. Помимо этого, в дочерние таблицы стоит добавить одно или больше числовых полей, которые будут служить вторичным (или внешним) ключом. Перед созданием дочерней записи эти поля будут заполняться значениями из родительской таблицы.

Преимущества искусственных ключей-ускорение соединений таблиц, более высокая скорость обновлений и удалений и упрощение поиска дочерних записей. Единственная проблема - генерация значения ключа. Я использую системную таблицу, в которой хранится текущее значение ключа. В таблице имеется по одной записи на каждую пользовательскую таблицу базы данных. Однако такой подход может привести к проблемам при конкурентном доступе, если вести себя не осторожно. Так как собственно значение ключа никакого смысла не имеет (именно поэтому они и называются абстрактными ключами), не следует требовать новое значение ключа в середине транзакции. Это может создать слишком длительное ожидание на системной таблице, когда вам нужно получить значение ключа для работы с очень интенсивно используемой пользовательской таблицей. Лучше получить значение ключа за пределами транзакции даже с риском потерять его при откате изменений.

Возможно также генерировать значение ключа на рабочей станции. Это позволит избежать лишнего путешествия на сервер и, соответственно, конкуренции при доступе к системной таблице. Точно также как я никогда не даю уйти строителям без того, чтобы получить у них имя хорошего мастера по кровле, я никогда не упускаю возможности поинтересоваться у разработчиков их методами генерации уникальных ключей. Два интересных подходя, которые я слышал: использование глобально уникальных идентификаторов и получение с сервера уникального начального значения при старте приложения и затем использование локального свойства приложения для генерации ключа (сливайте две части для получения уникальной алфавитно - цифровой комбинации). Глобально уникальные идентификаторы (GUID) представляют собой строку из 26 символов, считаются абсолютно уникальными во всем мире и их очень быстро создавать. Эти строки довольно неприглядны на вид, но их уникальность стоит затраченных усилий.

Шаг 8: Добавьте каждой таблице поле timestamp.

Клиент-серверные приложения почти всегда используют оптимистическую блокировку. Учитывая этот факт, проверка на наличие конфликтов становится более сложной в реализации. Во время создания приложения, вы постоянно должны напоминать себе "я не обязательно работаю с самой последней версией данных”.

Хотя первичный ключ и позволяет определить конфликты модификаций, он поможет только выяснить, не удалил ли кто-нибудь запись, которую вы пытаетесь модифицировать. Первичный ключ не поможет определить, не внес ли кто-то новые данные, пока вы занимались редактированием.
Обычно клиент-серверные приложения используют некоторую форму метки "дата-время", которая обновляется при каждой модификации данных. Я говорю "некоторую форму" потому что тип данных можно использовать почти любой. Соответствующее поле анализируется при попытке обновления для выяснения, нет ли конфликта с тем значением, что в данный момент хранится на рабочей станции. Например, представление Visual FoxPro может создавать такую команду после обновления данных в представлении:


Replace state With ‘NY’ For city = ‘New York’ In vcustomers
TableUpdate(.T., .F., “vcustomers”):

* Эта часть кода создается автоматически
* и пересылается на сервер.
* По одной такой команде создается для каждой
* модифицированной записи.
Update customers Set state = ‘NY’ ;
Where cust_pkey = ?vcustomers.cust_pkey and ;
timestamp = ?vcustomers.timestamp

Если эта команда возвращает ошибку, связанную с конфликтом модификаций, вы можете сообщить пользователю, что кто-то внес изменения в ту же запись, которую он редактирует. Что делать дальше зависит от вашей доброй воли. Вы можете перезапросить данные представления или можете принудительно подавить изменения другого пользователя функцией TableUpdate с назначением параметру Force значения .T. (это делать не рекомендуется, кроме случаев, когда вы предоставляете пользователю подробную информацию о новых значениях полей).

Свойство представления по имени WhereType отвечает за проверку конфликтов при многопользовательской работе. У вас есть выбор из четырех вариантов. Варианты, при которых сравнение выполняется по ключевым и модифицированным полям (Key and Modified Fields) или ключевым и редактируемым полям (Key and Updatable Fields) выглядят довольно солидно, но мне кажется, что в большинстве случаев эти варианты слишком опасны в работе. Мне не хотелось бы, чтобы пользователи 1 и 2 изменили два различных поля одной записи не зная об изменениях друг друга.

Это оставляет только два варианта где анализу подвергаются только ключевые поля (Key Fields ONLY) или ключевые поля и метки времени (Key and Timestamp). Последний вариант используется только если ваша база данных поддерживает тип данных TIMESTAMP или аналогичный. Поле такого типа автоматически модифицируется при каждой модификации записи. Клиентское приложение не должно модифицировать это поле, клиент просто использует его для проверки конфликтов.

Так как SQL Server поддерживает тип TIMESTAMP. Более того, при использовании SQL Server Upsizing Wizard вы можете заказать, чтобы в каждую таблицу, которая переносится на сервер, добавлялось поле типа TIMESTAMP. В отличие от типа данных с аналогичным именем, TIMESTAMP у SQL Server не имеет ничего общего с датами и временем, это просто бинарная строка, автоматически поддерживаемая сервером. Прелесть этого типа данных в том, что сервер поддерживает содержимое поля автоматически и с большей точностью, чем поле, в котором хранится реальная дата и время (вам никогда не придется беспокоиться о двух пользователях, внесших изменения одновременно).

 

Проблема!

Следует учитывать некоторые особенности поведения полей типа TIMESTAMP SQL Server.

Если вы не будете перезапрашивать представление после каждого обновления, поле TIMESTAMP в локальном курсоре не будет обновляться новыми данными с сервера. В результате, последующие попытки записи той же самой записи после новой модификации не пройдут, так как сравнение меток укажет на их различие.

 

Хитрость!

Как обойти ограничения Upsizing Wizard. Этот инструмент имеет столько недостатков, что, на мой взгляд, практически бесполезен. Большинство из тех, кого я спрашивал, вынуждены писать собственные инструменты для переноса таблицы. К счастью, версия 6.0 включает исходные тексты для всех Мастеров и Построителей. Если вы хотите что-то поправить, то откройте архив \Tools\Xsource\Xsource.zip. 

 

Итак, комбинация Key and Timestamp выглядит весьма привлекательной для работы с SQL Server, а как быть с иными серверами баз данных? И как вы сможете создавать локальные прототипы, которые работают с таблицами маз без изменения кода? Советую добавить собственное поле, где будет храниться метка и обновлять его из триггера либо на клиенте, либо на сервере. Затем вы можете включить поле в состав ключа (см. Шаг 7) и использовать тип проверки Key Fields Only. Список ваших ключей будет включать cust_pkey и timestamp. (Обратите внимание, что поля, указываемые в свойствах KeyField или KeyFieldList представления вовсе не обязательно должны быть первичными или кандидат ключами).

Можно использовать поле с меткой даты-времени, только имейте ввиду, что тип DATETIME у Visual FoxPro имеет точность до одной секунды, достаточно времени, чтобы два пользователя внесли изменения. Если вы чувствуете, что это может создать вам проблемы, используйте иную технику для генерации уникального числа или строки. Я использовал этот обходной маневр до тех пор пока не перевел базу данных на сервер и не добавил "нормальное" поле типа TIMESTAMP. (Так как у вас нет контроля над генерацией имен полей, назовите поле Visual FoxPro как-то особенно, чтобы не возникало конфликтов при переносе базы данных на сервер). После переноса просто поменяйте параметр WhereType на Key and Timestamp и дело сделано.

Шаг 9: Жизнь без дат.

Для меня было большим удивлением обнаружить, что SQL Server, впрочем как и большинство серверов баз данных, не имеет данных типа DATE. Для хранения дат используется тип DATETIME. Если вы конвертируете приложение, которое использует поля типа DATE, масштаб возникающих проблем может быть достаточно велик. По большому счету, все зависит от того, как ваше приложение обрабатывает даты и насколько вы готовы видеть 12:00:00 AM, прицепленные к концу ваших дат. Ниже вы найдете описание проблем, с которыми я столкнулся и возможные обходные пути.

Объекты, привязанные к полям типа DATE

В тех случаях, когда отображение времени совершенно неприемлемо, вам придется идти на некоторые ухищрения, чтобы избавиться от "временной" части даты. Есть несколько вариантов. Можно использовать DBSetProp для модификации типа поля представления с DATETIME на DATE. (Как я говорил в части 1, использование локальных копий контейнера баз данных с описаниями представлений исключает конфликты при многопользовательской работе).

 

Create SQL View vOrder Remote Connection Remote1 As Select * from orders
DBSetProp(“vOrders.orderdate”, “Field”, “DataType”, “D(8)”)
Use vOrders

Это не только отрежет время, но и создаст стандартное поле типа DATE в вашем представлении, а ваш код будет нормально работать. Что особенно приятно, когда вы передадите на сервер обновленные данные, они получат стандартную приставку 12:00:00:000 AM и ошибки не будет.
Если ваши объекты на форме привязаны не к представлениям, а к курсорам, открытым только на чтение и получаемым посредством сквозных запросов, вы можете использовать конвертирование на серверной стороне. Вот команда, которая позволяет получить только дату:

 

SELECT orderid, ;
customerid, ;
CONVERT(char(10), orderdate, 101) as orderdate ;
from orders

Обратите внимание, что 101 относится к необязательному аргументу стиля форматирования функции CONVERT. Параметр 1 указывает на использование американского формата даты - mm/dd/yy. Если вы хотите получить четырехзначное отображение года, добавьте 100. 

Для того чтобы функция CONVERT работала с локальными данными, создайте хранимую процедуру с именем Convert. Поместите ее в ту же самую базу данных, где хранятся описания представлений. Пусть ваша процедура принимает те же самые параметры, что и функция SQL Server и если вы передаете ей тип DATETIME, возвращает SubStr(TToC(pDateTimeField),1,10). Для обработки данных SQL Server типа CHAR(10) вам нужно написать одноименную хранимую процедуру.

Поля типа DATETIME не могут быть “пустыми”

Если неприятности с потерей типа DATE при переходе к SQL Server для вас еще не катастрофа, то имейте ввиду, что вам придется найти решение проблемы, заключающейся в том, что у большинства серверов баз данных поле типа DATETIME не может быть пустым. (Так много продуктов, которые слабее Visual FoxPro, не так ли?) Эти поля должны быть заполнены нормальной информацией или хранить значение NULL.

Если вы позволяете вашим полям хранить значение NULL, то сможете быстро решить проблемы, связанные с работой пользовательского интерфейса, но можете получить новые сложности. Необдуманное использование значений NULL может привести к неожиданным результатам. Так, сравнение двух дат, одна из которых имеет значение NULL, возвращает NULL, Empty(NullDate) возвращает .F. и т. д.) Если же вы готовы обрабатывать ситуацию с NULL, они могут оказаться довольно полезными при выводе данных пользователям. Как известно, Visual FoxPro поддерживает настройку SET NullDisplay TO и свойство NullDisplay для таких объектов как TextBox.

Если вы не хотите использовать NULL, Visual FoxPro и SQL Server не станут особенно ругаться. Передача пустой даты на SQL Server приводит к тому, что сервер использует значение умолчания 01/01/1900 12:00:000 AM. Затем, после исполнения запроса, вы увидите 01/01/1900 в локальном курсоре, при условии, что вы использовали один из подходов, описанных выше для урезания лишней информации. Я не думаю, что это так уж плохо, но при установке SET CENTURY OFF, вы получите 01/01/00, что будет воспринято как 1 января 2000 года. (Как будто недостаточно проблемы 2000 года, при переходе на SQL Server вы создаете проблему 1900 года!)

Для решения этой проблемы я предлагаю три варианта решения. Первый - добавление значения умолчания для всех полей типа DATETIME. Можно использовать нечто заведомо бессмысленное, например 01/01/9999. (Обратите внимание, что SQL Server поддерживает диапазон дат от 1 января 1753 до 31 декабря 9999). Вам все равно придется использовать SET CENTURY ON, для того чтобы отличить ее от 1999. Второй - использовать значение умолчания, которое соответствует вашим бизнес правилам. Скажем, все даты отправки получают значение Дата оформления заказа + 8 недель и являются обязательными для ввода

Третий вариант требует некоторой ловкости рук. Согласитесь, что было бы здорово очистить все даты в локальном курсоре равные {01/01/1900}, не посылать модификации на сервер и не оставлять незавершенных модификаций. Это можно сделать, и делается это так:

 

Procedure OpenView
LParameters pcView
* Получаем данные с сервера
Use (pcView) in 0
* Отключаем передачу модификаций на сервер
CursorSetProp(‘SendUpdates’, .F., pcView)
* Удаляем даты 01/01/1900 из курсора
RemoveDefaultDates(pcView)
* Удаляем незавершенные модификации/очищаем буфер изменений
TableUpdate(.T., .T., pcView)
* Restore update capability to view
CursorSetProp(‘SendUpdates’, .T., pcView)

 

Для сокращения размера статьи я не стану приводить код функции RemoveDefaultDates. Все что она делает, это проходит по полям типа DATE и заменяет значение {01/01/1900} на {}. Теперь все формы ввода данных будут выглядеть и работать точно также как это было до перевода базы данных на сервер.

Ну, хорошо, все это работает с курсорами, полученными от представлений, а как быть с результатами исполнения сквозных запросов? Точно также. Вам просто нужно запустить функцию RemoveDefaultDates для каждого курсора, который используется для представления данных пользователю. (Я надеюсь. что вы построили класс, описывающий вашу систему.) 

 

Хитрость!

Различия между удаленными и локальными курсорами, полученными при исполнении сквозных запросов

Курсоры, полученные исполнением сквозного запроса, открыты только на чтение, если результат получен от файл-сервера и могут быть модифицированы, если источник данных - нормальная серверная СУБД. Если вы строите локальный прототип, имейте ввиду, что сначала курсор нужно открыть на чтение/запись

Вычисления с датами

Следует тщательно проверить все вычисления, которые вы делаете над датами. Из-за того, что изначально ваша система работала только с датами, а после перевода на сервер вы получили еще и время (в том числе и в локальном курсоре), расчеты могут давать иные результаты. Например:

?Date() + 20 && 03/08/99 + 20 = 03/28/99
?Datetime() + 20 && 03/08/99 10:35:15 PM + 20 = ;
&& 03/08/99 10:35:35 PM!
Select orderdate, ;
orderdate + 14 as ExpectedDate ;
from Orders 

&& Если для SQL Server: ExpectedDate получает значение на 14 дней после orderdate 
&& Если для VFP: ExpectedDate получает значенине на 14 секунд после orderdate!

 

Вы видите, что добавление числа к значению даты может дать разные результаты в зависимости от источника. Трудно сказать, будут ли добавлены дни или секунды. Проблему можно обойти, если выполнить приращение даты с использованием функции. Это позволяет вам организовать необходимый интерфейс для определения типа источника и выполнить расчеты в соответствии с этим.

Шаг 10: Не пользуйтесь Empty(), пользуйтесь Null.

Если вы похожи на меня, то наверняка часто использовали возможности, предоставляемые функцией Empty(). Очень удобно, что Empty(eExpression) работает для любого типа данных, кроме объектов. Вы можете перехватывать значения 0, пустую строку, логическое False даже не проверяя типа данных. Это расслабляет.

После перевода системы на сервер, вы можете получить непредвиденные результаты от функций и запросов. Они могут даже возвращать иные типы данных. При оценке полученных значений вы можете столкнуться с крупными проблемами. Например:

 

?Empty({}) && true
?Empty({01/01/1900}) && false 
?Empty(Null) && false

Мы уже обсуждали проблему даты, назначенной по умолчанию - {01/01/1900}. И вы знаете, что может произойти. Очистка полей, кажется, решает проблему, а как быть со значениями NULL? Большинство из вас знают, что NULL не значит "пустое значение", но знаете ли вы, что даже если в таблицах на сервере нет полей, принимающих NULL как значение, вы все равно сможете получить в клиентском курсоре эту величину? Попробуйте сделать так:

 

Function GetMaxOrderQty
lcSQL = "SELECT MAX(Quantity) AS nMaxQty" +;
" FROM [Order Details] " +;
" WHERE ProductID = 99 " 
SQLExec(lcSQL, “cBigOrder”)
If Reccount(“cBigOrder”) > 0
    lnRetval = 0
Else
    lnRetval = cBigOrder.nMaxQty
Endif
Return lnRetval

Каков тип возвращаемого значения? Может быть разным. Если в таблице есть записи, у которых ProductdID = 99, результат будет численным. Если таких записей нет, результат будет NULL, несмотря на факт, что таблица Quantity не принимает NULL.

Причина в том, что при сквозных запросах с использованием агрегатных функций, даже если результатом исполнения является NULL, вы все равно получаете на рабочую станцию курсор, имеющий хотя бы одну запись. Исключением является ситуация, когда ваша команда включает предложение Group By на не агрегированных полях. В нашем случае курсор имеет единственную запись, значение которой равно NULL. Если вы не будете вести себя аккуратно, могут возникнуть проблемы.
В приведенном выше примере величина lnRetval имеет значение NULL, так как в таблице нет изделий с номером 99. Исключить появление NULL можно несколькими способами. Один из них - поиск всех агрегатный функций в вашем коде и включение проверок функцией ISNULL(). Можно также написать оболочку для функции Empty, назовем ее IsEmpty, которая перехватывает значения NULL и даты 01/01/1900. Такая оболочка может пригодиться для проверки данных разного типа без боязни получить непредвиденные результаты.
И, наконец, добавление функции COUNT (COUNT (*) AS CNT) во все запросы, подобные приведенному выше позволяет организовать стандартную проверку возвращаемой величины. Поле Cnt всегда будет численным и можно проверять Cnt > 0 вместо Reccount() > 0 не боясь встретиться с NULL.

От редакции:

Можно также использовать стандартную замену NULL на некоторую строку или число посредством SET NullDisplay TO или применить встроенную функцию SQL Server ISNULL для превращения NULL в нечто иное.

Например

lcSQL = "SELECT ISNULL(MAX(Quantity), 0) 

    AS nMaxQty FROM [Order Details] WHERE ProductID = 99" 
? SQLExec(handle, lcSQL, "cBigOrder")

В этом случае вы получаете 0

Шаг 11: преимущества свободных объектов

Мы рассмотрели целый ряд ограничений, с которыми приходится сталкиваться при работе с полями разного типа (тип зависит от источника). Мне приходилось сталкиваться со всеми этими проблемами. В результате, написание единого кода, работающего с различными источниками данных, представляет непростую задачу. Очевидно, не все приложения нуждаются в подобной гибкости, но для тех, кому это действительно нужно, использование свободных объектов может решить проблему.
Несвязанные объекты на форме не типизированы. Вы можете взять набор записей, полученных от сервера и заполнить объект их содержимым. Это позволяет организовать необходимую обработку перед представлением данных пользователю.
Высокая гибкость, но много кодирования. Только представьте, сколько кода нужно написать для организации чтения данных, прохода по объектам и заполнения формы, затем придется снова пройти по объектам для создания SQL команды, которая выполнит модификации на сервере. Не говоря уже о том, что при использовании объектов, привязанных к данным, вы получаете бесплатно проверку типов на уровне поля. Большинство разработчиков настолько привыкли к использованию объектов, привязанных к данным, что скорее будут землю есть, чем перейдут к использованию свободных объектов. Должен быть еще какой-то способ.
Если мне пришлось бы прожить жизнь заново, я изменил бы только одну вещь: я создал бы еще один слой абстракции данных. (Я также купил бы акции Microsoft на десять лет раньше, но кто знал?) Дополнительный слой был бы реализован в форме курсора, открытого на чтение и запись с той же самой структурой и содержимым, что и курсор, полученный при использовании представления.
Затем объекты можно привязать к источнику при сохранении возможности манипулировать курсором, перед тем как представить информацию пользователю. Процесс выглядел бы примерно так:

Procedure Load
* Для этого теста открываем форму с данными
Use vOrders In 0
* Получаем копию результирующего набора
Select * from vOrders Into Cursor cOrders1 NoFilter
* Делаем копию cOrders1, открытую только на чтение
Use (DBF()) In 0 Again Alias cOrders 
* Закрываем временных read-only курсор
Use In cOrders1

* Теперь, когда сработают методы Init
* объектов формы, привязанных к cOrders.
* Область с именем cOrders будет основной для работы формы
EndProc


Теперь можно работать с формой как обычно. При сохранении данных передайте данные отредактированного курсора обратно в представление и выполните TABLEUPDATE. Все что мы говорили выше про использование представлений, по-прежнему имеет место, мы просто добавили один дополнительный слой. При работе с небольшими результатами, это нормальное требование для систем клиент-сервер и скоростью обработки, которую обеспечивает Visual FoxPro, использование этого слоя не даст какого-то замедления работы.
Возможность манипулировать данными перед представлением их пользователю дает вам комбинацию преимуществ от использования свободных и привязанных объектов. Вы получаете средства внесения дополнительной функциональности, такие как поддержка различных валют, множества языков, в общем, всего того, что позволяет получить из "сырых" данных то, что ожидает пользователь.

Шаг 12: Создание формы с доступом ко многим источникам данных

В программе, сопровождающей журнал, вы сможете найти форму, которая обращается к различным источникам данных. Для простоты понимания я разместил весь код в рамках самой формы, но вы всегда можете расширить функциональность, после того как разберетесь с ее организацией. Кроме того, для простоты я создал только дно поле для организации фильтров и не включил функциональность Query by Form.
Перед тем как запустить форму раскройте архив на дискете, сопровождающей журнал, или возьмите это архив с нашего Веб узла и раскройте его в любой каталог. Вы найдете форму Orders, набор картинок, необходимых для ее работы и головную программу main.prg. В каталоге AppData вы найдете копию базы данных Northwind Traders в формате Visual FoxPro. (Для создания этой базы данных я использовал SQL Server Data Transformation Wizard.) Каталог SSViews хранит удаленное представление vOrders, помещенное в контейнер по имени AppViews. Также каталог VFPViews хранит описание локального представления, vOrders в контейнере AppViews. (За дополнительной информацией об организации каталогов обратитесь к Шагу 5.)
Запустите Visual FoxPro версии 6.0 и установите каталог умолчания туда, куда вы раскрыли архив с примером. Запустите SQL Server 7.0. Через аплет Панели управления ODBC User DSN, который указывает на базу данных Northwind Traders, и назовите его NORTHWIND_SS7.
Для того чтобы запустить форму с обращением к базе данных SQL Server выполните такую команду: MAIN(“NORTHWIND_SS7”). Для использования данных в формате VFP выполните MAIN(“NORTHWIND_VFP”). Как видно на Рис. 1 заголовок формы отражает то, с каким источником она работает. Щелкните Find, введите имя заказчика “VINET”, и щелкните Retrieve. С сервера будут получены пять заказов.

 

Рис. 1. Форма Customer Sales Orders способна работать с данными различных источников


Не рассчитывайте найти какой-то магический код. Ничего такого в форме нет. То, что вы обнаружите, практически полностью соответствует коду для работы буферизированными таблицами. Единственное отличие - добавление предложения NoData в команде USE метода Load, настройка локальной переменной cCustomer, которая служит в качестве параметра для представления и последующий вызов функции Requery для получения данных по соответствующему заказчику. Вот только две процедуры, которые представляют хоть какой-то интерес:

 

PROCEDURE cmdfind.Click
PRIVATE cCustomerID
cCustomerID = “”
IF Thisform.PendingChanges()
    WITH Thisform
        .Lockscreen = .T.
        IF Not Thisform.lFindMode
            thisform.lFindMode = .T.
* Переходим в другой режим
            this.Caption = "\<Retrieve"
            .SetAll("Enabled", .F., "textbox")
            .SetAll("Enabled", .F., "_commandbutton")
            .txtCustomerID.Enabled = .T.
            .txtCustomerID.Setfocus()
        ELSE && В режиме поиска
            .SetAll("Enabled", .T., "textbox")
            .SetAll("Enabled", .T., "_commandbutton")
            this.Caption = "\<Find"
            .txtCustomerID.Enabled = .F.
            cCustomerID = .txtCustomerID.Value
            Requery()
            thisform.lFindMode = .F.

        ENDIF
       .cmdfind.Enabled = .T.
       .txtOrderID.Enabled = .F.
       .Refresh()
       .Lockscreen = .F.
    ENDWITH
ENDIF
ENDPROC

Выражение признательности

При разработке методологии миграции с Visual FoxPro на SQL Server я пользовался помощью многих людей. Хочу особо поблагодарить Brent Vollrath и Terry Weiss из Micro Endeavors; Richard Berry, Phu Luong и остальную команду из Visual Garpac Development Team; Roger Nettler и Stan из Programming Solutions; и Bill Martiner – независимого консультанта и специалиста по SQL Server.

Заключение

Надеюсь, что не нагнал на вас страху, и вы все же начнете миграцию на SQL Server. Я знаю, что это не просто, но тут скорее кропотливая, нежели сложная работа. Надеюсь, что моя статья сэкономит вам немало времени, так как многие проблемы вы будете знать заранее.
Что же касается единого кода для обращения к таблицам Visual FoxPro также как и к серверам баз данных, я не рекомендовал бы эту технику в качестве долговременного подхода. Слишком много кода проверок и дублирующихся инструментов, которые придется создать в такой ситуации. В результате, вы можете получить систему, которая плохо работает для всех серверов. (Хотя на мощном железе это можно поправить). Я бы использовал только локальные представления, если система планируется к переносу на SQL Server или Oracle. После создания прототипа постепенно мигрируйте код для поддержки только удаленного сервера.
Способность SQL Server 7.0 работать на Windows 95/98 и NT, равно как масса новых возможностей делают процесс миграции мене болезненным. Он вполне может служить единственным источником данных, тогда как Visual FoxPro возьмет на себя работу по преобразованию данных в информацию. Работая вместе, эти два инструмента представляют великолепную пару.

Джим Фалино - счастливый разработчик на инструментах семейства Fox с 1991 года, когда он начал использовать FoxBASE+. Джим имеет сертификацию Microsoft Certified Professional по Visual FoxPro и вице-президент Профессиональной ассоциации разработчиков баз данных в регионе New York. В течение последних трех лет он был менеджером проекта по разработке очень крупного приложения, использующего в качестве клиентской части Visual FoxPro. Его адрес - jim@garpac.com. 
 


Возврат к списку статей

Возврат на главную страницу

© Edel Ltd. Все права защищены. 1999 г.