for flooders
:: Главная :: Решения :: Статьи :: Сайт М. Дроздова :: Файловый архив :: Книга по VFP 9 :: Русский Help Online :: OFF-LINE Форум
   Л и с о в о д ы   в с е х   с т р а н,  о б ъ е д и н я й т е с ь !!!  

Список Форумов  :: Visual Foxpro, Foxpro for DOS
   :: Помощь сайту :: 

CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Вячеслав Клепинин
Автор

Сообщений: 1597
Откуда: Санкт-Петербург
Дата: 06.04.06 15:35:34ОтветитьЦитировать
Привет всем!

В методе Load формы создаю объект из класса MyCursorAdapter:
  
  this.oTK = CREATEOBJECT('MyCursorAdapter')  
  WITH this.oTK  
     .Alias = 't_tk'  
     .DataSourceType = 'ODBC'  
     .DataSource = sqlstringconnect(pcConnString)  
     .CursorSchema = 'id_tk i, name c(80), shortname c(15)'  
     .SelectCmd = 'SELECT id_tk, name, shortname FROM tk'  
     .FetchSize = -1  
     .KeyFieldList = 'id_tk'  
     .Tables = 'tk'  
     .UpdatableFieldList = 'name, shortname'  
     .UpdateNameList = 'id_tk tk.id_tk, name tk.name, shortname tk.shortname'  
     .CursorFill(.t.)  
  ENDWITH

Поле id_tk в таблице tk на MS SQL Server - типа IDENTITY. Надо получать его значение после вставки очередной записи.
Для этого в классе MyCursorAdapter, наследуемом от CursorAdapter, в обработчике AfterInsert пишу:
  
  LPARAMETERS cFldState, lForce, cInsertCmd, lResult  
  IF lResult = .t.  
     LOCAL lnCurrentArea, lcKeyField  
     lnCurrentArea = SELECT()    
     IF SQLEXEC(this.DataSource, "SELECT @@IDENTITY AS newID", "_cur_id") = 1  
        lcKeyField = this.Alias + '.' + this.KeyFieldList && Формируется имя таблицы и поля  
        replace &lcKeyField WITH _cur_id.newID  
        USE IN _CUR_ID  
     ENDIF   
     SELECT(lnCurrentArea)  
  ENDIF
То есть попытка создания некого универсального механизма, учитывающего, что ключ в курсоре и таблице на сервере имеют одинаковые имена.

На форме Grid, в котором отображается курсор.
Код в Click кнопки, по которой добавляется новая запись:

  
  LOCAL lcText  
    
  DO FORM InputString WITH 'Новая товарная категория', 'Добавить', '', 80 TO lcText  
  IF LEN(lcText) > 0  
     INSERT INTO t_tk (name, shortname) VALUES (lcText, " ")  
     thisform.Grid1.Refresh  
  ENDIF  
  thisform.Grid1.SetFocus

Вызываемая форма InputString просто позволяет ввести строку и возвращает её в Unload'e.
После добавления новой записи она появляется в Grid, но вижу, что значение поля id_tk равно нулю. Событие AfterInsert курсорадаптера происходит только после перемещения указателя в курсоре на соседнюю (или любую другую) запись, после чего сразу вижу, что поле id_tk добавленной записи становится отличным от нуля и соответстует значению этого поля в таблице на сервере.
Меняю код в методе Click кнопки:

  
  LOCAL lcText  
    
  DO FORM InputString WITH 'Новая товарная категория', 'Добавить', '', 80 TO lcText  
  IF LEN(lcText) > 0  
     CREATE CURSOR tmp (Name c(80), ShortName(15))  
     INSERT INTO tmp (name, shortname) VALUES (lcText, " ")  
     INSERT INTO t_tk (name, shortname) VALUES (tmp.name, tmp.shortname)  
     USE IN tmp  
     thisform.Grid1.Refresh  
  ENDIF  
  thisform.Grid1.SetFocus
Эффект тот же самый - запись добавилась, событие AfterInsert не произошло.
Делаю так:

  
  LOCAL lcText  
    
  DO FORM InputString WITH 'Новая товарная категория', 'Добавить', '', 80 TO lcText  
  IF LEN(lcText) > 0  
     CREATE CURSOR tmp (Name c(80), ShortName(15))  
     INSERT INTO tmp (name, shortname) VALUES (lcText, " ")  
     INSERT INTO t_tk (name, shortname) SELECT * FROM tmp  
     USE IN tmp  
     thisform.Grid1.Refresh  
  ENDIF  
  thisform.Grid1.SetFocus
Тихо балдею. Событие AfretInsert происходит вовремя, появляющаяся в Grid строка содержит правильное значение поля id_tk.
Почему-то такой вариант работает.

И ещё.
В методе AfterInsert пытаюсь получить значение функции SCOPE_IDENTITY():
  
     IF SQLEXEC(this.DataSource, "SELECT SCOPE_IDENTITY() AS newID", "_cur_id") = 1  
     .....
В ответ получаю, что _cur_id.newid - NULL!

Вот такая, блин, загогулина.
Господа, просветите!

Заранее спасибо!
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Влад Колосов

Сообщений: 22498
Откуда: Ростов-на-Дону
Дата: 06.04.06 16:06:23ОтветитьЦитировать
BOL
Returns the last IDENTITY value inserted into an IDENTITY column in the same scope. A scope is a module -- a stored procedure, trigger, function, or batch

Поскольку в SQLEXEC(this.DataSource, "SELECT SCOPE_IDENTITY() AS newID", "_cur_id") НЕ производится вставка, то и
SCOPE_IDENTITY() возвращает null.

P.S. Я бы делал SendUpdates после вставки, CA все же буферизирован.


------------------
Совершенство - это не тогда, когда нельзя
ничего прибавить, а тогда, когда нечего убавить.




Исправлено: Влад Колосов, 06.04.06 16:09
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Владимир Максимов

Сообщений: 13784
Откуда: Москва
Дата: 06.04.06 16:50:06ОтветитьЦитировать

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 12569
Дата: 06.04.06 17:08:14ОтветитьЦитировать
Мда, нет решения.


------------------
Есть многое на свете, друг Горацио...
Что и не снилось нашим мудрецам.
(В.Шекспир Гамлет)
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Владимир Максимов

Сообщений: 13784
Откуда: Москва
Дата: 06.04.06 17:11:39ОтветитьЦитировать
PaulWist
Мда, нет решения.
Почему? По приведенной ссылке 3 варианта решения. Два действуют при некоторых ограничениях, а через хранимые процедуры работает всегда.
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 12569
Дата: 06.04.06 17:21:30ОтветитьЦитировать
Владимир, да всё это, на мой взгляд, через "назад", приведенные Цингаузом решения приходилось использовать и раньше.

Хотя, зависит от задачи, но возвращать IDENTITY на клиета опять же на мой взгляд не совсем правильно, всё таки это нарушает логику КС, подозреваю, что IDENTITY на клиенте в большинстве случаев нужен для заполнения FK, что как сам понимаешь - не правильно.


------------------
Есть многое на свете, друг Горацио...
Что и не снилось нашим мудрецам.
(В.Шекспир Гамлет)
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Владимир Максимов

Сообщений: 13784
Откуда: Москва
Дата: 06.04.06 17:29:48ОтветитьЦитировать
PaulWist
подозреваю, что IDENTITY на клиенте в большинстве случаев нужен для заполнения FK, что как сам понимаешь - не правильно.
Почему? А как же создавать документы имеющие "шапку" и "детали"? Т.е. создали запись в шапке и ... как создать детали не зная кода в только что созданной шапке?
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Вячеслав Клепинин
Автор

Сообщений: 1597
Откуда: Санкт-Петербург
Дата: 06.04.06 17:32:36ОтветитьЦитировать
Владимир Максимов

Эти материалы Цингауза я читал, именно там и нашёл решение.
Единственное, я не пользовал в своём коде TABLEUPDATE(.F.) - как у него, почему-то считал, что курсор адаптер сам всё сделает. Тем более, что инструкция INSERT ... SELECT ... отрабатывает нормально, без явного вызова TABLEUPDATE(). С TABLEUPDATE() всё работает правильно.

Влад Колосов

> Поскольку в SQLEXEC(this.DataSource, "SELECT SCOPE_IDENTITY() AS newID", "_cur_id")
> НЕ производится вставка, то и
> SCOPE_IDENTITY() возвращает null.

Обойти это никак нельзя? Планируется многопользовательский ввод, боюсь, что с @@IDENTITY будут проблемы.



Исправлено: Вячеслав Клепинин, 06.04.06 17:37
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Вячеслав Клепинин
Автор

Сообщений: 1597
Откуда: Санкт-Петербург
Дата: 06.04.06 17:37:13ОтветитьЦитировать
PaulWist

Цитата:
IDENTITY на клиенте в большинстве случаев нужен для заполнения FK, что как сам понимаешь - не правильно
В том - то и дело, идёт оперативный ввод рекламных модулей, порядка 2000 в день, и если в базе нет какого-то сопуствующего параметра, например, организации или классификатора, их нужно оперативно добавлять при вводе модуля. То есть добавили организацию, и сразу в несколько таблиц ставим её id.
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Владимир Максимов

Сообщений: 13784
Откуда: Москва
Дата: 06.04.06 17:47:16ОтветитьЦитировать
Вячеслав Клепинин
Владимир Максимов
Эти материалы Цингауза я читал, именно там и нашёл решение.

> Поскольку в SQLEXEC(this.DataSource, "SELECT SCOPE_IDENTITY() AS newID", "_cur_id")
> НЕ производится вставка, то и
> SCOPE_IDENTITY() возвращает null.

Обойти это никак нельзя? Планируется многопользовательский ввод, боюсь, что с @@IDENTITY будут проблемы.
Так там же по ссылке и приведено решение - хранимые процедуры. По другому SCOPE_IDENTITY() для CursorAdapter - не получишь, поскольку происходит выполнение другого SCOPE. Вне контекста CursorAdapter.
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 12569
Дата: 06.04.06 17:48:20ОтветитьЦитировать
Владимир Максимов
PaulWist
подозреваю, что IDENTITY на клиенте в большинстве случаев нужен для заполнения FK, что как сам понимаешь - не правильно.
Почему? А как же создавать документы имеющие "шапку" и "детали"? Т.е. создали запись в шапке и ... как создать детали не зная кода в только что созданной шапке?

Ну как,
- создаём временную табличку на сервере (или используем готовую постоянную-временную табличку) засылаем на сервер запись из мастера
- засылаем Детали
- вызываем ХП, которая знает о наших временных табличках и умеет эти данные засунуть в постоянные таблички

В данном случае нет головной боли о возврате для IDENTITY, те если надо ХП через OUTPUT вернёт ID, и тогда можно сделать REQUERY() и то только в том случае если занесенная запись удовлетворяет критерию отбора.


------------------
Есть многое на свете, друг Горацио...
Что и не снилось нашим мудрецам.
(В.Шекспир Гамлет)
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 12569
Дата: 06.04.06 17:53:16ОтветитьЦитировать
Вячеслав Клепинин
PaulWist
Цитата:
IDENTITY на клиенте в большинстве случаев нужен для заполнения FK, что как сам понимаешь - не правильно
В том - то и дело, идёт оперативный ввод рекламных модулей, порядка 2000 в день, и если в базе нет какого-то сопуствующего параметра, например, организации или классификатора, их нужно оперативно добавлять при вводе модуля. То есть добавили организацию, и сразу в несколько таблиц ставим её id.

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


------------------
Есть многое на свете, друг Горацио...
Что и не снилось нашим мудрецам.
(В.Шекспир Гамлет)
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Владимир Максимов

Сообщений: 13784
Откуда: Москва
Дата: 06.04.06 21:03:45ОтветитьЦитировать
PaulWist
Владимир Максимов
А как же создавать документы имеющие "шапку" и "детали"? Т.е. создали запись в шапке и ... как создать детали не зная кода в только что созданной шапке?

Ну как,
- создаём временную табличку на сервере (или используем готовую постоянную-временную табличку) засылаем на сервер запись из мастера
- засылаем Детали
- вызываем ХП, которая знает о наших временных табличках и умеет эти данные засунуть в постоянные таблички

В данном случае нет головной боли о возврате для IDENTITY, те если надо ХП через OUTPUT вернёт ID, и тогда можно сделать REQUERY() и то только в том случае если занесенная запись удовлетворяет критерию отбора.
Это надо понимать так, что от Cursor Adapter следует отказаться? Как стыкуется идеология временных таблиц и уже сделанная выборка?

Или же предполагается перхватывать момент сброса изменений и писать собственную ХП (создание временных таблиц, закачка данных, вызов ХП)? И чем этот подход отличается от подхода описанного Алексеем по поводу создания собственной ХП для возврата SCOPE_IDENT()?

Как мне кажется, у Алексея все значительно проще.
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 12569
Дата: 07.04.06 09:30:24ОтветитьЦитировать
Цитата:
Это надо понимать так, что от Cursor Adapter следует отказаться?

Да, надо отказаться. Аргумент один и не бесспорный - придерживаться одной выбранной идеологии, во всяком случае для меня, не надо вспоминать "а вот в этой форме" как осуществляется работа с данными.

Цитата:
Как стыкуется идеология временных таблиц и уже сделанная выборка?

Вопрос давольно большой.

Добавление данных Мастер-Детали (курсоры на клиенте "пустые" в смысле не выбраны с сервера, заполнены пользователем)

Идея.

procedure Form.SaveDataToServerMethod()  
 	* переводим транзакцию в ручной режим  
  	SQLSETPROP(hnd,'Transaction',2)  
 	* выполняем создание временных таблиц  
  	cc = FILETOSTR('CreateTmpTableForStepN.txt')  
  	SQLEXEC(hnd, cc)  
 ** проверки на ошибки опустим   
 	* Заносим Мастера (пусть будет одна запись)  
 	* строим строку для занесения во временную таблицу  
  	cc = [INSERT INTO  #Master (....)]	  
  	cc = cc + [VALUES (?MasterLocalCursor.Fields,.....)]  
  	SQLEXEC(hnd, cc)  
 	* Заносим детали  
  	SELECT DetailsLocalCursor  
  	SCAN  
 	* строим строку для занесения во временную таблицу  
  		cc = [INSERT INTO  #Details (....)]	  
  		cc = cc + [VALUES (?DetailsLocalCursor.Fields,.....)]  
  		SQLEXEC(hnd, cc)  
  	ENDSCAN  
 	* Вызываем собственно ХП сервера для занесения в таблички данных  
  	SQLEXEC(hnd, [ exec pr_InputStepN ?InputPatameters]  
 	* Удаляем временные таблички  
  	cc = FILETOSTR('DropTmpTableForSepN.txt')  
  	SQLEXEC(hnd, cc) >= 0  
 	* Завершаем транзакцию  
  	SQLCOMMIT(hnd)  
 	* Восстанавливаем соединение на автокоммит  
  	SQLSETPROP(hnd,'Transaction',1)  
  endproc  
    
 *Сама ХП сервера  
    
  CREATE PROCEDURE pr_InputStepN   
  @InputParameters...  
  AS  
  DECLARE @ID int  
  -- Проверки всякие  
  -- Заполняем Мастера  
  BEGIN Transaction -- если нужно  
  INSERT INTO Master (MasterFields..) SELECT MasterFields FROM #Master  
  SET @ID = SCOPE_IDENT()  
  -- Заполняем детали  
  INSERT INTO Details (MasterID, DetailsFields..) SELECT @ID, DetailsFields FROM #details  
  if @@error <> 0 ....  
  begin  
  ROLLBACK TRAN  
  RAISEERROR(...)  
  end  
  COMMIT TRAN

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

Цитата:
Или же предполагается перхватывать момент сброса изменений и писать собственную ХП (создание временных таблиц, закачка данных, вызов ХП)? И чем этот подход отличается от подхода описанного Алексеем по поводу создания собственной ХП для возврата SCOPE_IDENT()?
Как мне кажется, у Алексея все значительно проще.

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


------------------
Есть многое на свете, друг Горацио...
Что и не снилось нашим мудрецам.
(В.Шекспир Гамлет)
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Вячеслав Клепинин
Автор

Сообщений: 1597
Откуда: Санкт-Петербург
Дата: 07.04.06 09:31:56ОтветитьЦитировать
PaulWist

Цитата:
Вячеслав, надо ли это понимать, что в форме ввода есть возможность добавлять новые позиции в справочник? Те вызывается форма редактирования справочника из модуля редактирования документа?

Да, именно так. Сущность "Модули", ессно, включает в себя мигрирующие ключи, но, кроме этого, она связана с несколькими другими сущностями отношением "многие ко многим", и необходимость получать ID определяется именно этим. Причём данные этих сущностей пополняются динамически, то есть в одном модуле может рекламироваться несколько организаций, брендов, товарных категорий и пр., и они постоянно пополняются. И заставлять юзера отдельно заходить в справочники - это резкое торможение процесса. Все эти множественные сущности показываются в своих Grid окна свойств модуля; при сохранении модуля соответствующим образом модифицируются записи в индексных таблицах, реализующих отношение "многие ко многим". Не представляю, как здесь можно использовать ХП.
От курсорадаптера отказываться не хочется - уж очень штука удобная; использовать requery неудобно, потому что некоторые справочники содержат десятки тысяч записей, да и проблема найти её потом, нужную запись, после обновления. Не вводить же ещё одно поле, содержаще уникальные значения! Ключи на базе GUID, генерируемых в фоксе, тоже не хочется использовать, сразу размер таблиц растёт.
Больше всего напрягает использование @@IDENTITY, потому что ввод многопользовательский, а эта переменная возвращает ID последней добавленной записи. Хотя триггеров на Insert в БД нет, только каскадное удаление и изменение.
Что же делать-то?
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 12569
Дата: 07.04.06 09:47:29ОтветитьЦитировать
Слушай Вячеслав, а почему бы в момент редактирования документа, при вводе нового значения справочника форма ввода справочника не возвращала бы только, что введённое значение, вполне приемлемый вариант.


------------------
Есть многое на свете, друг Горацио...
Что и не снилось нашим мудрецам.
(В.Шекспир Гамлет)
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
boba
[MVP]

Сообщений: 4968
Откуда: Медвежьи озера-
Дата: 07.04.06 10:09:25ОтветитьЦитировать
я возился сэтим делом пару месяцев и более
половина событий срабатывает один раз всего
если только нет в коде пары tableupdate- cursorfill ( те перезапрос)
Как я помню такая же штука раньше была в ремоут вью
Второй tableupdate давал правду, но на деле извменений н6е вносил
А работало так tableupdate -requery()
Как я понял, класс курсор адаптера собран из механизма view
и работает по этому сценарию
а иначе не работает не только упомянутое вами событие но и куча других
Я тоже вел переписку с Микрософт по этому поводу , конкретно с Джимом Сандерсом , они подтвердили все это
Може исправят во втором сервис пэке так как уже много людей обратило на это внимание
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
MichaelD

Сообщений: 7578
Дата: 07.04.06 10:16:12ОтветитьЦитировать
Вячеслав,

Вячеслав Клепинин
Что же делать-то?

- в любом случае, [добавление + получение кода добавленной] должен делать сервер, возвращая клиенту код добавленной
- если, как показал Павел, то нужно предусмотреть возврат клиенту списка кодов добавленных записей (строка, массив) для последующего использования на клиенте, если потребуется


------------------
С уважением,
Михаил Дроздов, Пермь, Россия
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Влад Колосов

Сообщений: 22498
Откуда: Ростов-на-Дону
Дата: 07.04.06 11:11:31ОтветитьЦитировать
Именно так, в таких сложных случаев View не используется, но используются хранимые процедуры плюс возврат данных (ID).


------------------
Совершенство - это не тогда, когда нельзя
ничего прибавить, а тогда, когда нечего убавить.
Ratings: 0 negative/0 positive

Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Вячеслав Клепинин
Автор

Сообщений: 1597
Откуда: Санкт-Петербург
Дата: 07.04.06 12:31:50ОтветитьЦитировать
Господа!
Курсор адаптер будет жить!
Всё оказалось достаточно просто. Нужно при помощи SQLEXEC добавлять запись непосредственно в таблицу на сервере, одновременно получая значение SCOPE_IDENTITY(), потом обновлять курсор и позиционироваться на запись с полученным ID:
  
  lcCmd = 'INSERT INTO tablename (Field1, ....) VALUES (?var1, ?...) SELECT SCOPE_IDENTITY() as newid'  
  lnResult = SQLEXEC(oCA.DataSource, lcCmd, '_Cur_id')  
  IF lnResult = 1  
     oCA.CursorRefresh()  
     SEEK _cur_id.newid ORDER TAG tagname IN cursorname   
     USE IN _cur_id  
  ENDIF
Всё отлично работает, для таблицы ~14 тыс записей, 200 байт одна запись, понадобилось 0,32 секунды - что для меня вполне приемлемо!

И не нужно насиловать себя всякими AfterInsert и прочими прибамбасами!

Огромное спасибо всем откликнувшимся!



Исправлено: Вячеслав Клепинин, 07.04.06 12:33
Ratings: 0 negative/0 positive



Извините, только зарегистрированные пользователи могут писать в этом форуме.

On-line: 45 sphinx  and Guests: 44


© 2000-2017 Fox Club 
Яндекс.Метрика