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

Сообщений: 1597
Откуда: Санкт-Петербург
Дата регистрации: 26.03.2004
Привет всем!

В методе 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
Влад Колосов

Сообщений: 22664
Откуда: Ростов-на-Дону
Дата регистрации: 05.05.2005
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 все же буферизирован.


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




Исправлено 1 раз(а). Последнее : Влад Колосов, 06.04.06 17:09
Ratings: 0 negative/0 positive
Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Владимир Максимов

Сообщений: 14003
Откуда: Москва
Дата регистрации: 02.09.2000
Как автомат-ки получать автоинкриментное поле SQL сервера в КА
Ratings: 0 negative/0 positive
Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 14327
Дата регистрации: 01.04.2004
Мда, нет решения.


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

Сообщений: 14003
Откуда: Москва
Дата регистрации: 02.09.2000
PaulWist
Мда, нет решения.
Почему? По приведенной ссылке 3 варианта решения. Два действуют при некоторых ограничениях, а через хранимые процедуры работает всегда.
Ratings: 0 negative/0 positive
Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
PaulWist

Сообщений: 14327
Дата регистрации: 01.04.2004
Владимир, да всё это, на мой взгляд, через "назад", приведенные Цингаузом решения приходилось использовать и раньше.

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


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

Сообщений: 14003
Откуда: Москва
Дата регистрации: 02.09.2000
PaulWist
подозреваю, что IDENTITY на клиенте в большинстве случаев нужен для заполнения FK, что как сам понимаешь - не правильно.
Почему? А как же создавать документы имеющие "шапку" и "детали"? Т.е. создали запись в шапке и ... как создать детали не зная кода в только что созданной шапке?
Ratings: 0 negative/0 positive
Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Вячеслав Клепинин
Автор

Сообщений: 1597
Откуда: Санкт-Петербург
Дата регистрации: 26.03.2004
Владимир Максимов

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

Влад Колосов

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

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



Исправлено 1 раз(а). Последнее : Вячеслав Клепинин, 06.04.06 18:37
Ratings: 0 negative/0 positive
Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Вячеслав Клепинин
Автор

Сообщений: 1597
Откуда: Санкт-Петербург
Дата регистрации: 26.03.2004
PaulWist

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

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

> Поскольку в 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

Сообщений: 14327
Дата регистрации: 01.04.2004
Владимир Максимов
PaulWist
подозреваю, что IDENTITY на клиенте в большинстве случаев нужен для заполнения FK, что как сам понимаешь - не правильно.
Почему? А как же создавать документы имеющие "шапку" и "детали"? Т.е. создали запись в шапке и ... как создать детали не зная кода в только что созданной шапке?

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

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


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

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

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


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

Сообщений: 14003
Откуда: Москва
Дата регистрации: 02.09.2000
PaulWist
Владимир Максимов
А как же создавать документы имеющие "шапку" и "детали"? Т.е. создали запись в шапке и ... как создать детали не зная кода в только что созданной шапке?

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

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

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

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

Сообщений: 14327
Дата регистрации: 01.04.2004
Цитата:
Это надо понимать так, что от 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
Откуда: Санкт-Петербург
Дата регистрации: 26.03.2004
PaulWist

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

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

Сообщений: 14327
Дата регистрации: 01.04.2004
Слушай Вячеслав, а почему бы в момент редактирования документа, при вводе нового значения справочника форма ввода справочника не возвращала бы только, что введённое значение, вполне приемлемый вариант.


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

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

Сообщений: 7578
Дата регистрации: 14.05.2005
Вячеслав,

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

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


------------------
С уважением,
Михаил Дроздов, Пермь, Россия
Ratings: 0 negative/0 positive
Re: CursorAdapter, событие AfterInsert и поле сервера типа IDENTITY
Влад Колосов

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


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

Сообщений: 1597
Откуда: Санкт-Петербург
Дата регистрации: 26.03.2004
Господа!
Курсор адаптер будет жить!
Всё оказалось достаточно просто. Нужно при помощи 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 и прочими прибамбасами!

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



Исправлено 2 раз(а). Последнее : Вячеслав Клепинин, 07.04.06 13:33
Ratings: 0 negative/0 positive


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

On-line: 11 (Гостей: 11)

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