:: Visual Foxpro, Foxpro for DOS
И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
Здравствуйте
Собрался с силами и, прислушавшись к советам ssa и Igor Korolyov и других не менее уважаемых форумчан, решился навести порядок в работе с курсорадаптерами. А именно вынести все команды создания курсоров, добавления, изменения и удаления их строк в отдельные универсальные функции. Вроде получается, но дошел до tableupdate и теперь сижу и туплю:
Создал функцию, производящую сохранение изменений в указанном курсоре:
PARAMETERS lcCursor1
IF EMPTY(lcCursor1)
=MESSAGEBOX('Вы не указали что сохранять!',64)
RETURN .f.
ENDIF
LOCAL ca_name
ca_name=GETCURSORADAPTER(lcCursor1)
IF !TABLEUPDATE(1,.f.,lcCursor1)
IF ShowError('Ошибка при сохранении изменений в таблице '+ALLTRIM(ca_name.comment))
IF MESSAGEBOX('Похоже другой пользователь изменил данные! Плевать на него?',36)=6
=TABLEUPDATE(1,.t.,lcCursor1)
RETURN .t.
ELSE
=TABLEREVERT(.t.,lcCursor1)
RETURN .f.
ENDIF
ELSE
=TABLEREVERT(.t.,lcCursor1)
RETURN .f.
ENDIF
ENDIF
RETURN .t.

в ней функция ShowError, которая все ошибки, кроме 1585 подробно выводит на экран:

LPARAMETERS lcErrText
LOCAL laE(5)
=AERROR(laE)
IF !INLIST(lae(1),1585)
messagebox(lcErrText ;
+CHR(13)+'AERROR(1) : '+ALLTRIM(STR(lae(1))) ;
+CHR(13) ;
+CHR(13)+'AERROR(2) : '+lae(2) ;
+CHR(13) ;
+IIF(!ISNULL(lae(5)),CHR(13)+'AERROR(5) : '+ALLTRIM(STR(lae(5))),'') ;
+CHR(13) ;
+IIF(!ISNULL(lae(3)),CHR(13)+'AERROR(3) : '+lae(3),'') ;
)
RETURN .f.
ELSE
RETURN .t.
ENDIF

Написал все это и сразу в голове много вопросов, на которые не могу найти ответов:
1. Если 1-й tableupdate (в строке 6) закончился неудачей, то по желанию пользователя либо перезапись либо отмена. Но 2-й tableupdate (в строке 9) тоже может успешно не завершиться по какой-то причине (да и tablerevert тоже). Их результаты тоже нужно проверять. Замкнутый круг?
2. Если мной изменялась 1 строка в курсоре, то вроде просто: если конфликт (строка изменилась другими) - перезаписать да/нет. А если я изменял много строк (пакетно) и конфликт может возникнуть в 5-й, 14-й, 123-й и т.д. строках. Для каждой переспрашивать?
3. Как быть если нужно не просто спрашивать "перезаписать да/нет?", а что-то типа: "поле Цена было 5, вы сохраняете 6, но кто-то уже поставил 4. перезаписать да/нет?"
4. Если п.2 "скрестить" с п.3, то вообще коллапс ...

Кто-то может сказать, что я все слишком усложняю, но зато 1 раз написать функцию и на все ситуации.
Буду благодарен советам.
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
PaulWist

Сообщений: 14601
Дата регистрации: 01.04.2004
1. TableUpdate необходимо обернуть в транзакцию, поскольку в "общем" случае сохранение происходит в больше чем одну таблицу.

2. Ошибки сохранения не обязательно связаны с конфликтом модификации юзера, поэтому сообщение 'Похоже другой пользователь изменил данные!' не совсем правильное.

3. Самое главное, существуют 3 стратегии сохранения данных

а) первый сохранил, первый прав.
б) последний сохранил, последний прав.
с) последний решает сам.

Опыт показывает, что если юзеру НУЖНО сохранить, то он ДОБЬЁТСЯ своего любыми способами, таким образом стратегия б) необходима в 99.99% случаев.


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

Сообщений: 137
Дата регистрации: 08.02.2014
PaulWist
1. TableUpdate необходимо обернуть в транзакцию, поскольку в "общем" случае сохранение происходит в больше чем одну таблицу.
2. Ошибки сохранения не обязательно связаны с конфликтом модификации юзера, поэтому сообщение 'Похоже другой пользователь изменил данные!' не совсем правильное.

3. Самое главное, существуют 3 стратегии сохранения данных

а) первый сохранил, первый прав.
б) последний сохранил, последний прав.
с) последний решает сам.

Опыт показывает, что если юзеру НУЖНО сохранить, то он ДОБЬЁТСЯ своего любыми способами, таким образом стратегия б) необходима в 99.99% случаев.

Спасибо, по каждому из ваших пунктов:
1. Это функция апдейта одной таблицы и ЕЁ ВЫЗОВ планирую обернуть в транзакцию
2. 'Похоже другой пользователь изменил данные!' - только если ошибка 1585, по остальным алерт с кодами и описаниями этой ошибки
3. Согласен почти полностью. "Почти" потому что данные могут оказаться критическими (например деньги на счету и тд)

Готов выслушать еще мнения
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
AndyNigmatec

Сообщений: 1551
Откуда: Волгоград
Дата регистрации: 28.06.2015
Голосую: кто последний - тот и папа (с)

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



Исправлено 1 раз(а). Последнее : AndyNigmatec, 28.08.18 10:32
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
AndyNigmatec
Голосую: кто последний - тот и папа (с)
данные критические - но раз юзверь имеет права на их изменение - значит ему можно и нужно их менять, а ежели там логические условия (ну там остаток не менее нуля) - то можно же и триггер навесить к примеру

Если по такому принципу, то и вся функция не нужна, просто =TABLEUPDATE(1,.t.) и баста.
Вопрос-то в том и стоит: как правильно (и красиво) сделать для тех 0.01% когда нужна экспертиза ДНК )))
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
AndyNigmatec

Сообщений: 1551
Откуда: Волгоград
Дата регистрации: 28.06.2015
ну и засунуть сию экспертизу в тригер, ежели отцовство установлено - то все ок, иначе - выбрасываем исключение ... как-то так вижу, но не настаиваю - условия то разные бывают
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
AndyNigmatec
ну и засунуть сию экспертизу в тригер, ежели отцовство установлено - то все ок, иначе - выбрасываем исключение ... как-то так вижу, но не настаиваю - условия то разные бывают

Вопрос не где (в тригере, хп, просто функции), а как!
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
AndyNigmatec

Сообщений: 1551
Откуда: Волгоград
Дата регистрации: 28.06.2015
Так на вопрос как - вы в самом первом посте и расписали )))

Как я понимаю возникли сомнения как обрабатывать возникающие ошибки/исключения - типа предлагать ли пользователю перезаписывать именно свои изменения и для каждой ли записи. Это не вопрос как - а вопрос идеологии, и на него PaulWist как раз и ответил.

Доб. для каждой ли записи мучить пользователя сильно зависит от логики задачи - где-то может быть крайне назойливым 100500 вопросов пользователю при сохранении одного "документа" и логичнее ограничиться одним вопросом, а где-то может эта инфа об ошибках важна для каждой изменяемой записи.
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
PaulWist

Сообщений: 14601
Дата регистрации: 01.04.2004
Sandwich
Вопрос не где (в тригере, хп, просто функции), а как!

Использовать:

1. Первичные ключи
2. Внешние ключи
3. Правила
4. Триггеры

Ошибка, нарушение бизнес логики в этих сущностях БД автоматом сгенерит ошибку, которую поймает TableUpdate.


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

Сообщений: 34580
Дата регистрации: 28.05.2002
Сохранять "многозаписей" лучше по одной - в ручном цикле. При этом никаких messagebox "в процессе" сохранения, и тем более не стоит применять такую "вывернутую" схему как в приведенном коде (когда метод "извещения об ошибке" определяет "тип ошибки" для вызывающего кода).
Можно пробегаясь по изменениям (GetNextModified в помощь) и сохраняя их "по одному" набивать в какой-то буфер (от тупой строки до массива, или вспомогательного курсора) все произошедшие ошибки, НЕ применяя повторный force tableupdate и/или tablerevert.
Потом уже, в конце, спросить за все ошибки и запускать повторный цикл по "непрошедшим" записям.
Вообще подобным "скопом" сохраняют "несущественные" справочники (оставляя "непрошедшие" записи несохранёнными, а прошедшие фиксируя коммитом транзакции - не только фоксовой, но и ODBC-ной, что более важно).
Либо табличные части документов - и при этом НЕ допускается никаких "частичных сохранений" - либо ВСЁ прошло, либо ничего. Но в этом случае вероятность получить конфликт совместного изменения весьма мала - т.к. для этого надо чтобы пользователи начали править один и тот же "документ" одновременно.

Вариант с показом "что было, что мы ввели, что какой-то дядя ввёл пока мы думали" вполне возможен - рефреш записи после "неудачного" tableupdate и смотрим через OldVal() и CurVal() на "теневые" значения полей (соответственно "старое" и "то что сейчас на сервере" - "наше" значение напрямую как курсор.поле доступно, ну или через EVAL("курсор.поле") если опосредовано имена полей использовать).

Я не уверен что эту схему имеет смысл применять для сохранения МНОГИХ записей за раз (это либо дичайшая форма где все подобные конфликты будут показаны, либо "по одному" придётся ходить и разрешать их - подтверждая перезапись, или отменяя свои изменения) - вот одной записи вполне логично и несложно будет. В FFC классы есть эту схему поддерживающие (но не для ODBC источников, насколько я помню, и совершенно точно не для CAD-ов) - код взять напрямую вряд ли получится, но идею подсмотреть вполне себе можно. И главное понять как именно получать всё что надо для рисования своего "разрешателя конфликтов".
DO FORM HOME()+ "\samples\solution\ffc\conflicts.scx"


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
Igor Korolyov
Сохранять "многозаписей" лучше по одной - в ручном цикле.

Спасибо за совет, помогло.

Но я уперся в одну проблему: GETNEXTMODIFIED возвращает номер строки, которая изменилась, не смотря на то что новое измененное значение не отличается от старого, т.е. было 5 поменяли на 5.

Как это можно победить?



Исправлено 1 раз(а). Последнее : Sandwich, 29.08.18 21:11
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sawradym

Сообщений: 2244
Откуда: Винница
Дата регистрации: 15.05.2007
Sandwich
Igor Korolyov
Сохранять "многозаписей" лучше по одной - в ручном цикле.

Спасибо за совет, помогло.

Но я уперся в одну проблему: GETNEXTMODIFIED возвращает номер строки, которая изменилась, не смотря на то что новое измененное значение не отличается от старого, т.е. было 5 поменяли на 5.

Как это можно победить?

Так не меняйте 5 на 5. Никто не мешает перед сохранением проверить не совпадают ли старое и новое значение и при совпадении просто пропускать сохранение.


------------------
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Сам же говоришь - она ИЗМЕНИЛАСЬ Фокс не анализирует что на что меняется, он сам факт модификации фиксирует. Хочешь побыть умнее разработчиков, просто в том же цикле проверяй значения в "помеченных как изменённые" полях (GETFLDSTATE(-1) покажет какие поля фокс считает изменёнными, и потом сравнивай OLDVAL(имя_поля) и EVAL(имя_поля)). Можно через TableRevert() "отменить" эти фиктивные правки.

Хотя в общем случае даже изменение "само на себя" МОЖЕТ иметь прикладной смысл. Например триггер может пересчитать какие-нить поля, ту же "дату последнего изменения" или "автор последнего изменения". А если там есть какая мега-бизнес-логика, то это (менять поле "само на себя") можно применять как запускалку соответствующей логики.
Опять же, в конкурентной/многопользовательской среде попытка поменять "Вася" на "Вася" НЕ является бессмысленной - может быть в середине процесса редактирования пользователем инфы кто-то прописал туда вместо "Васи" "Колю", и это уже не будет изменение "самого себя на само себя" - хотя клиент до начала сохранения и не видел что там были конкурентные модификации (увидит при попытке такого вот фиктивного сохранения - тот самый Update Conflict).

P.S. "пропускать" сохранение в общем случае нельзя - для тех же операций перезапроса/CursorRefresh курсор должен быть "чистым" - незафиксированные изменения (даже "фиктивные") не дадут обновить курсор. Так что если уж и надо "игнорировать изменение 5 на 5", то надо для этой записи TableRevert сделать.


------------------
WBR, Igor




Исправлено 1 раз(а). Последнее : Igor Korolyov, 29.08.18 22:37
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
Igor Korolyov
Сам же говоришь - она ИЗМЕНИЛАСЬ Фокс не анализирует что на что меняется, он сам факт модификации фиксирует. Хочешь побыть умнее разработчиков, просто в том же цикле проверяй значения в "помеченных как изменённые" полях (GETFLDSTATE(-1) покажет какие поля фокс считает изменёнными, и потом сравнивай OLDVAL(имя_поля) и EVAL(имя_поля)). Можно через TableRevert() "отменить" эти фиктивные правки.
Эх! Надеялся что чего-то "секретного" не знаю и хотел обойтись без этого.
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Нет ничего "секретного" - просто такие изменения сохраняют и всё Ну а кому надо геморроя - извращаются отделяя их от "настоящих изменений"


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
Igor Korolyov
Нет ничего "секретного" - просто такие изменения сохраняют и всё Ну а кому надо геморроя - извращаются отделяя их от "настоящих изменений"
да я и сохраняю, только tableupdate на такой записи возвращает .f. и приходится его игнорировать
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Sandwich
да я и сохраняю, только tableupdate на такой записи возвращает .f. и приходится его игнорировать
Если возвращает .F. то надо смотреть AERROR(), возможно команды генерируемые для сервера, или просто лог на сервере и устранять причины ошибки - не должен он так работать...


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
Всем снова здравствуйте
Решил не париться и многозаписи сохранять TABLEUPDATE(1,.t.,lcCursor1).
Один раз апдейт завершился ошибкой о том, что пытаюсь записать не уникальную запись по уникальному ключу.
Вопрос: записи, которые апдейтились до этой ошибки сохранились в БД или "откатились вместе с ошибкой"?
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Igor Korolyov

Сообщений: 34580
Дата регистрации: 28.05.2002
Если сохранение не обёрнуто в транзакцию (локальную для dbf, серверную для ODBC) то будут сохранены.


------------------
WBR, Igor
Ratings: 0 negative/0 positive
Re: И снова конфликт совместного доступа
Sandwich
Автор

Сообщений: 137
Дата регистрации: 08.02.2014
Igor Korolyov
Если сохранение не обёрнуто в транзакцию (локальную для dbf, серверную для ODBC) то будут сохранены.
Не успел написать:
сам проверил - сохранилось. А причина оказалась в следующем:
При операциях (вставка/изменения) сначала проверял на уникальность с помощью LOCATE - все было нормально (дубликаты исключались). Но сегодня провел тест на быстродействие, сравнивая скорость LOCATE и SELECT. SELECT "выиграл" со счетом 9 сек (10 000 запросов) против 17 сек (10 000 LOCATE-ов).
Я обрадовался и везде поменял локейты на селекты. Скорость возросла. Однако это привело к такому аварийному завершению TABLEUPDATE: оказывается SELECT "не видит" вставленные через INSERT значения в буфферизированный курсор. А LOCATE прерасно "видит".
Можно как-то "протереть глаза" селекту?
Вопрос снят - хэлп рулит.



Исправлено 1 раз(а). Последнее : Sandwich, 15.09.18 20:49
Ratings: 0 negative/0 positive


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

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

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