EasySQL4Fox
Средства быстрой адаптации FoxPro приложения для работы с SQL Server
DBC+DBF или SQL Server? "За" и "против". Критерии выбора.
С тех пор, как Visual Foxpro приобрел средства для работы с другими базами данных используя техенологию Клиент-Сервер для многих разработчиков и заказчиков стала заманчивой перспектива разместить данные в базе SQL-Server , Oracle, MySQL, .. Так как Microsoft естественно ненавязчиво продвигает к использованию свой SQL Server путем создания некоторых средств исключительно для работы из VFP именно с этим продуктом, то не будем (пока еще) пытаться свернуть с рельс и уделим внимание их стремлению.
Долгие годы в народе звучат рассуждения и сетования на политику Microsoft по которой в жертву стратегическим планами этой компании приносятся целые (не всегда плохие) продукты. Очевиден тот факт, что FoxPro фактически стал "костью в глотке" уже не только победе SQL Server в масштабе всей планеты, но и (что гораздо ужаснее) и NET-технологиям. Как бы не потела Microsoft над ADO.NET , но при достаточно поверхностном сравнении очевидно, что чтобы создать в VS.NET функционал подобный VFP им понадобится наверное лет сто.
Тем не менее постараемся непредвзято обойтись с обсуждением разных продуктов и попытаться выявть наиболие оптимальные их сочетания в разных условиях.
Итак. Нужен ли Фоксу SQL Server? Если да, то в каких ситуациях?
1. Когда бы не советовал переходить на SQL: Размеры Ваших таблиц не превышают гигабайта. Заявленная производительность локальной сети 100Mb/s, реальная - >= 50.
В данном случае Вам предлагается оценить еще некоторые ресурсы и спланировать затраты. Во первых оцените типовой размер физической памяти на ваших компьютерах и постарайтесь проанализировать требования к памяти на типовых запросах ваших программ к данным. Достаточно зафиксировать факты полного использования физической памяти в процессе запросов. Для этого можно воспользоваться диспетчером задач Windows. Другим путем - если имеют место долговременного выполнения запросов - попробуйте устранить эти зависы путем временного добавления физической памяти на компьютор. Дело в том , что для FoxPro остается критическим наличие памяти для оптимизации. Разница в скорости выполнения запросов - несколько порядков. Другими словами - стоит в процессе запроса использовать всю память - отрубается оптимизация и вы получаете приложение - тормоз. Полезно также проанализировать варианты оптимизации запросов из программы используя средства Sys(3054) и Sys(3092). А смысл всего сказанного выше в том, что при доступе к данным на одном компьютере или в достаточно производительной сети VFP будет быстрее, чем SQL. Причина - более простые технологии распределения памяти, каширования и доступа к данным, а так же - отсутствие затрат на ODBC. Если камнем приткновения является производительность сети, то попробуйте ввести принудительную буферизацию с помощью локальных курсоров где это можно. Так, например, если у вас справочник клиентов содержит сотню тысяч наименований и вы ссылаетесь на него из GRID (или BROWSE) листая таблицу заголовков документов, то есть смысл предварительно создать локальный курсор с индексами на ключи и искать сначала значения в нем, а потом обращаться к источнику. Все результаты обращений к источнику сбрасывайте в локальный курсор. Посторайтесь везде использовать только SET RELATION , а не LOCATE или SEEK. Естественно я не говорю о соблюдении всех правил, необходимых для работы оптимизации RushMore. Имеется железная практика, когда в результате анализа жалоб на медленность FoxPro выясняется, что разработчик не знает и не соблюдает условий для работы оптимизации RushMore и в результате - порочит добрейшее имя этого продукта.
Наличие нескольких удаленных точек (например с доступом по телефонной линии) при основной работе в локальной сети так же не является необходимыи условием перехода на SQL. Лучше написать отдельные приложения для этих точек с использованием примитивного Automation-server на FoxPro, чем переводить все, что есть на SQL.
2. Когда вы не обойдетесь без SQL Server или Oracle
Некоторые из Ваших таблиц превышают (или могут в перспективе превысить) размер 2Gb. Уверен. Ограничение на размер файлов в VFP - умышленно не ликвидируется. Хотя с другой стороны при приближении к этим критическим размерам и при наличии приличного числа одновременных сессий FoxPro начинает несомненно уступать SQL Server по производительности в целом. Тут уже без более сложных серверных технологий не обойтись. Ограничение в 2Gb мешает FoxPro в другом случае, который будет упомянут ниже.
Если сеть всетаки тормозит или основная работа ведется с удаленных рабочих мест, то вам, как не крути, придется переходить на технологию Клиент-Сервер. В этом случае однозначно посоветую не тратить время зря и реализовывать эту технологию с размещением данных в SQL Server или Oracle (забыть про automation и DBF). Как показывает практика - только доступ к COM объектам (Automation серверам) будет уступать в скорости ODBC, не говоря уже о производительности SQL Server на больших объемах данных.
Если Вы создаете тиражируемые продукты, то, естественно, работа с SQL - Server - один из первых критериев, по которому его будут оценивать.
Есть показатель по которому SQL Server предпочтительнее DBF - надежность. Индексы практически не рушатся.
3. Когда FoxPro и SQL Server нужен ТРЕТИЙ - ADO.NET
Весьма редкая ситуация, но имела место.
Во первых. Самые распространенные отзывы, которые приходилось слышать от клиентов, которые начинают работать с SQL сервером после FoxPro : "too stupid database" или "SQL Server - it is just SELECT, INSERT, UPDATE, DELETE" . Возникали эти возгласы, как правило, в процессе попыток написать хранимые процедуры на T-SQL или PL-SQL(Oracle). Проблема имеет место и очень актуальна! Написать более-менее сложную процедуру на T-SQL, которая заменяла бы программу на FoxPro, - затея абсолютно не оправданная. Полностью согласен: T-SQL и PL-SQL - издевательство над разработчиком. Заявляю это не без оснований. Практически реализовать сложный функционал - возможно, но работать это будет ужасно и отрицательных эмоций Вы поимеете очень много. Так что советую - не тратьте время зря. Не теряя времени создавайте COM (automation) сервер на VFP, размещайте его на сервере с SQL и пишите процедуры, которые будут выбирать исходные данные из SQL Server, обрабатывать их реализуя сложный функционал на основе отраборанных ранее кодов на FoxPro и отсылайте результат непосредственно клиенту. Запись результата во временные базы SQL при условии, что нет возможности отключить трансакции, опять убъет ваше приложение. Отработка сложных (да и любых) алгоритмов в FoxPro тем более оправдана, что даже внутри SQL на T-SQL для доступа и модификации данных вам придется выбирать их во внутренние курсоры. Уж лучше выбрать их в курсоры FoxPro. В способностях FoxPro как удаленного сервера в технологии "Клиент-Сервер" сомневаться не приходится. Видел кучу сайтов, которые реализованы именно на Фоксе. При этом Фокс замещает не только SQL Server при доступе к данным, но и целиком ASP, работая как генератор HTML страниц.
Вот тут то и вредит умышленно не ликвидируемое ограничение на размер файла в FoxPro. Реальный пример: Приложение реализует схему "центральная база" и "оторванные представления" по районам региона и занимается учетом расчета с населением за газ. ~400000 абонентов. Ежемесячно печатают грузовик квитанций. Сквозной учет. Т.е. нет обязательных ежемесячных режимов превращающих документы прошлых месяцев в свернутое сальдо. Продолжительность периода - любая. Размер баз за год вырастает под 70Gb. Одна из процедур работая при формировании отчетов по расчетам позволяет динамически выстроить в один список счета к оплате и документы оплаты в порядке хронологии, перекрыть их по абонентам, вывести сальдо и реализацию. Размер 2Gb будет превышен при попытке выбрать в такой курсор документы более чем за три месяца. Естественно принято решение (и в целях ускорения процедуры) создавать на прошлый месяц промежуточное сальдо, которое, если программа видит, принимается за входящее и более ранние документы не выбираются в курсор. Все прекрасно, пока не возникает бредовой идеи получить рассчеты целиком за год. Естественно ни кто не хочет это суммировать вручную и возникает делема:
Фактически работая как automation server рядом с SQL Server FoxPro замещает именно прямое назначение ADO.NET . Думаю что это решение будет более правильным.
Если выбираем SQL Server и не хотим переписывать приложение "с нуля". Две группы возникающих проблем.
Итак, вы всетаки решили связать будущее с SQL Server. Хорошим шагом было бы поискать наработанные решения, которые позволи ли бы реализовать нужный функционал в FoxPro приложении. Смотрим и видим: Большинство из предлагаемых средств разработки ориентированы на то, что вы намерены переписать приложение снуля. Mere Mortals Framework, CodeMine Framework, ... - хорошие штуки, но изначально вам будут предложены базовые средства, которые Вы должны взять за основу своего приложения. А что же делать, если эта основа уже давно взята? Можно ли путем "хирургического вмешательства" адаптировать FoxPro приложение для работы с данными в базах SQL Server ?
Практика показывает, что можно. При этом изначально нужно понимать следующие вещи:
Практически никогда речь не может идти о разовом переходе с DBF на SQL при котором приложение теряет функции работы с данными в DBF и приобретает способность работы с данными в базах SQL. Если даже в перспективе вы планируете полностью отказаться от DBF, то процесс переноса данных будет затянут и переход обычно осуществляется с несколько попыток и в несколько этапов. Конечно, в процессе перехода можно параллельно развивать два приложения: исходное, которое пока еще работает с DBF, и его копию, на которой уже началась операция по адаптации к работе с SQL. Но оправдано ли параллельное развитие двух приложений? Считаю, что более целесообразно попробовать наделить одно и то же приложение новыми функциями не влияя на существующие. Большинство сред разработки (frameworks) фактически предлагают именно такое решение. Среда разработки, в которой выполнено приводимое выше в пример приложение по рассчетам с населением за газ (как и базовая система учета к которой оно относится) фактически вводит уровень абстрагирования предметной области приложения от типов источников данных (DBF, SQL Server, Oracle, ...) и от механизма доступа к ним (прямой доступ к DBF, ODBC, Automation, ADO, ...). Иными словами приложение работает с набором объектов и функций практически не зная об типах данных и транспортов. Одно и то же приложение работает с центральной базой SQL Server в центральном офисе газовой компании и с DBF (в качестве оторванного представления) в районах, которые (DBF) содержат данные только текущего района (участка). Описываемый здесь продукт фактически состоит из некоторых базовых честей этой среды разработки и ставит целью предоставить другим разработчикам весомую помощь но не в случае переписывания всего приложение в новой среде разработки, а адаптации существующего приложения к работе с данными как в DBF, так и в базе SQL Server.
Начнем с того, что вцелом проблемы, возникающие в процессе адаптации FoxPro приложения к работе с SQL Server, можно разделить на две группы:
Преобразование синтаксиса SQL запросов.
Вопервых. Синтаксис запросов в FoxPro несравнимо более свободный, чем допускает стандарт SQL . Так большинство кодов написанных на версияд VFP до седьмой будут содержать сокращенные ключевые поля, например, SELE ... FROM ... WHER ... . Порядок фраз в FoxPro также может быть любой, но он является строго определенным для SQL. Есть еще куча различий, которые будут конкретно описаны ниже. Вовторых - использование функций в запросах - обычное дело. Хотя для множества функций и можно найти эквивалент в T-SQL, но имена и синтаксис будет различен. Втретьих - не все манипуляции с переменными FoxPro в запросах можно реализовать путем передачи их значений в ODBC префиксом "?". Возникают проблемы, когда выбираются данные во временную таблицу в базе TEMPDB.
Прежде чем начать описание предлагаемых средств для преобразования синтаксиса определим, что практически разработчики используют два метода адаптации запросов: конвертация синтаксиса на этапе выполнения программы и на этапе разработки. В обоих случаях используются функции библиотеки EFoxSQL.FLL
Библиотека EFoxSQL.FLL и ее функции
Основные функции библиотеки для преобразования синтаксиса FoxPro к T-SQL:
E_SQL_SELECT_CONVERT() - непосредственно преобразование синтаксиса;
E_SQL_FIND_VARIABLES() - преобразование ссылок на переменные и выражения FoxPro в их значенияж
E_SQL_REMOVE_DIRECTIVES() - убирает префиксы-дериктивы из запроса для выполнения его в FoxPro
E_SQL_CREATE_STRU_CONVERT() - преобразует названия типов и добавляет ключевые слова в команду CREATE
Библиотека подключается стандартным способом SEL LIBRARY TO ... EFOXSQL.FLL ADDITIVE
Функция E_SQL_SELECT_CONVERT()
Синтаксис:
Запрос с синтаксисом T-SQL = E_SQL_SELECT_CONVERT(Запрос с синтаксисом FoxPro)
Функция выполняет следующие действия по преобразованию текста запроса Select SQL:
1) Дополняет ключевые слова в фразах запроса до их полной длины если они усечены (вплоть до 4-х знаков)
Изначально FoxPro позволял использовать усеченные минимум до четырех знаков любые ключевые слова и названия функций. В версиях VFP начиная с 7-й по умолчанию включена проверка синтаксиса, которая старается дополнить фразы до полной длины в процессе их написания, но возможность писать сокращенные фразы все же остается. А многие запросы были написаны еще на более ранних версиях. Так что функция E_SQL_SELECT_CONVERT постарается дополнить ключивые слова и если, например, ваше выражение напоминало
SELE DIST ... FROM ... GROU BY ...
то в результате функции оно будет выглядеть
SELECT DISTINCT ... FROM ... GROUP BY ...
2) Заключает в квадратные скобки имена полей, которые совпадаютс с зарезервированными словами в T-SQL
Существует перечень зарезервированных слов в T-SQL, который во многом может совпадать с ключевыми словами в FoxPro. Но, если FoxPro предпочитает сам отличать имя поля от ключевого слова, то T-SQL требует принудительно указать различие между ними путем заключения в квадратные скобки имен полей совпадающих с ключевыми словами. Так если в запросе, передаваемом в SQLExec() , вы оставите ссылки на имена полей USER, TABLE, ... не заключенными в [ ], то получите ошибку.
Функция E_SQL_SELECT_CONVERT постарается отыскать случаи ссылки на такие поля и заключит из в квадратные скобки, например
SELECT USER,SUM,ORDER,NAME ...
в результате функции будет выглядеть:
SELECT [USER],SUM,ORDER,NAME ...
3) Перестраивает в фиксированном для SQL порядке фразы SELECT, INTO, FROM, WHERE, GROUP BY, HAVING and ORDER BY , которые могли быть указаны в FoxPro в произвольном порядке
Думаю , что не редкость увидеть в коде FoxPro порядок фраз подобно
Select a.F1,b.F2, ... From table1 a Join table2 b On a.FF=b.FF Order By 1 Group By a.F1 Select a.F1,b.F2, ... Order By 1 Group By a.F1 From table1 a Join table2 b On a.FF=b.FF Select a.F1,b.F2, ... Order By 1 From table1 a Join table2 b On a.FF=b.FF Group By a.F1
Да и вообще, задумывался ли какой разработчик о том, что этот порядок нужно будет гдето соблюдать? Функция E_SQL_SELECT_CONVERT преобразует это к виду:
SELECT A.F1,B.F2, ... FROM TABLE1 JOIN TABLE2 ON A.FF=B.FF GROUP BY A.F1 ORDER BY 1
Фактически наличие только трех этих пунктов функционала сделало популярность описываемой библиотеке и позволило народу не создавать отдельный экземпляр кода кучи запросов для T-SQL, хотя это - десятая часть ее возможностей.
4) Опция FORCE во фразе FROM будет перенесена в секцию OPTION() запроса
Фраза FORCE сразу после фразы FROM в FoxPro позволяет указать оптимизатору, что нужно выполнить объединения JOIN в том порядке, в котором они указаны в запросе. В противном случае оптимизатор применит свои критерии при выборе очередности JOIN. В T-SQL данная директива указывается в конце запроса с синтаксисом OPTION(FORCE ORDER).
5) В FoxPro можно определять критерии группировки во фразе GROUP BY используя порядковые номера столбцов в результате запроса. T-SQL этого не поймет. Потому E_SQL_SELECT_CONVERT() заменит номера столбцов на их выражения.
Так, например, запрос:
Select F1,F2+F3 as F4,F5,Sum(F6) From table Group By 1,2,3
будет преобразован в
SELECT F1,F2+F3 AS F4,F5,SUM(F6) FROM table GROUP BY F1,F2+F3,F5
6) Если в запросе указана группировка GROUP BY, то в Transact-SQL фраза SELECT не может содержать столбцов не являющимися результатом агрегатных функций (SUM(),MIN(),MAX(),...) или не перечисленных во фразе GROUP BY.
Аналогичная проблема возникает в FoxPro начиная с 7.0 Но , как правило, она решается путем SET ENGINEBEHAVIOR 70
Если фраза GROUP BY присутствует в запросе, то функция E_SQL_SELECT_CONVERT() применит функцию MAX() для каждого столбца, который не указан во фразе GROUP BY и не является агрегатной функцией (SUM,MIN,MAX,...). Внимание! Вы должны поправить логические поля вручную потому, что MAX() функция не может применяться к данным типа bit. Если группировка не важна по этому полю, то поставьте вместо ссылки на поле - ссылку на константу типа CAST(0 as bit). Иначе Вам придется преобразовать его в числовое и включить во фразу GROUP BY.
7) Для T-SQL недопустимо ссылаться на столбцы в результате запроса из фразы GROUP BY по их алиасу (... as alias)
Все найденные во фразе GROUP BY ссылки на столбцы по алиасам функция заменит E_SQL_SELECT_CONVERT() на выражения столбцов. Например:
Select F1,F2+F3 as F4,F5,Sum(F6) From Table Group By F1,F4,F5
будет преобразовано в
SELECT F1,F2+F3 AS F4,F5,SUM(F6) FROM Table GROUP BY F1,F2+F3,F5
8) Для T-SQL недопустимо ссылаться на столбцы в результате запроса из фразы HAVING по их алиасу (... as alias)
Все найденные во фразе HAVING ссылки на столбцы по алиасам функция заменит E_SQL_SELECT_CONVERT() на выражения столбцов. Например:
Select F1,F2+F3 as F4,F5,Sum(F6) From Table Group By F1,F4,F5 Having F4>0
будет преобразовано в
SELECT F1,F2+F3 AS F4,F5,SUM(F6) FROM Table GROUP BY F1,F2+F3,F5 HAVING F2+F3>0
9) Если ключевые слова AND, OR и NULL постаринке ограничены точками (.AND. , .OR. , ...) , точки будут удалены
10) По умолчанию SQL Server использует READ COMMITED TRANSACTION ISOLATION LEVEL. Это значит, что все запросы будут сопровождаться блокировкой записей. Fox этого не делает.
Блокирование записей в процессе запросов приведет к ошибке DEAD LOCK во многих ситуациях при одновременной работе множества пользователей, когда ктото пытается модифицировать записи. FoxPro по умолчанию этого не делает. Чтобы отменить блокировки в процессе запросов, объявите в FoxPro перед вызовом функции E_SQL_SELECT_CONVERT() переменную
SETLOCSYS_SQL_Server_Select_NoLock = .T.
Если функция видит эту переменную и ее значение .T., то после каждой ссылки на таблицу во фразе FROM она добавит директиву WITH(NOLOCK) и данный запрос будет выполняться без блокировки записей в таблицах.
Не советую менять READ COMMITED TRANSACTION ISOLATION LEVEL в SQL-Server. Это очень вероятно может привести к потере данных!
11) В T-SQL нельзя ссылаться на логические значения (тип bit) в условиях без операций сравнения.
Если в FoxPro можно просто сослаться на логическое поле F1 в условии фильтрации (хотя в целях использования оптимизации этого делать нельзя)
SELECT * FROM tbl WHERE F1 and F2>3
то для T-SQL это будет непонятно и функция E_SQL_SELECT_CONVERT() постарается выловить все такие ситуации во фразах SELECT и WHERE и добавить операцию сравнения
SELECT * FROM TBL WHERE F1=1 AND F2>3
12) Будут заменены эквивалентами в T-SQL с преобразованием синтаксиса следуэщие функции FoxPro:
ALLTRIM() ; SUBSTR() ; IIF() ; AT() ; MINUTE() ; SEC() ; DTOC() ; STRTRAN() ;
Функция ALLTRIM() будет заменена две: LTRIM(RTRIM())
Функция SUBSTR() будет заменена на SUBSTRING()
Функция IIF() будет заменена конструкцией CASE , например:
SELECT *,IIF(F1>F2,F3,F4) AS F5 FROM tblбудет преобразовано в
SELECT *,CASE WHEN F1>F2 THEN F3 ELSE F4 END AS F5 FROM TBL
Функция AT() будет заменена на CHARINDEX()
Функция VAL() будет заменена на CONVERT(numeric(10),expr). Например:
Select F1,Val(F2) as F2 From tblбудет преобразовано в
SELECT F1,CONVERT(NUMERIC(10),F2) AS F2
Функция NVL() будет заменена на ISNULL()
Функция HOUR будет заменена на DATEPART(hour, var). Например:
Select F1,HOUR(F2) as F2 From tblбудет преобразовано в
SELECT F1,DATEPART(HOUR,F2) AS F2
Функция MINUTE() будет звменена на DATEPART(minute,expr)
Функция SEC() будет заменена на DATEPART(second,expr)
Функция DTOC() будет заменена на CONVERT(char(10),expr,102)
!!! Для замены стиля даты 102 на любой другой (см документацию по T-SQL) перед вызовом функции E_SQL_SELECT_CONVERT() в программе FoxPro объявите переменную ___EFOXSQL_Set_DToC_Style и присвойте ей код нужного стиля, например 103
Функция STRTRAN() будет заменена на REPLACE()
Но! Допустимы только три первых параметра!
Функции PADR() и PADL() будут заменены на dbo.PADR() и dbo.PADL()
То есть предполагается, что в базе данных, к
которой обращен запрос, объявлены функции (UDF
user-defined functions) PADL и PADR. Для проверки наличия и
объявления этих функций выполняйте в начале
программы на FoxPro код подобно:
Где:
Функция E_SQL_FIND_VARIABLES()
Синтаксис:
Запрос = E_SQL_FIND_VARIABLES(cЗапрос,lКакЗначения)
Как уже сказано выше данная функция предназначена для преобразования ссылок на переменные и выражения FoxPro в запросах SQL на этапе выполнения. Почти во всех случаях можно передавать значения переменных, свойств объектов и даже целых константных выражений в ODBC с помощью префикса "?", например:
... Where a.F1 = ?m.V1 or a.F1=?V2 && ссылки на переменные
... Where a.F1=?Object.Container.Property && ссылка на свойство объекта
... Where a.F1<=?(m.V1+1) and a.F2>=?(m.V1-1) && ссылки на константные выражения (в которых не участвуют поля таблиц и которые могут быть вычеслены перед выполнением запроса)
В большинстве случаев данный синтаксис позволит вам передавать значения для запроса в ODBC, и функция E_SQL_FIND_VARIABLES() вам не понадобится. Но возникают две ситуации, в которых это не будет работать:
1) Если в запросе данные выбираются во временную таблицу в базе TEMPDB, например
=SQLExec(m.Connect,[Select * Into #ttt From table a Where a.F1=?m.V1])
будет выполнено, но вы не найдете результат выполнения - таблицу #ttt. Думаю, что это происходит по причине временной смены сессии доступа к данным в процессе передачи ODBC переменных FoxPro. А так как все временные таблицы существуют только для сессии, в которой они были созданы, то они теряются при смене сессий.
2) Если доступ к данным осуществляется не через ODBC, а, например, строка запроса передается NET-приложению на сервере, работающему с сервером через ADO.NET
Функция E_SQL_FIND_VARIABLES() позволяет преобразовать ссылки на переменные в их значения. Для работы функции предлагается заменить перефикс "?" на "~". Если не передать второй параметр или передать .F., то функция просто заменит "~" на "?". Но когда передается .T. в качестве второго параметра, то все ссылки на переменные, свойства, выражения с префиксом "~" будут заменены на их значения. Например:
m.F1 = {^2004-01-24}
m.F2 = 12345.6789
SELECT * FROM tbl WHERE F1=~m.F1 AND F2=~m.F2 Into #temporary_table_nameбудет преобразовано вSELECT * FROM tbl WHERE F1='2004-01-24' AND F2=12345.6789 Into #temporary_table_name
При этом вычисляемые поля логического типа (bit) по умолчанию в T-SQL будут иметь результат numeric. Необходимо принудительно предприсать им тип bit. Например если запрос выглядит
SELECT nF1,lF2 ... и lF2 имеет тип BIT, то - нет проблем.Но если Вы напишите запрос
Select F1,~m.True as F2 ...
где m.True - логическая переменная FoxPro, то в результате столбец будет иметь тип N(1), так как SQL воспримет константу 1 как numeric type. Функция E_SQL_SELECT_CONVERT() постарается выявить такие случаи и применит функцию CAST T-SQL для приведения типов:
Select F1, cast (1 as bit) as F2 ...
Функция E_SQL_REMOVE_DIRECTIVES()
Если преобразование синтаксиса происходит на
этапе выполнения, то для варианта выполнения
запросов в FoxPro при доступе к данным в DBF данная
функция понадобится вам в следующем качестве:
m._Cmd = [Sele * From table
Where F1=?m.V1 and F2=?m.V2]
IF m._SQL && data access selection
Функция E_SQL_Remove_Directives() просто уберет префиксы "?" и "~" из выражения. Вы конечно можете сделать это сами, но не забудьте про возможные строковые константы содержащие данные символы.
Функция E_SQL_CREATE_STRU_CONVERT()
Если в программе приходится создавать таблицы с большой структурой данных, то поддерживать две копии команды с описанием этой структуры - перспектива не очень заманчивая. Предлагается:
Например:
Исходныей текст в коде FoxPro:
CREATE TABLE
t1 (IDENT C(10),CODE C(10),NAME C(100))
Создаем строковую переменную и добавляем ~
m._Cmd = [IDENT ~C(10),CODE ~C(10),NAME ~C(100)]
Далее в зависимости от тапа данных:
m._Cmd = [IDENT ~C(10),CODE
~C(10),NAME ~C(100)]
IF m._SQL
Функция E_SQL_Create_Stru_Convert() сделает следующее:
Заменит описатель типа с ~ на тип данных в SQL Server
Добавит описатель DEFAULT с пустым константным значением соответственно типу;
Добавит описатель NOT NULL
В результате (в предыдущем примере) получится строка:
IDENT CHAR(10) DEFAULT '' NOT NULL,CODE CHAR(10) DEFAULT '' NOT NULL,NAME CHAR(10) DEFAULT '' NOT NULL
Преобразование синтаксиса SQL запросов на этапе выполнения
Описанные выше функции выполнены на C не с целью скрыть исходный текст. Изначально предполагалось преобразовывать синтаксис на этапе выполнения, а для этого нужна СКОРОСТЬ. Фактически в наших приложениях преобразование синтаксиса выполняется в выражениях столбцов GRID без всяких видимых замедлений (механизм более сложный, чем просто выборка из базы. Используется принудительная буферизация. В случае работы с DBF - только SET RELATION а не LOCATE каждый раз. Описано подробно в других статьях).
Самым важным преимуществом метода преобразования синтаксиса является даже не экономия кода, а написание его в единственном экземпляре для работы с различными типами данных. Порой бывает трудно четко сформулировать сложный запрос в единственном экземпляре (не запутаться в полях и связях), а уж развивать его в двух экземплярах - плодить ошибки. Потому основными шагами по изменению кода программы для работы как с данными в DBF так и в базе SQL Server будут:
Присвоить текст запроса строковой переменной.
Вставить префикс "?" или "~" (если будете использовать функцию E_SQL_Find_Variables()) перед ссылками на переменные, свойства объектов и константные выражения FoxPro (выражения предварительно взять в скобки)
Выполнить вручную те преобразования, которые не сможет сделать функция E_SQL_Select_Convert() так, которые поддерживают синтаксис и для DBF и для SQL
Проанализировать глобальный признак, отпеделяющий тип данных
Если данные - в базе SQL Server то:
При необходимости преобразовать ссылки на переменные функцией E_SQL_Find_Variables()
Преобразовать синтаксис строковой переменной функцией E_SQL_Select_Convert()
Выполнить команду в строковой переменной функцией SQLExec()
Если ошибка - то обработать
Если данные - в DBF
Убрать префиксы "?", "~" функцией E_SQL_Remove_Directives()
Выполнить команду директивой &
Выглядеть это будет приблизительно
или
Естественно, функция E_SQL_Select_Convert() выполнит преобразование ни во всех возможных ситуациях. Извращенность кода порой допускает только правки его вручную. Но в выполненных ранее проектах она позволяла ипользовать более от 50% до 70% запросов без правки вручную. Если вы видите другие возможные преобразования, которые хотели бы, чтобы делала E_SQL_Select_Convert(), - пришлите предложения. По возможности они будут реализованы в следующих версиях.
Преобразование синтаксиса запросов на этапе разработки
Некоторые из разработчиков, использующих EasySQL4Fox, желают создавать два варианта текста запросов на этапе разработки. Есть мнение, что надежнее видеть уже преобразованный текст в программе, чем доверять это функции E_SQL_Select_Convert() на этапе выполнения. Так, фирма Pinter Consulting создала сласс DetaTier, методы которого вызываются дла доступа к данным в базе SQL Server. Так как в методы данного класса должны передаваться запросы с уже преобразованным для SQL синтаксисом, то для преобразования запросов в коде программы используется еще одно средство в виде программы E_SQL_Text_Convert.prg
Так как в данном случае скорость не нужна, то программа написана на FoxPro, хотя использует функции библиотеки EFoxSQL.FLL . Предлагается делать следующее:
Как и в примерах с преобразованием на этапе выполнения принимаем, что имеется глобальный признак, определяющий где находятся данные: в DBF или SQL ? В предыдущих примерах мы предполагали, что это переменная m._SQL или m._UsingSQL . В классе DataTier разработанном фирмой Pinter Consulting для этого предназначено поле AccessMethod
IF oDataTier.AccessMethod = [SQL]Когда вы определите свой глобальный признак и создадите свой обработчик ошибок SQL, то для удобства преобразования нужно открыть вашу копию программы E_SQL_Text_Convert.prg и изменить препроцессорные объявления в начале программы на свои:
#
Define DATA_IF "IF m._SQL" && Replace this definition with valid#
Define SQL_ERROR_ROUTINE "Do SQL_Error With m.Cmd" && Replace this definition with validТакже указываем в начале программы правильный путь к библиотеке EFoxSQL.FLL
Далее перед началом модификации текста программы назначте вызов E_SQL_Text_Convert клавише F3
ON KEY LABEL
F3 Do ....\E_SQL_Text_ConvertПосле этого идем по тексту программы и при нахождении очередного запроса, который выбирает данные из рабочих таблиц, а не курсоров,например (пример взят из жизни и не является образцом программирования на FoxPro):
- SELECT DISTINCT C.pref_no, D.ref_id, ;
- ALLTRIM(D.org) + IIF(Empty(D.org), "", " - ") + ALLTRIM(D.tle) + " " + ;
- ALLTRIM(D.giv) + " " + ALLTRIM(D.sur) AS Name, ;
- C.Guarantee, D.id ;
- FROM coappref C, Demo D;
- WHERE C.ID = pnID;
- AND C.mod_id = pnModID;
- AND C.Pref_id = D.Id;
- AND C.Pref_id > pnPId+1 ;
- ORDER BY 1;
- INTO CURSOR cursSpec1
делаем следующее:
Вручную вносим изменения, которые не может сделать функция E_SQL_Select_Convert(). Изменения должны поддерживать синтаксис и FoxPro и SQL. В нашем примере только ликвидируем функцию Empty()
Далее проставляем знак '?' перед ссылками на переменные , свойства объектов, константные выражения FoxPro. При этом выражения берем в скобки.
помечаем текст запроса
и жмем F3
Получаем сообщение:

Если откажемся, то преобразованный текст останется в _CLIPBOARD . А если говорим "Да", то программа изменяет исходный код следующим образом:
Остается:
Убрать INTO [CURSOR] CURSSPEC1
Раскомментировать и дописать SQLExec и обработчик ошибок
Некоторые преобразования должны быть выполнены вручную
Естественно, функция E_SQL_Select_Convert() выполнит преобразование ни во всех возможных ситуациях. Извращенность кода порой допускает только правки его вручную. Но в выполненных ранее проектах она позволяла ипользовать более от 50% до 70% запросов без правки вручную. Если вы видите другие возможные преобразования, которые хотели бы, чтобы делала E_SQL_Select_Convert(), - пришлите предложения. По возможности они будут реализованы в следующих версиях.
Но есть ряд преобразований синтаксиса, который вы должны делать вручную. Вот некоторые из них:
- Замените синтаксис cSubstr $ cString на At(cSubstr,cString)>0 . А E_SQL_Select_Convert() заменит его на CHARINDEX.
- Замените ссылки на поля MEMO и GENERAL во фразе SELECT запроса на '' если присутствует группировка GROUP BY.
- Замените ссылки на логические значения типа VAR=VAL as FIELDNAME во фразе SELECT запроса на IIF(VAR=VAL,?True,?False) as FIELDNAME. Перед этим объявите m.True=.T. и m.False=.F.
- Если в результате запроса планируются логические (тип bit) поля и группировка GROUP BY, то включите эти поля в GROUP BY или используйте вместо них константные выражения типа ~m.True as ... или ~m.False as ... (m.True=.T. and m.False=.F.)) потому что функция MAX(), как и любая другая агрегатная, не может быть применена к значению типа Bit
- В хранимых процедурах. Если вы создаете временные таблицы выборкой с фразой INTO и планируете сортировать по столбцам типа char фразой ORDER BY, то конвертируйте эти столбцы в тип binary в ORDER BY, например: Select CHARFIELD1,CHARFIELD2 Into #temptable Order By Cast(CHARFIELD1 as binary(10)),Cast(CHARFIELD2 as binary(15)) Потому что SQL по умолчанию использует сортировку не по ASCII.
Замещение прямого доступа к данным технологией "Клиент-Сервер"
Мы не случайно описали сначала проблему преобразования синтаксиса, а потом переходим к технологии "Клиент-Сервер". Хотя этот этап можно считать более важным и более трудоемким, ноон использует средства преобразования синтаксиса, описанные выше.
Итак, попробуем сформулировать перечень проблем, которые нам предстоит решить для адаптации нашего приложения к работе в режиме "клиент-сервер":
Процедуры открытия таблиц заменяются подключением к базе данных.
При отсутствии прямого доступа к таблицам, расположенным в базе SQL Server Или Oracle страновятся невозможными ряд команд FoxPro и их сопровождение (LOCATE,SEEK,GO,BROWSE,RELPACE,SCATTER,GATHER,...).
Программа должна работать как с DBF в режиме "файл-сервер", так и с базами SQL . При этом код приложения должен изменить минимально. Фактически требуется ввести уровень абстрагирования предметной области приложения од типа данных.
VFP для организации работы с данными в SQL или других базах в режиме "клиент-сервер" предлагает класс CursorAdapter. Перечисленные выше проблемы он предлагает решать следующим способом:
При открытии таблиц вам предлагается использовать USE в случае прямого доступа к DBF и создавать объект класса CursorAdapter в случае клиент-сервер.
Перед командами прямого доступа к таблицам (LOCATE,SEEK,GO) в случае клиент-сервер предлагается формулировать запрос в свойстве SelectCmd и выполнить метод FillCursor. Фактически предлагается сформулировать параметризированный запрос. Естественно, для LOCATE и SEEK фраза WHERE в SelectCmd должна соответствовать условию поиска.
В других случаях нужно додумывать алгоритм. Глупо было бы выбирать в курсор целиком большую таблицу.
Чтобы не пораждать два экземпляра запросов - один(исходный) для выборки напрямую из DBF, а другой - для выборки из SQL - воспользуйтесь функцией E_SQL_Select_Convert() и прилагаемыми методиками для преобразования уже существующего и отлаженного запроса к стандарту SQL.
Смотрите подробное описание класса в документации по VFP 9.
Предлагается несколько иной подход, чем при использовании CursorAdapter . Не нужно создавать отдельный объект на каждый алиас. Команды прямого доступа заменяются вызовом методов класса, а уж внутри метода принимается решение - как поступать. При этом единый текст условия LOCATE или запроса будет передаваться в метод и метод будет конвертировать его функцией E_SQL_Select_Convert() для данных в SQL.
Можете использовать класс непосредственно, но думаю, что придется его наследовать и развивать. Вцелом идея формулируется так:
Вы создаете объект этого класса или наследника (например с именем "Access") и вставляете его в контейнер, форму или используете отдельно.
Все таблицы сессии должны быть открыты путем вызова метода USE этого объекта. Если таблица - DBF и используется напрямую, то она будет открыта командой USE объект закроет ее в событии DESTROY , и вы не должны заботиться о ее закрытии. Если таблица находится в базе данных SQL, то предварительно должно быть создано соединение с источником данных функцией SQLConnect() или SQLStringConnect(), и идентификатор этого соединения должен быть передан третьим параметром в метод USE
Находите в методах форм и программах команды LOCATE, SCATTER, GATHER, REPLACE, GO TOP, BROWSE, INSERT и заменяйте их на вызовы соответствующих методов объекта ACCESS с соблюдением индивидуальных для метода правил.
Унаследуйте класс и на основании методик, применяемых в существующих методах, создавайте свои методы, например, эквиваленты команд SKIP, SEEK, ...
Если вы намерены использовать Pass-Through технологию доступа к данным, а не представления (Views), то будут очень полезны методы SELECTSQL и SENT_UPDATES
Смотрите пример (SAMPL.SCX) для понимания работы класса и его методов более детально. Кратко же методы делают следующее:
BROWSE (cAlias, cBrowseCommand [,cKeyFieldList)
Если cAlias ссылается на таблицу DBF с прямым доступом, то выполняет команду BROWSE. Если cAlias ссылается на таблицу в базе данных SQL Server, то этот метод сделает следующее:
- Если предварительно методами LOCATE или GO_TOP не были созданы временное представление во временной DBC или курсор для этого cAlias, то будет создано временное предстваление (temporary view) во временной базе DBC .
- Если передан список ключевых полей (через запятую), то вновь созданное представление будет редактируемым и все изменения в нем будут сохраняться в исходной таблице.
- Свойство FetchAsNeeded вновь созданного представления будет установлено в .T. для быстрого выполнения выборки первых записей таблицы в локальный курсор и последующей выборки строк в процессе просмотра их в GRID (BROWSE)
- Выбирается алиас представления и выполняется команда BROWSE.
Для подробного изучения смотрите кнопки "Browse" и "Refresh and Browse" в примере SAMPLE.SCX
BROWSE_PREPARE (cAlias, cBrowseCommand [,cKeyFieldList)
Делает то же, что и метод BROWSE, но не выполняет команду BROWSE. Фактически же метод BROWSE сначала вызывает метод BROWSE_PREPARE и выполняет команду BROWSE. Используйте данный метод для подготовки алиасов для объектов GRID.
CLOSE (cAlias)
Закрывает отдельную таблицу, открытую методом USE
CONTINUE (cAlias)
Выполняется после метода LOCATE. Если cAlias - таблица DBF с прямым доступом, то просто будет выполнена команда CONTINUE. Метод возвратит значение Found(). Если cAlias ссылается на таблицу в базе SQL, то будет выбран курсор, созданного командой LOCATE и выполнена команда SKIP. Метод возвратит значение NOT EOF()
GATHER (cAlias [,cFieldList])
Если cAlias ссылается на таблицу DBF с прямым доступом, то просто выполнит команду GATHER ... MEMVAR ... . Если cAlias - курсор или представление таблицы из базы SQL, созданные командами LOCATE, GO_TOP, BROWSE и этот курсор или view являются updatable, то также будут выполнена GATHER ... MEMVAR ... . Для отправки изменений в исходную таблицу метод перейдет на другую строку в представлении/курсоре и вернется обратно (буферизация строки). Если локальное представление или курсор не будут найдены, то получите сообщение об ошибке.
GET_TEMP_FILE (cAlias [,cAsAlias] [,nDataSession])
Генерирует, регистрирует во внутренним списке и возвращает уникальное имя временного файла. Этот временный файл будет удален в событии DESTROY.
GO_TOP (cAlias [,cKeyFieldList] [,cOrderBy])
Если cAlias ссылается на таблицу DBF, то просто выполняет Go TOP. Если cAlias ссылается на таблица в базе SQL Server и алиас существует, то также будет выполнено GO TOP. Если нет алиаса, то данный метод создаст с временное представлетие (temporary view) с именем cAlias во временной базе данных и установит его свойство "FetchAsNeeded" в .T. для ускоренной выборки первых строк во временный курсор. Если указан список ключевых полей, то временное представление будет редактируемым и все изменения в нем будут сохранены в исходной таблице. Если указано cOrderBy, то строки в курсор будут выбираться в указанном порядке.
INSERT (cAlias [,cFieldList])
Если cAlias ссылается на таблицу DBF, то будет выполнена INSERT INTO (cAlias) FROM MEMVAR или , если передан список полей), INSERT INTO cAlias (cFieldList) VALUES (cFieldList) . Если cAlias ссылается на таблицу в базе SQL Server и cAlias существует (Used(), создан методами LOCATE,GO_TOP,BROWSE) и cAlias - updatable, то команда INSERT INTO также будет выполнена для алиаса. После чего для отправки изменений в исходную таблицу будет выполнен переход на соседнюю строку и назад (буферизация строки). Если алиас отсутствует, но cAlias ссылается на таблицу в базе SQL то команда INSERT INTO будет выполнена непосредственно в источнике функцией SQLExec() . При этом все ссылки на переменные будут преобразованы.
IS_UPDATABLE (cAlias)
Возвращает .T. если алиас редактируемый, т.е. все изменения в нем будут сохранены в исходной таблице.
LOCATE (cAlias, cCondition [,cKeyFieldList])
Ссылки на переменные, свойства объектов и константные выражения FoxPro в cCondition должны быть с префиксом ? Если cAlias ссылается на таблицу DBF, то просто выполняется LOCATE с заданным условием. При этом из cCondition будут удалены ненужные знаки функцией E_SQL_Remove_Directives() . Если cAlias ссылается на таблицу в базе SQL Server, то будет выполнено преобразование синтаксиса функцией E_SQL_Select_Convert([Select * Where ] + cCondition). Если существует курсор от предыдущей выборки, то он будет закрыт. Далее будет сделана выборка в курсор с именем cAlias функцией SQLExec(). Если передан список ключевых полей cKeyFieldList, то выбранный курсор будет редактируемым, т.е. все изменения в нем будут сохранены в исходной таблице. После метлда LOCATE Можно вызывать методы CONTINUE, GATHER, SCATTER, INSERT, BROWSE, GO_TOP, которые будут работать с созданным курсором.
LOCATE_BF (cAlias, cCondition ,cKeyFieldList)
Делает то же самое, что и LOCATE, но использует принудительную буферизацию. Если для алиаса еще не создан локальный буфер, то будет создан внутренний пустой курсор со структурой идентичной таблице. При каждом вызове метод LOCATE_BF будет сначала искать нужное значение в этом локальном курсоре. И уж если нужного значения нет - то будет искать в исходной таблице. Результат каждого поиска сохраняется в локальном буфере.
Очень ускоряет работу BROWSE и GRID !
!!! Список полей cKeyFieldList - обязателен. Но ! После работы метода текущим будет алиас локального буфера с найденной строкой, а не cAlias. И буфер не может быть редактируемый.
Объект Access поудаляет за собою вуфера в событии DESTROY
Для примера смотрите кнопку "Browse Invoices" в SAMPLE.SCX
REPLACE (cAlias, cFields, cFor)
В качестве cFields передавайте FIELD1 With Value1,FIELD2 With Value2,... Можно ссылаться на переменные, свойства объектов и константные выражения FoxPro префиксом '?' . Если cAlias ссылается на таблицу DBF, то просто выполняется команда Replace ... For ... Предварительно функция E_SQL_Remove_Directives() удалит '?' . Если cAlias ссылается на таблицу в базе SQL Server и существует курсор или view созданные методами LOCATE, GO_TOP, BROWSE , то так же будет выполнена команда REPLACE ... ROR ... для cAlias. Если cAlias отсутствует, но ссылается на таблицу в базе SQL Server, то в источнике будет выполнена команда UPDATE ... WHERE ... Фраза 'With' будет заменена на '=' и сгенерированную команду UPDATE выполнит функция SQLExec().
SCATTER (cAlias [,cFieldList] [,lBlank])
Если Used(cAlias) , то будет выполнена команда SCATTER, иначе - ошибка.
SELECTSQL (cFromAlias, cCommand, cResultAlias [, cUpdateTable [, cKeyFieldList]] [,lRemote [,lTemporary [,lReadOnly]]] [,lTmpTable] [,lFetchAsNeeded] )
Запрос Select SQL. Выбирает данные из таблиц в источнике в локальный курсор или View. В команде cCommand должны быть помечены ссылки на переменные, свойства объектов и константные выражения FoxPro символами '?' или '~' .
Если cFromAlias ссылается на DBF, то просто будет выполнена команда cCommand + [ INTO CURSOR ] + cResultAlias + [ READWRITE]. Предварительно лишний синтаксис будет удален функцией E_SQL_Remove_Directives().
Если cFromAlias ссылается на таблицу в базе SqL Sever, то запрос передается через соединение, определенное для cFromAlias путем передачи третьего параметра в метод USE. cCommand будет обработана функцией E_SQL_Find_Variables(), а потом E_SQL_Select_Convert().
Если нужен редактируемый курсор в результате запроса, то нужно передать имя исходной таблицы как cUpdateTable и список ключевых полей как cKeyFieldList. В случае выборки из DBF изменения в курсоре могут быть сохранены в исходной таблице только вызовом метода SENT_UPDATES. Для SQL будет updatable курсор или view с буферизацией строки.
Для выборки в удаленную таблицу передайте lRemote=.T. При этом, если вы передадите lTemporary=.T., то в случае DBF создастся CURSOR (cResultAlias) READWRITE, а в случае базы SQL - временная таблица '#' + cResultAlias в базе TEMPDB. !!!Если при этом в запросе есть ссылки на локальные переменные, то они должны быть помечены только знаком '~' . Для создания курсора не readwrite - передайте lReadOnly=.T.
Если в случае выборки из DBF вы хотите выбрать не в курсор, а во временную таблицу, то передайте lTmpTable=.T. . В этом случае можно будет выполнять ZAP и PACK.
Если передать lFetchAsNeeded, то в случае выборки из базы SQL Server метод создаст не курсор, а временный view во временной DBC и установит его свойство FetchAsNeeded = .T. Выборка первых строк будет ускорена, а остальные строки бубут "подтягивться" в процессе пролистывания GRID или непосредственно view командой SKIP. !!!Команда GO приведет к немедленному выбору всех остальных строк.
SEND_UPDATES (cAlias [,cCurRec [,cField]])
Вызовите этот метод для отправки изменений в источник, если cAlias был выбран методом SELECTSQL и переданы cUpdateTable и cKeyFieldList. Для DBF данные будут отправлены в таблицу кодом метода, а для SQL будет сделан временный переход на другую запись и обратно (буферизация строки).
USE (cTable [,cAlias] [,nConnect])
Каждая таблица должна быть открыта путем вызова данного метода. Если не указан cAlias, то он будет равен JUSTSTEM(cTable). Все DBF, открытые этим методом будут закрыты в DESTROY. Если таблица находится в базе SQL Server, то предварительно должно быть установлено соединение функцией SQLConnect() или SQLStringConnect() и идентификатор этого соединения должен быть передан в этот метод третьим параметром.
Пример использования класса EFox_Data_Adapter - Sample.scx
Перед активизацией формы сделайте текущей ее директорию. Когда запустите форму, первым делом она спросит, где расположены данные - в DBF (имеется ввиду прямой доступ) или в базе SQL ? Форма работает с двумя таблицами:
Если Вы выберите вариант DBF, то далее вам будет предложено указать директорию, где находятся эти таблицы. Если решите работать с SQL Server, то появится стандартный диалог для подключения к базе данных SQL Server. Если таблицы ранее не были созданы, то укажите любую директорию или любую базу данных SQL Server. Форма предложит создать таблицы в выбранных вами источниках.
Вверху формы - поля с кодом и наименованием текущего клиента. Если нет текущего клиента, то они недоступны.
Далее в фоме можно делать следующее:
Всю остальную информацию более полно можно получить изучая тексты методов.
Перечень компонентов продукта EasySQL4Fox
| EFoxSQL.FLL | библиотека с функциями для преобразования синтаксиса SQL запросов составленных на FoxPro к синтаксису языка Transact-SQL MS SQL Server. |
| E_SQL_Text_Convert.prg | программа для преобразования синтаксиса запросов в тексте с кодом на FoxPro |
| E_Check_UDFs_For_Connect.prg | проверка наличия и создания UDFs dbo.PADL() и dbo.PADR в базе SQL Server |
| Classlib\ | директория с классом EFox_Data_Adapter и примером |
| Classlib \ EFoxSQL.VCX(VCT) | библиотека с классом EFox_Data_Adapter |
| ClassLib \ Sample.SCX(SCT) | форма -приме использования класса EFox_Data_Adapter |
| ClassLib \ ISample.SCX(SCT) | вызывается из Sample.scx |
| EasySQL4Fox.CHM | документация |
EasySQL4Fox home Скачать ознакомительную версию EasySQL4Fox English language
www.vallmind.com http://ecalcpad.vallmind.com http://vmzipper.vallmind.com