powered by simpleCommunicator - 2.0.61     © 2026 Programmizd 02
Целевая тема:
Создать новую тему:
Автор:
Закрыть
Цитировать
Форумы / FoxPro, Visual FoxPro [игнор отключен] [закрыт для гостей] / Владимиру Максимову, вопрос
18 сообщений из 18, страница 1 из 1
Владимиру Максимову, вопрос
    #33422990
For Peace
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Добрый день!
Ниже описан текст программы, который Вы мне как то предлагали использовать для сохранения изменений.
Прошу поясните что за переменная m.gltransact, она не определена и в этом месте выдает ошибку.
Спасибо за внимание.

*___________________________________________
IF CURSORGETPROP("Buffering")=9999 && 3
LOCAL lnField,lcField && llOverwrite,llChanged,
glCnanged=.F.
glOverwrite=.F.
FOR lnField=1 TO FCOUNT()
lcField=FIELD(lnField)
IF CURVAL(lcField)<>OLDVAL(lcField)
glCnanged=.T.
EXIT
ENDIF
ENDFOR
IF glCnanged
lnResult=MESSAGEBOX("База заблокирована другим пользователем, повторить попытку записи?",4,"Сохранение изменений...")
IF lnResult=6
glOverwrite=.T.
ENDIF
ENDIF
IF glCnanged AND NOT glOverwrite
=TABLEREVERT()
ELSE
IF m.gltransact
BEGIN TRANSACTION
=TABLEUPDATE(.F.,.T.)
END TRANSACTION
ELSE
=TABLEUPDATE(.F.,.T.)
ENDIF
ENDIF
ENDIF

LOCAL llSuccess, lcMessageText, laError(1), llOverWrite, llExit
llOverWrite = .F.
llExit = .F.
DO WHILE llExit = .F.
* Предполагаю что будет только один шаг цикла
llExit = .T.
BEGIN TRANSACTION
llSuccess = TableUpdate(.T.,m.llOverWrite,alias())
IF llSuccess=.T.
END TRANSACTION
ELSE
* Немедленный откат изменений
ROLLBACK
* Анализ причины ошибки ПОСЛЕ отката
=AERROR(laError)
IF laError[1,1]=1585
lcMessageText = "Пока Вы вносили изменения другим пользователем "+;
"были изменены те же самые данные. Писать поверх внесенных изменений?"
IF MessageBox(m.lcMessagetext,4+32+256,'Конфликт обновления')=6
* Ответили "Да". Повторяю цикл сохранения, но уже с перезаписью
llExit = .F.
llOverWrite = .T.
loop
ELSE
* Ответили "Нет". Действия по обновлению данных
ENDIF
ELSE
lcMessageText = "В процессе сохранения произошла ошибка № "+;
LTRIM(STR(laError[1,1]))+chr(13)+laError[1,2]
MessageBox(m.lcMessageText,0+48,'Ошибка при сохранении')
ENDIF
ENDIF
ENDDO
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33423183
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
А Вы ничего не путаете?

То что написано после строки

LOCAL llSuccess, lcMessageText, laError(1), llOverWrite, llExit

Действительно похоже на код написанны мной, но вот первая часть

IF CURSORGETPROP("Buffering")=9999 && 3
...
ENDIF

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

Однако в любом случае, я не вижу смысла в том куске, который использует переменную m.gltransact.

Похоже, что эта переменная проверяет факт существования (точнее, отсутствия) "внешней" транзакции (TXNLEVEL()). Но в любом случае нужен контроль корректности завершения функции TableUpdate().

В том виде, в котором приведен этот код, он не имеет смысла.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33423245
For Peace
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Вообщем задача такая, чтобы не морочиться с сохранением я в отдельной процедуре хочу сделать универсальную процедуру сохранения,
Т.е. на всю комлексную программу где около 50 баз данных и множество не связанных задач есть общая кнопка "Редактировать" , "Сохранить" и "Изменить"
и вот по нажатию этой кнопки сохранить- перебираются все 50 баз где скидываются буфера, может быть даже и в лишний раз. Прошу помогите с кодом этой процеДУРЫ, где в оптимальном варианте и безопасно можно скидывать с буфера в базу в одной процедуре.
Примерно это будет так, как вы написали, а именно под мою задачу она сгодится?

Спасибо зараннее!

P.S. Я с этим месяц вожусь, то что предьявите мне - не глядя возьму за основу



LOCAL llSuccess, lcMessageText, laError(1), llOverWrite, llExit
llOverWrite = .F.
llExit = .F.
DO WHILE llExit = .F.
* Предполагаю что будет только один шаг цикла
llExit = .T.
BEGIN TRANSACTION
llSuccess = TableUpdate(.T.,m.llOverWrite,alias())
IF llSuccess=.T.
END TRANSACTION
ELSE
* Немедленный откат изменений
ROLLBACK
* Анализ причины ошибки ПОСЛЕ отката
=AERROR(laError)
IF laError[1,1]=1585
lcMessageText = "Пока Вы вносили изменения другим пользователем "+;
"были изменены те же самые данные. Писать поверх внесенных изменений?"
IF MessageBox(m.lcMessagetext,4+32+256,'Конфликт обновления')=6
* Ответили "Да". Повторяю цикл сохранения, но уже с перезаписью
llExit = .F.
llOverWrite = .T.
loop
ELSE
* Ответили "Нет". Действия по обновлению данных
ENDIF
ELSE
lcMessageText = "В процессе сохранения произошла ошибка № "+;
LTRIM(STR(laError[1,1]))+chr(13)+laError[1,2]
MessageBox(m.lcMessageText,0+48,'Ошибка при сохранении')
ENDIF
ENDIF
ENDDO
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33423406
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
При написании универсальных процедур надо менять саму логику работы. Кроме того, для облегчения себе жизни (упрощения кода) следует ввести некоторые допущения.

Сам процесс сохранения лично я (в своей задаче) разбиваю на несколько этапов:

Этап 0 - проверка самого факта внесения каких-либо изменений

Этап 1 - проверка факта ввода обязательных реквизитов и проверка корректности ввода данных

Этап 2 - формирование значений служебных полей, недоступных пользователю и заполнение прочих полей, недоступных пользователю

Этап 3 - собственно запись в базу данных

Этап 4 - обновление информации на формах и во временных таблицах

Каждый этап оформляется как отдельная процедура или метод класса.

Более того, каждый этап, в свою очередь, разбивается на несколько подэтапов. В данном случае, интересен "Этап 3".

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

Код: plaintext
1.
2.
3.
4.
IF (НЕ удалось сбросить буфер)
	(Выясняем причину неудачи и сообщаем пользователю)
	(Возможный откат изменений выполненных на "Этапе 2")
ENDIF

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

В этом условном коде - 2 подэтапа (или 3, в зависимости от задачи)

Первый подэтап - это собственно процесс сброса буфера. Оформляем как отдельную процедуру или метод класса.

Например, ниже следующий код был написан на VFP6SP5.

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
* SAVE_BUFFER
*
* Сброс буфера в исходные таблицы
* Предполагается, что таблицы находятся в каком-либо режиме буферизации, т.е.
* речь идет об изменении буфера
*----------------------------*
* Возвращаемое значение
* Возвращает .T. если сброс прошел успешно или .F. - если нет
*----------------------------*
* Параметры
*!*	tcRecordBuffer - Список таблиц, через запятую, у которых следует сбросить только буфер строки
*!*	tcTableBuffer - Список таблиц, через запятую, у которых следует сбросить буфер всей таблицы
*!*	tlTransaction - Следует ли окружить сброс буфера транзакцией?
LPARAMETERS tcRecordBuffer, tcTableBuffer, tlTransaction
IF TYPE('tcRecordBuffer')<>'C'
	tcRecordBuffer=''
ENDIF
IF TYPE('tcTableBuffer')<>'C'
	tcTableBuffer=''
ENDIF
IF TYPE('tlTransaction')<>'L' OR PCOUNT()< 3 
	tlTransaction=.T.
ENDIF

LOCAL llSuccess
llSuccess=.T.
IF m.tlTransaction=.T.
	BEGIN TRANSACTION
ENDIF

* Сначала делаю разбор первого параметра (сброс буфера текущей строки)
LOCAL lnI, laRecordBuffer( 1 )
IF m.llSuccess=.T. AND EMPTY(m.tcRecordBuffer)=.F. ;
		AND ALINES(laRecordBuffer,ChrTran(m.tcRecordBuffer,",",chr( 13 )),.T.)> 0 
	FOR lnI= 1  TO ALEN(laRecordBuffer)
		IF USED(laRecordBuffer[m.lnI])=.T. AND CursorGetProp("Buffering",laRecordBuffer[m.lnI])> 1 
			llSuccess=TableUpdate(.F.,.T.,laRecordBuffer[m.lnI])
		ENDIF
		IF m.llSuccess=.F.
			EXIT
		ENDIF
	ENDFOR
ENDIF

* Теперь разбираю второй параметр (сброс буфера таблицы)
LOCAL laTableBuffer( 1 )
IF m.llSuccess=.T. AND EMPTY(m.tcTableBuffer)=.F. ;
		AND ALINES(laTableBuffer,ChrTran(m.tcTableBuffer,",",chr( 13 )),.T.)> 0 
	FOR lnI= 1  TO ALEN(laTableBuffer)
		IF USED(laTableBuffer[m.lnI])=.T. AND CursorGetProp("Buffering",laTableBuffer[m.lnI])> 3 
			llSuccess=TableUpdate(.T.,.T.,laTableBuffer[m.lnI])
		ENDIF
		IF m.llSuccess=.F.
			EXIT
		ENDIF
	ENDFOR
ENDIF

IF m.tlTransaction=.T.
	IF m.llSuccess=.T.
		END TRANSACTION
	ELSE
		ROLLBACK
	ENDIF
ENDIF
RETURN m.llSuccess

Правда, здесь используется безусловный сброс буфера - второй параметр в команде TableUpdate() всегда .T., но его можно сделать еще одним параметром данной процедуры.

Как видишь, здесь вообще нет разбора причины ошибки. Просто возвращает .T. или .F.

А вот если этот метод вернул .F., то тогда делаем анализ причины ошибки.

Для этого выполняем второй подэтап. Т.е. запускаем ЕЩЕ ОДИН метод, внутри которого вызывается функция AERROR() и выполняется разбор причины ошибки.

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

Я использовал такую схему анализа ошибки

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
* MessageError
*
* В качестве возвращаемого значения формируется сообщение об ошибке
LOCAL lcMessageText, laError( 1 , 7 )
EXTERNAL ARRAY gaErrors

DO CASE
CASE AERROR(laError)= 0 			
********************************
* т.е. нет ошибок
********************************
	lcMessageText = ""

CASE laError[ 1 , 1 ]= 1526  			
********************************
* Ошибки ODBC
********************************
	DO CASE
	CASE laError[ 1 , 5 ]= 6 
		lcMessageText = 'Не удалось найти нужный сервер SQL'+chr( 13 )+;
				'Возможно, связь по сети не установлена.'
	CASE laError[ 1 , 5 ]= 229  
		lcMessageText = 'Вы вошли по паролю пользователя, который не обладает'+chr( 13 )+;
				'достаточными правами для выполнения требуемых действий'+chr( 13 )+;
				'с одним из объектов базы данных'+chr( 13 )+;
				RTRIM(SubStr(laError[ 1 , 3 ],RAT("]",laError[ 1 , 3 ])+ 1 ))+chr( 13 )+;
				'Обратитесь к администратору сервера или разработчику программы.'
	CASE laError[ 1 , 5 ]= 515  
		lcMessageText = 'Указано недопустимое значение'
	CASE laError[ 1 , 5 ]= 547  
		lcMessageText = 'Нарушение ссылочной целостности'+chr( 13 )+;
				'Нельзя удалить элемент на который ссылаются из других мест,'+chr( 13 )+;
				'либо Вы ссылаетесь на элемент которого уже не существует'
	CASE laError[ 1 , 5 ]= 1000 
		lcMessageText = 'Возможно, произошел разрыв соединения'
	CASE laError[ 1 , 5 ]= 2627  
		lcMessageText = 'Элемент с такими же ключевыми значениями уже существует' + chr( 13 )+;
				'Измените введенные значения'
	CASE laError[ 1 , 5 ]= 18456 
		lcMessageText = 'При установке соединения указано неверное имя пользователя или пароль'
	CASE laError[ 1 , 5 ]>= 50000 
		lcMessageText = 'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 5 ]))+chr( 13 )+;
				RTRIM(SubStr(laError[ 1 , 3 ],RAT("]",laError[ 1 , 3 ])+ 1 ))
	OTHERWISE
		lcMessageText = 'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 5 ]))+chr( 13 )+;
				RTRIM(SubStr(laError[ 1 , 3 ],RAT("]",laError[ 1 , 3 ])+ 1 ))+chr( 13 )+;
				'Обратитесь к администратору сервера или разработчику программы.'
	ENDCASE

CASE InList(laError[ 1 , 1 ], 1427 , 1429 )=.T.	
********************************
* Ошибки OLE
********************************
	lcMessageText = 'Произошла ошибка в '+LTRIM(STR(laError[ 1 , 4 ]))+chr( 13 )+ALLTRIM(laError[ 1 , 3 ])
	IF IsNull(laError[ 1 , 5 ])=.F.
		lcMessageText = lcMessageText + chr( 13 )+;
				'подробнее о ней можно прочитать в'+chr( 13 )+ALLTRIM(laError[ 1 , 5 ])
					
	ENDIF

********************************
* Далее ошибки собственно VFP
********************************
CASE laError[ 1 , 1 ]= 108 
		lcMessageText = 'Произошла ошибка № 108'+chr( 13 )+;
				'Таблица используется другим пользователем.'+chr( 13 )+;
				'Никакие изменения в ней невозможны'
CASE laError[ 1 , 1 ]= 109 
		lcMessageText = 'Произошла ошибка № 109'+chr( 13 )+;
				'Запись используется другим пользователем.'+chr( 13 )+;
				'Никакие изменения в ней невозможны'
CASE laError[ 1 , 1 ]= 130 
		lcMessageText = 'Произошла ошибка № 130'+chr( 13 )+;
				'Одна из записей используется другим пользователем.'+chr( 13 )+;
				'Никакие изменения в ней невозможны'
CASE laError[ 1 , 1 ]= 1539 
* триггер вернул .F.
	LOCAL lcDopText
	lcDopText=''
	IF TYPE('gaErrors[1,2]')='C'
		lcDopText=chr( 13 )+gaErrors[ 1 , 2 ]
	ENDIF
	IF TYPE('gaErrors[1,1]')<>'N' OR gaErrors[ 1 , 1 ]=- 1 
		lcMessageText = 'Нарушение ссылочной целостности.'
		DO CASE
		CASE laError[ 1 , 5 ]= 1 		&& Insert trigger failed
			lcMessageText=m.lcMessageText+chr( 13 )+;
				'При создании документа, Вы сослались на не существующий документ'+;
				m.lcDopText
		CASE laError[ 1 , 5 ]= 2 		&& Update trigger failed
			lcMessageText=m.lcMessageText+chr( 13 )+;
				'При изменении документа, Вы сослались на не существующий документ'+;
				m.lcDopText
		CASE laError[ 1 , 5 ]= 3 		&& Delete trigger failed
			lcMessageText=m.lcMessageText+chr( 13 )+;
				'Вы удаляете документ, на который ссылаются из других документов'+;
				m.lcDopText
		OTHERWISE		&& На всякий случай.
		ENDCASE
	ELSE
		lcMessageText = 'Произошла ошибка № '+LTRIM(STR(gaErrors[ 1 , 1 ]))+m.lcDopText
	ENDIF
CASE laError[ 1 , 1 ]= 1884 
* Нарушение уникальности индекса типа Primary или Candidat
		lcMessageText = 'Элемент с такими же ключевыми значениями уже существует'+chr( 13 )+;
				'Измените введенные значения'+chr( 13 )+;
				'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 1 ]))+chr( 13 )+ALLTRIM(laError[ 1 , 2 ])
OTHERWISE
		lcMessageText = 'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 1 ]))+chr( 13 )+ALLTRIM(laError[ 1 , 2 ])
ENDCASE

RETURN (m.lcMessageText)

В вызвавшей процедуре анализирую возвращенное значение и если это не пустая строка выдаю MessageBox().

Т.е. получаю примерно такой код

Код: plaintext
1.
2.
3.
4.
5.
IF Save_Buffer("Table1,Table2","Table3,Table4",.T.) = .F.
	LOCAL lcMessageText
	lcMessageText = MessageError()
	MessageBox(m.lcMessage)
ENDIF

Если оформить передачу списка полей в метод Save_Buffer как проперти класса, то можно совсем "отвязаться" от конкретной формы.

Смысл всего выше сказанного в том, чтобы разбить процесс сохранения на несколько частей. Каждая часть решает свой круг задач. Такую схему достаточно легко модифицировать под свои нужды в конкретной ситуации. Т.е. можно вклинить дополнительную обработку между разными этапами. В случае единого универсального блока это становиться проблематичным.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33428646
~Иван~
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Приветствую Владимир,
большое спасибо за классный
и комментированный код
буду его использовать.
Но возникла задача, которую вы предвидели,

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

НЕобходимо спрашивать юзера
"Писать поверх изменений или нет"
Вот только ума не приложу,
где это "вставить", чтоб было так же красиво и понятно как у Вас
:-)
Ясно, что необходимо
анализировать наличие ошибки
AERROR(laError)
IF laError[1,1]=1585
Но где и как????
То есть "отправить" юзера в метод * MessageError
или прямо в методе SAVE_BUFFER ????
и тогда как и где???
Вообщем поимеем наглость, и для законченности
этого красивого решения, смеем попросим Владимира
дописать для нас неучей код под этот случай.
Очень много людей будут Вам благодарны.
Я не первый код в фоксе,
но до сих пор так красиво писать не могу
Спасибо
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33428700
Igor Korolyov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Hi Владимир!

Мне кажется что в общем случае не удастся сделать сохранение "всем скопом" -
пример: Структура Master-Detail с Restrict триггерами - удалили данные из
Master - удалили данные из Detail (всё в буфере конечно! при этом Detail
100% будет в table mode, а режим для Master не критичен - пускай это будет
record mode, или table, но таблица в списке стоит ДО detail-таблицы) -
выполняем сохранение по твоей схеме - на сохранении Master падаем по ошибке
триггера - т.к. из detail данные ещё не удалены! они всё ещё в буфере.
Ты скажешь - "значит надо поменять порядок сохранения, сначала сохраняя
Detail, а потом Master" - хорошо, поменяем порядок, и рассмотрим вторую
ситуацию - ввод новой записи - один новый Master и к нему несколько Detail -
сначала сохраняем Detail - и конечно падаем по ошибке триггера :(
Менять порядок обратно - тогда см. п.1.
В общем я пришёл к выводу. что в подобной ситуации единственно приемлемым
вариантом является МНОГОСТУПЕНЧАТЫЙ, ИЕРАРХИЧЕСКИЙ процесс сохранения
данных.
Начинаем сохранение с TableUpdate() для удалённых записей - при этом по всей
иерархии связей курсоров идём от наиболее глубоких к корню (от Detail к
Master-у). Дойдя до мастера (до корня), мы можем сохранить все записи в
нём - а потом пойти сохранять изменённые и добавленные записи вниз по
иерархии (но уже в обратном порядке - от более высоколежащих к более
глубоким).
Вариант изменения PK в Master таблицах я даже не рассматриваю, как заведомо
бессмысленное действие, которое будет отметено Update триггером в любом
случае.

Posted via ActualForum NNTP Server 1.3
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33429422
~Иван~
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Да Владимир,
можно ли еще Вас попросить
привести код для случая с
удалением записи, как в представлении,
так и в таблице/группе таблиц, представлений.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33429557
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Igor Korolyov
Мне кажется что в общем случае не удастся сделать сохранение "всем скопом" -
пример: Структура Master-Detail с Restrict триггерами - ...
В общем случае, конечно. Но ведь я специально оговорился, что речь идет о конкретной (моей) задаче, где я же сам и ввел ряд ограничений и допущений.

В частности, у меня невозможно одновременно (в одном буфере процесса редактирования) и создать и удалить записи из пары таблиц Master-Detail. Т.е. либо удаляешь, либо создаешь (модифицируешь), но никак не и то, и другое. Хотя в Detail допустимо и удалять и создавать в одном процессе.

В таблицах, где идет связь один-ко-многим, триггер на удаление всегда Cascade, а на вставку всегда Restrict.

Т.е. у меня (в моей задаче) такого вопроса вообще не стоит. Master всегда первая в списке на обновление, а за ней всегда Detail.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33429567
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
~Иван~НЕобходимо спрашивать юзера
"Писать поверх изменений или нет"
Вот только ума не приложу,
где это "вставить", чтоб было так же красиво и понятно как у Вас
:-)
Ясно, что необходимо
анализировать наличие ошибки
AERROR(laError)
IF laError[1,1]=1585
Но где и как????

Ну, это достаточно просто. Идея заключается в том, что команда AERROR() не сбрасывает код ошибки. Т.е. ее можно запускать сколько угодно раз и она всегда будет возвращать одно и то же значение до тех пор, пока не возникнет новая ошибка. Т.е. можно примерно так:

1) В методе Save_Buffer добавлем 4 параметр, который будет определять значение второго параметра в команде TableUpdate(). Думаю, как это дописать в исходный код, сообразишь.

2) Собственно сохранение получается примерно так:

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
LOCAL lcMessageText, laError( 1 ), llOverWrite
FOR lnI= 1  TO  2 
	llOverWrite = (m.lnI= 2 )
	* По умолчанию, цикл выполняем только  1  раз
	lnI= 2 
	* Выполняем попытку сохранения
	IF Save_Buffer("Table1,Table2","Table3,Table4",.T.,m.llOverWrite) = .F.
		* Сначала определяю номер ошибки
		=AERROR(laError)
		* Теперь формирую сообщение об ошибке
		lcMessageText = MessageError()
		* Если ошибка - это модификация другим пользователем,
		* то дополнительный запрос
		IF laError[ 1 , 1 ]= 1585 
			lcMessageText = m.lcMessageText +chr( 13 )+;
					'Писать поверх?'
			IF MessageBox(m.lcMessageText, 4 + 32 + 256 )= 6 
				* Пошли на второй заход цикла
				m.lnI= 1 
			ENDIF
		ELSE
			MessageBox(m.lcMessage)
		ENDIF
	ENDIF
ENDFOR

~Иван~
Да Владимир,
можно ли еще Вас попросить
привести код для случая с
удалением записи, как в представлении,
так и в таблице/группе таблиц, представлений.

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

У меня ведь, если дать команду

Save_Buffer("Table1,Table2","Table3,Table4")

Это означает, что сначала будет сброшен буфер текущей строки в Table1, затем буфер текущей строки в Table2, затем буфер всей таблицы в Table3 и затем буфер всей таблицы в Table4.

Вот и остается определиться в каком порядке необходимо сбрасывать буферы Local View и таблиц-источников
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33431697
~Иван~
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
Приветствую всех, в частности Владимира Максимова
Жажда универсализации сподвигла создавать меня глобальный объект для манипулирования записями
В частности я работаю с представлениями
в 5 буферизации, источником одного грида может быть несколько представлений, соединенных отношением 1-1 (потому что
представление обновляет только одну таблицу)
На основании кода Владимира, имел неосторожность кое-чего
"дописать"
Особенно меня интересует метод удаления записи.
Подскажите какие грабли я не вижу, кроме вышеописанных
Владимиром и Игорем
спасибо

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
47.
48.
49.
50.
51.
52.
53.
54.
55.
56.
57.
58.
59.
60.
61.
62.
63.
64.
65.
66.
67.
68.
69.
70.
71.
72.
73.
74.
75.
76.
77.
78.
79.
80.
81.
82.
83.
84.
85.
86.
87.
88.
89.
90.
91.
92.
93.
94.
95.
96.
97.
98.
99.
100.
101.
102.
103.
104.
105.
106.
107.
108.
109.
110.
111.
112.
113.
114.
115.
116.
117.
118.
119.
120.
121.
122.
123.
124.
125.
126.
127.
128.
129.
130.
131.
132.
133.
134.
135.
136.
137.
138.
139.
140.
141.
142.
143.
144.
145.
146.
147.
148.
149.
150.
151.
152.
153.
154.
155.
156.
157.
158.
159.
160.
161.
162.
163.
164.
165.
166.
167.
168.
169.
170.
171.
172.
173.
174.
175.
176.
177.
178.
179.
180.
181.
182.
183.
184.
185.
186.
187.
188.
189.
190.
191.
192.
193.
194.
195.
196.
197.
198.
199.
200.
201.
202.
203.
204.
205.
206.
207.
208.
209.
210.
211.
212.
213.
214.
215.
216.
217.
218.
219.
220.
221.
222.
223.
224.
225.
226.
227.
228.
229.
230.
231.
232.
233.
234.
235.
236.
237.
238.
239.
240.
241.
242.
243.
244.
245.
246.
247.
248.
249.
250.
251.
252.
253.
254.
255.
256.
257.
258.
259.
260.
261.
262.
263.
264.
265.
266.
267.
268.
269.
270.
271.
272.
273.
274.
275.
276.
277.
278.
279.
280.
281.
282.
283.
284.
285.
286.
287.
288.
289.
290.
291.
292.
293.
294.
295.
296.
297.
298.
299.
300.
301.
302.
303.
304.
305.
306.
307.
308.
309.
310.
311.
312.
313.
314.
315.
316.
317.
318.
319.
320.
321.
322.
323.
324.
325.
326.
327.
328.
329.
330.
331.
332.
333.
334.
335.
336.
337.
338.
339.
340.
341.
342.
343.
344.
345.
346.
347.
348.
349.
350.
351.
352.
353.
354.
355.
356.
357.
358.
359.
360.
361.
362.
363.
364.
365.
366.
367.
368.
369.
370.
371.
372.
373.
374.
375.
376.
377.
378.
379.
380.
381.
382.
383.
384.
385.
386.
387.
388.
389.
390.
391.
392.
393.
394.
395.
396.
397.
398.
399.
400.
401.
402.
403.
404.
405.
406.
407.
408.
409.
410.
411.
412.
413.
414.
415.
416.
417.
418.
419.
420.
421.
422.
423.
424.
425.
426.
427.
428.
429.
430.
431.
432.
433.
434.
435.
436.
437.
438.
439.
440.
441.
442.
443.
444.
445.
446.
447.
448.
449.
450.
451.
452.
453.
454.
455.
456.
457.
458.
459.
460.
461.
462.
463.
464.
465.
466.
467.
468.
469.
470.
471.
472.
473.
474.
475.
476.
477.
478.
479.
480.
481.
482.
483.
484.
485.
486.
487.
488.
489.
490.
491.
492.
493.
494.
495.
496.
497.
498.
499.
500.
501.
502.
503.
504.
505.
506.
507.
508.
509.
510.
511.
512.
513.
514.
515.
516.
517.
518.
519.
520.
521.
522.
523.
524.
525.
526.
527.
528.
529.
530.
531.
532.
533.
534.
535.
536.
537.
538.
539.
540.
541.
542.
543.
544.
545.
546.
547.
548.
549.
550.
551.
552.
553.
554.
555.
556.
557.
558.
559.
560.
561.
562.
563.
564.
565.
566.
DEFINE CLASS posaverecord AS custom


	Height =  22 
	Width =  55 
	Name = "posaverecord"


	PROCEDURE readme
		TEXT NOSHOW  

		ВладимирМ

		Сам процесс сохранения лично я (в своей задаче) разбиваю на несколько этапов:

		Этап  0  - проверка самого факта внесения каких-либо изменений

		Этап  1  - проверка факта ввода обязательных реквизитов и проверка корректности ввода данных

		Этап  2  - формирование значений служебных полей, недоступных пользователю и заполнение прочих полей, недоступных пользователю

		Этап  3  - собственно запись в базу данных

		Этап  4  - обновление информации на формах и во временных таблицах

		Каждый этап оформляется как отдельная процедура или метод класса.

		ENDTEXT   
	ENDPROC


	*-- Проверка факта из менения записи в таблице
	PROCEDURE ischange
		*SET STEP ON 
		* IsCHANGE
		*
		* Этап  0  - проверка самого факта внесения каких-либо изменений
		* Предполагается, что таблицы находятся в каком-либо режиме буферизации, т.е.
		* речь идет об изменении буфера
		*----------------------------*
		* Возвращаемое значение llReturn
		* Возвращает .T. если были изменения записей  или .F. - если нет
		*----------------------------*
		* Параметры
		*tcAliasName - Список таблиц, через запятую, у которых следует проверить наличие изменений записи
		LPARAMETERS tcAliasName
		LOCAL llReturn
		m.llReturn=.F.

		*Если не передан параметр, то имеем ввиду, что брать текущий алиас
		IF PCOUNT()< 1 
		m.tcAliasName=SELECT( 0 )
		ENDIF

		IF TYPE('m.tcAliasName ')<>'C'
		m.tcAliasName =''
		ENDIF

		*запомним текущий алиас
		 LOCAL lcCurrentAlias
		 lcCurrentAlias=SELECT( 0 )

		* Сначала делаю разбор параметра списка таблиц
		LOCAL lnI, laAliasName[ 1 ]
		IF EMPTY(m.tcAliasName )=.F. AND ALINES(laAliasName,ChrTran(m.tcAliasName ,",",chr( 13 )),.T.)> 0 
		      FOR lnI= 1  TO ALEN(laAliasName, 1 )
		          IF USED(laAliasName[m.lnI])=.T.
		             m.llModify=.NOT.EMPTY(CHRTRAN(GETFLDSTATE(- 1 ,laAliasName[m.lnI]),"1",""))
		          ENDIF
		          *Если встретилась хоть одна модификация, то выходим из цикла
		          IF m.llModify=.T.
		            m.llReturn=.T. 
		            EXIT
		          ENDIF
		      ENDFOR
		ENDIF

		*Восстановим ситуацию
		IF TYPE('m.lcCurrentAlias')=='N'
		   SELECT (m.lcCurrentAlias)
		ENDIF 

		RETURN m.llReturn
	ENDPROC


	*-- Сохранеие положения указателя записи
	PROCEDURE saverecno
		* SaveRECNO
		*
		* Сохранеие положения указателя записи
		* в массиве-свойстве объекта
		*----------------------------*
		* Параметры
		*tcAliasName - Список таблиц, через запятую, у которых следует запомнить указатель записи
		LPARAMETERS tcAliasName
		*SET STEP ON 
		*Если не передан параметр, то имеем ввиду, что брать текущий алиас
		IF PCOUNT()< 1 
		m.tcAliasName=ALIAS()
		ENDIF

		IF TYPE('m.tcAliasName ')<>'C'
		m.tcAliasName =''
		ENDIF

		*запомним текущий алиас
		 LOCAL lcCurrentAlias
		 lcCurrentAlias=SELECT( 0 )

		THIS.AddProperty('aAliasName[1]')

		  

		* Сначала делаю разбор параметра списка таблиц
		LOCAL lnI,preaAliasName[ 1 ]
		IF EMPTY(m.tcAliasName )=.F. AND ALINES(preaAliasName,ChrTran(m.tcAliasName ,",",chr( 13 )),.T.)> 0 
		   DIMENSION  this.aAliasName[ALEN(preaAliasName, 1 ), 2 ]
		      FOR lnI= 1  TO ALEN(this.aAliasName, 1 )
		        this.aAliasName[m.lnI, 1 ]=preaAliasName[m.lnI]
		          IF USED(this.aAliasName[m.lnI])=.T.
		             *сохранение положения записи   
		             IF EOF(this.aAliasName[m.lnI, 1 ])=.F.
		                this.aAliasName[m.lnI, 2 ]=RECNO(this.aAliasName[m.lnI, 1 ])
		             ELSE
		                this.aAliasName[m.lnI, 2 ]= 0 
		             ENDIF      
		          ENDIF
		      ENDFOR
		ENDIF


		*Восстановим ситуацию
		IF TYPE('m.lcCurrentAlias')=='N'
		 SELECT (m.lcCurrentAlias)
		ENDIF 
	ENDPROC


	*-- Перемещает указатель записи на запомненное в массиве
	PROCEDURE gorecno
		* GoRECNO
		* Восстановление положения указателя записи
		* номер записи в массиве-свойстве объекта
		*----------------------------*
		* Параметры
		*tcAliasName - Список таблиц, через запятую, у которых следует восстановить указатель записи
		LPARAMETERS tcAliasName
		*SET STEP ON 
		*Если не передан параметр, то имеем ввиду, что брать текущий алиас
		IF PCOUNT()< 1 
		m.tcAliasName=ALIAS()
		ENDIF

		IF TYPE('m.tcAliasName')<>'C'
		m.tcAliasName =''
		ENDIF

		*запомним текущий алиас
		 LOCAL lcCurrentAlias
		 lcCurrentAlias=SELECT( 0 )

		IF PEMSTATUS(THIS,'aAliasName', 5 )
			* Ищем в списке таблиц
			LOCAL lnI
			  FOR lnI= 1  TO ALEN(this.aAliasName, 1 )
			      IF USED(this.aAliasName[m.lnI, 1 ])=.T. AND ;
			         ATC(this.aAliasName[m.lnI, 1 ],m.tcAliasName)> 0 
			             *Если есть запись с таким номером, то на нее и встанет, если нет , то попадем в конец файла.
			             LOCATE FOR RECNO(this.aAliasName[m.lnI, 1 ])=this.aAliasName[m.lnI, 2 ]
			      ENDIF
			  ENDFOR
		ENDIF


		*Восстановим ситуацию
		IF TYPE('m.lcCurrentAlias')=='N'
		 SELECT (m.lcCurrentAlias)
		ENDIF 

		      
	ENDPROC


	PROCEDURE messageerror
		* MessageError
		*
		* В качестве возвращаемого значения формируется сообщение об ошибке
		LOCAL lcMessageText, laError( 1 , 7 )
		EXTERNAL ARRAY gaErrors

		DO CASE
		   CASE AERROR(laError)= 0 
		   ********************************
		   * т.е. нет ошибок
		   ********************************
		   lcMessageText = ""

		   CASE laError[ 1 , 1 ]= 1526  
		   ********************************
		   * Ошибки ODBC
		   ********************************
			 DO CASE
				CASE laError[ 1 , 5 ]= 6 
				lcMessageText = 'Не удалось найти нужный сервер SQL'+chr( 13 )+;
				'Возможно, связь по сети не установлена.'
				CASE laError[ 1 , 5 ]= 229  
				lcMessageText = 'Вы вошли по паролю пользователя, который не обладает'+chr( 13 )+;
				'достаточными правами для выполнения требуемых действий'+chr( 13 )+;
				'с одним из объектов базы данных'+chr( 13 )+;
				RTRIM(SubStr(laError[ 1 , 3 ],RAT("]",laError[ 1 , 3 ])+ 1 ))+chr( 13 )+;
				'Обратитесь к администратору сервера или разработчику программы.'
				CASE laError[ 1 , 5 ]= 515  
				lcMessageText = 'Указано недопустимое значение'
				CASE laError[ 1 , 5 ]= 547  
				lcMessageText = 'Нарушение ссылочной целостности'+chr( 13 )+;
				'Нельзя удалить элемент на который ссылаются из других мест,'+chr( 13 )+;
				'либо Вы ссылаетесь на элемент которого уже не существует'
				CASE laError[ 1 , 5 ]= 1000 
				lcMessageText = 'Возможно, произошел разрыв соединения'
				CASE laError[ 1 , 5 ]= 2627  
				lcMessageText = 'Элемент с такими же ключевыми значениями уже существует' + chr( 13 )+;
				'Измените введенные значения'
				CASE laError[ 1 , 5 ]= 18456 
				lcMessageText = 'При установке соединения указано неверное имя пользователя или пароль'
				CASE laError[ 1 , 5 ]>= 50000 
				lcMessageText = 'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 5 ]))+chr( 13 )+;
				RTRIM(SubStr(laError[ 1 , 3 ],RAT("]",laError[ 1 , 3 ])+ 1 ))
			 OTHERWISE
				lcMessageText = 'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 5 ]))+chr( 13 )+;
				RTRIM(SubStr(laError[ 1 , 3 ],RAT("]",laError[ 1 , 3 ])+ 1 ))+chr( 13 )+;
				'Обратитесь к администратору сервера или разработчику программы.'
			 ENDCASE

		  CASE InList(laError[ 1 , 1 ], 1427 , 1429 )=.T.
		    ********************************
		    * Ошибки OLE
		     ********************************
				lcMessageText = 'Произошла ошибка в '+LTRIM(STR(laError[ 1 , 4 ]))+chr( 13 )+ALLTRIM(laError[ 1 , 3 ])
				IF IsNull(laError[ 1 , 5 ])=.F.
				  lcMessageText = lcMessageText + chr( 13 )+;
				  'подробнее о ней можно прочитать в'+chr( 13 )+ALLTRIM(laError[ 1 , 5 ])
				ENDIF

			********************************
			* Далее ошибки собственно VFP
			********************************
			CASE laError[ 1 , 1 ]= 108 
			lcMessageText = 'Произошла ошибка № 108'+chr( 13 )+;
			'Таблица используется другим пользователем.'+chr( 13 )+;
			'Никакие изменения в ней невозможны'
			CASE laError[ 1 , 1 ]= 109 
			lcMessageText = 'Произошла ошибка № 109'+chr( 13 )+;
			'Запись используется другим пользователем.'+chr( 13 )+;
			'Никакие изменения в ней невозможны'
			CASE laError[ 1 , 1 ]= 130 
			lcMessageText = 'Произошла ошибка № 130'+chr( 13 )+;
			'Одна из записей используется другим пользователем.'+chr( 13 )+;
			'Никакие изменения в ней невозможны'
			CASE laError[ 1 , 1 ]= 1539 
				* триггер вернул .F.
				LOCAL lcDopText
				lcDopText=''
				IF TYPE('gaErrors[1,2]')='C'
				lcDopText=chr( 13 )+gaErrors[ 1 , 2 ]
				ENDIF
				IF TYPE('gaErrors[1,1]')<>'N' OR gaErrors[ 1 , 1 ]=- 1 
				    lcMessageText = 'Нарушение ссылочной целостности.'
				  DO CASE
					CASE laError[ 1 , 5 ]= 1 && Insert trigger failed
					lcMessageText=m.lcMessageText+chr( 13 )+;
					'При создании документа, Вы сослались на не существующий документ'+;
					m.lcDopText
					CASE laError[ 1 , 5 ]= 2 && Update trigger failed
					lcMessageText=m.lcMessageText+chr( 13 )+;
					'При изменении документа, Вы сослались на не существующий документ'+;
					m.lcDopText
					CASE laError[ 1 , 5 ]= 3 && Delete trigger failed
					lcMessageText=m.lcMessageText+chr( 13 )+;
					'Вы удаляете документ, на который ссылаются из других документов'+;
					m.lcDopText
				  OTHERWISE&& На всякий случай.
				  ENDCASE
		        ELSE
		          lcMessageText = 'Произошла ошибка № '+LTRIM(STR(gaErrors[ 1 , 1 ]))+m.lcDopText
		        ENDIF
			CASE laError[ 1 , 1 ]= 1884 
			* Нарушение уникальности индекса типа Primary или Candidat
			lcMessageText = 'Элемент с такими же ключевыми значениями уже существует'+chr( 13 )+;
			'Измените введенные значения'+chr( 13 )+;
			'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 1 ]))+chr( 13 )+ALLTRIM(laError[ 1 , 2 ])
		OTHERWISE
		    lcMessageText = 'Произошла ошибка № '+LTRIM(STR(laError[ 1 , 1 ]))+chr( 13 )+ALLTRIM(laError[ 1 , 2 ])
		ENDCASE

		RETURN (m.lcMessageText)
	ENDPROC


	PROCEDURE savebuffer
		* SAVEBUFFER
		*
		* Сброс буфера в исходные таблицы
		* Предполагается, что таблицы находятся в каком-либо режиме буферизации, т.е.
		* речь идет об изменении буфера
		*----------------------------*
		* Возвращаемое значение
		* Возвращает .T. если сброс прошел успешно или .F. - если нет
		*----------------------------*
		* Параметры
		*!* tcTableBuffer - Список таблиц, через запятую, у которых следует сбросить буфер всей таблицы
		*!* tlTransaction - Следует ли окружить сброс буфера транзакцией? Дефолт- следует .T.
		*!* tlOverWrite - Следует ли писать записи "поверх измененных" Дефолт-не следует .F.
		LPARAMETERS tcTableBuffer, tlTransaction,tlOverWrite
		*
		IF TYPE('tcTableBuffer')<>'C'
		   tcTableBuffer=''
		ENDIF
		IF TYPE('tlTransaction')<>'L' OR PCOUNT()< 2 
		   tlTransaction=.T.
		ENDIF
		IF TYPE('tlOverWrite ')<>'L' OR PCOUNT()< 2 
		   tlOverWrite=.F.
		ENDIF
		*
		LOCAL llSuccess
		llSuccess=.T.

		IF m.tlTransaction=.T.
		   BEGIN TRANSACTION
		ENDIF


		* Теперь разбираю сброс буфера таблицы
		LOCAL laTableBuffer( 1 )
		IF m.llSuccess=.T. AND EMPTY(m.tcTableBuffer)=.F. ;
		   AND ALINES(laTableBuffer,ChrTran(m.tcTableBuffer,",",chr( 13 )),.T.)> 0 
		     FOR lnI= 1  TO ALEN(laTableBuffer)
		         IF USED(laTableBuffer[m.lnI])=.T. AND CursorGetProp("Buffering",laTableBuffer[m.lnI])> 3 
		            llSuccess=TableUpdate(.T.,tlOverWrite,laTableBuffer[m.lnI])
		         ENDIF
		         IF m.llSuccess=.F.
		            EXIT
		         ENDIF
		     ENDFOR
		ENDIF

		IF m.tlTransaction=.T.
		   IF m.llSuccess=.T.
		      END TRANSACTION
		   ELSE
		      ROLLBACK
		        FOR lnI= 1  TO ALEN(laTableBuffer) 
		           TABLEREVERT(.T.,laTableBuffer[m.lnI]) &&если необходимо
		        ENDFOR   
		   ENDIF
		ENDIF

		RETURN m.llSuccess
	ENDPROC


	*-- Сохранение записей  в источниках
	PROCEDURE saverecord
		*SaveRecord-сохранение записи
		* Параметры
		*!* tcTableBuffer - Список таблиц, через запятую, у которых следует сбросить буфер всей таблицы
		*!* tlTransaction - Следует ли окружить сброс буфера транзакцией? Дефолт- следует .T.
		*!* tlOverWrite - Следует ли писать записи "поверх измененных" Дефолт-не следует .F.
		LPARAMETERS tcTableBuffer, tlTransaction,tlOverWrite
		*SET STEP ON 

		IF TYPE('tcTableBuffer')<>'C'
		tcTableBuffer=''
		ENDIF
		IF TYPE('tlTransaction')<>'L' OR PCOUNT()< 3 
		tlTransaction=.T.
		ENDIF
		IF TYPE('tlOverWrite ')<>'L' OR PCOUNT()< 3 
		tlOverWrite=.F.
		ENDIF


		LOCAL lcMessageText, laError( 1 ), llOverWrite
		FOR lnI= 1  TO  2 
		tlOverWrite = (m.lnI= 2 )
		* По умолчанию, цикл выполняем только  1  раз
		lnI= 2 
		* Выполняем попытку сохранения
		IF this.SaveBuffer(tcTableBuffer, tlTransaction,tlOverWrite) = .F.
		    * Сначала определяю номер ошибки
		    =AERROR(laError)
		    * Теперь формирую сообщение об ошибке
		    lcMessageText = this.MessageError()
		    * Если ошибка - это модификация другим пользователем,
		    * то дополнительный запрос
		    IF laError[ 1 , 1 ]= 1585 
		       lcMessageText ='Кто-то уже обновил эти данные.'++chr( 13 )+;
		                       m.lcMessageText +chr( 13 )+;
		                       +chr( 13 )+;
		                      'Писать поверх этих изменений?'
		       IF MessageBox(m.lcMessageText, 4 + 32 + 256 ,'Конфликт обновления')= 6 
		         * Пошли на второй заход цикла
		          m.lnI= 1 
		       ENDIF
		    ELSE
		      MessageBox(m.lcMessageText, 16 ,"Ошибка обновления")
		    ENDIF
		    
		ENDIF
		ENDFOR
	ENDPROC


	PROCEDURE delrecord
		*DelRecord
		*удаление записи в буферизированном источнике
		*Перед удалением сохранить указатель записи,
		*после на представлении вделать реквери, затем восстановить положение записи
		*----------------------------*
		* Параметры
		* tcAliasName - Список таблиц, через запятую, у которых следует удалить текущую запись
		* tlquestionDel - Следует ли спросить юзера об удалении записи
		* tlTransaction - Следует ли окружить сброс буфера транзакцией?
		* tlOverWrite - Следует ли писать записи "поверх измененных" Дефолт-не следует .F.
		LPARAMETERS tcAliasName , tlquestionDel ,tlTransaction ,tlOverWrite

		IF TYPE('tcAliasName')<>'C'
		  tcAliasName=''
		ENDIF

		IF TYPE('tlquestionDel')<>'L' OR PCOUNT()< 2 
		  tlquestionDel=.F.
		ENDIF


		IF TYPE('tlTransaction')<>'L' OR PCOUNT()< 3 
		  tlTransaction=.T.
		ENDIF

		IF TYPE('tlOverWrite ')<>'L' OR PCOUNT()< 3 
		  tlOverWrite=.F.
		ENDIF

		IF tlquestionDel=.T.
		   IF  MESSAGEBOX("Вы действительно хотите удалить запись/записи ?"+CHR( 13 )+;
		                  "                                        ", 1 + 16 + 256 ,"Удаление выбранной записи")= 2 
		       RETURN .F. 
		   ENDIF 
		ENDIF  


		*Удаление записи, проверяем если представление, по просто в нем таблереверт
		BEGIN TRANSACTION  

		LOCAL laAliasName ( 1 ),llReturnTransaction
		llReturnTransaction=.f. 
		IF EMPTY(m.tcAliasName )=.F. ;
		   AND ALINES(laAliasName ,ChrTran(m.tcAliasName ,",",chr( 13 )),.T.)> 0 
		     FOR lnI= 1  TO ALEN(laAliasName)
		         IF USED(laAliasName[m.lnI])=.T. AND INLIST(CursorGetProp("SourceType",laAliasName[m.lnI]), 1 , 2 );
		            AND RECNO(laAliasName[m.lnI])< 0   
		             * Эта запись добавлена в представление и еще не сохранялась  
		             * Поэтому достаточно отказаться от нее в представлении  
		                TABLEREVERT(.f.,laAliasName[m.lnI])  
		         ELSE 
		          *
		            IF USED(laAliasName[m.lnI])=.T. AND !BOF(laAliasName[m.lnI]).OR. !EOF(laAliasName[m.lnI])
		               DELETE IN (laAliasName[m.lnI])
		               llReturnTransaction=.T. 
		            ELSE    
		               llReturnTransaction=.F. 
		               EXIT 
		            ENDIF    
		         ENDIF
		    ENDFOR
		ENDIF

		IF m.llReturnTransaction=.T.
		   END TRANSACTION
		ELSE
		   ROLLBACK
		   * Теперь обработка ошибки, но ПОСЛЕ ЗАКРЫТИЯ ТРАНЗАКЦИИ  
		   AERROR(laError0)
		   MESSAGEBOX("При попытке удаления записей произшла ошибка № "+TRANSFORM(laError0[ 1 , 1 ])+CHR( 13 )+;
		              "Обратитесь к администратору или разработчику"+CHR( 13 )+;
		              "Сохранение невозможно", 0 + 16 + 0 ,'Ошибка удаления!!!') 
		ENDIF


		LOCAL lcMessageText, laError( 1 ), llOverWrite
		FOR lnI= 1  TO  2 
		tlOverWrite = (m.lnI= 2 )
		* По умолчанию, цикл выполняем только  1  раз
		lnI= 2 
		* Выполняем попытку сохранения
		IF this.SaveBuffer(tcAliasName,tlTransaction,tlOverWrite) = .F.
		    * Сначала определяю номер ошибки
		    =AERROR(laError)
		    * Теперь формирую сообщение об ошибке
		    lcMessageText = this.MessageError()
		    * Если ошибка - это модификация другим пользователем,
		    * то дополнительный запрос
		    IF laError[ 1 , 1 ]= 1585 
		       lcMessageText ='Кто-то уже обновил эти данные.'++chr( 13 )+;
		                       m.lcMessageText +chr( 13 )+;
		                       +chr( 13 )+;
		                      'Писать поверх этих изменений?'
		       IF MessageBox(m.lcMessageText, 4 + 32 + 256 ,'Конфликт обновления')= 6 
		         * Пошли на второй заход цикла
		          m.lnI= 1 
		       ENDIF
		    ELSE
		      MessageBox(m.lcMessageText, 16 ,"Ошибка обновления")
		    ENDIF
		    
		ENDIF
		ENDFOR
	ENDPROC


	*-- Добавление записи в группу таблиц
	PROCEDURE newrecord
		* GoRECNO
		* LOCATE for RECNO() = lnRecno Если есть запись с таким номером, то на нее и встанет, если нет , то попадем в конец файла.
		* Восстановление положения указателя записи
		* номера записи в массиве-свойстве объекта
		*----------------------------*
		* Параметры
		*tcAliasName - Список таблиц, через запятую, в которые следует добавить новые записи
		LPARAMETERS tcAliasName
		*SET STEP ON 
		*Если не передан параметр, то имеем ввиду, что брать текущий алиас
		IF PCOUNT()< 1 
		m.tcAliasName=ALIAS()
		ENDIF

		IF TYPE('m.tcAliasName')<>'C'
		m.tcAliasName =''
		ENDIF

		*запомним текущий алиас
		 LOCAL lcCurrentAlias
		 lcCurrentAlias=SELECT( 0 )

		IF EMPTY(m.tcAliasName )=.F. ;
		   AND ALINES(laAliasName ,ChrTran(m.tcAliasName ,",",chr( 13 )),.T.)> 0 
		     FOR lnI= 1  TO ALEN(laAliasName)
		         IF USED(laAliasName[m.lnI])=.T.
		            APPEND BLANK IN (laAliasName[m.lnI])  
		         ENDIF 
		     ENDFOR 
		ENDIF      


		*Восстановим ситуацию
		IF TYPE('m.lcCurrentAlias')=='N'
		 SELECT (m.lcCurrentAlias)
		ENDIF 
	ENDPROC


ENDDEFINE
*
*-- EndDefine: posaverecord
**************************************************
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33431791
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Эх, красота-то какая!
"мать, мать, мать...", по привычке откликнулось эхо

Такие большие фрагменты кода лучше оформлять как файл, архивировать и класть во вложения. Собственно, даже мой фрагмент уже на "грани". Надо бы было тоже упаковать в архив.

Твоя проблема в том, что ты пытаешся усидеть одновременно на 2 стульях. Точнее, даже на многих. Т.е. используешь несколько разных стилей программирования.

С одной стороны, у тебя есть метод, формирующий сообщения об ошибке. Но с другой, ты формируешь это сообщение напрямую в коде. Т.е. как минимум, дублируешь код.

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

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

Но это все не столь важно. Самая главная ошибка:

Не надо пытаться писать универсальный код.

Понимаешь, в одиночку просто невозможно сразу охватить все возможные проблемы. Да это и не нужно. Очень многое зависит от конкретной постановки задачи.

Например, Игорь указал на ситуацию, когда мой код работать не будет. Но все дело в том, что в моей задаче такой ситуации просто не может возникнуть. Т.е. то или иное решение зависит от конкретной задачи.

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

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

Почему в классах потомках? Ну, потому, что ситуации могут быть самые разные. В одном случае код сохранения должен быть одним, а в другом, чуть-чуть иначе.

Разбиение на этапы (методы), как раз и служит для того, чтобы облегчить написание этого самого "чуть-чуть иначе". Т.е. чтобы не возникало необходимости копировать большие куски кода. Заранее оставляются некие "разрывы" в коде, куда и можно вписать свои отличия.

Т.е. код должен быть не столько универсальным, сколько легко модифицируемым в классах-потомках.

А метод, формирующий сообщение об ошибках вообще должен быть выделен в отдельный класс. Ошибки ведь могут возникать не только при сохранении. Зачем же "привязывать" этот метод именно к классу, занимающимся исключительно сохранением.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33431841
Igor Korolyov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Hi Владимир!

Что-то меня всё равно терзают смутные сомнения...
А если в процессе работы сначала удалить И изменить одну или более записей в
Detail (конечно разные записи - т.е. часть удалить, а часть изменить), а
затем удалить саму эту Detail - разве при сохранении не будет проблем?
Каскадный триггер то удалит записи из Detai ещё ДО того как пойдёт попытка
сохранить данные из курсора Detail - и значит сохранить Detail не удастся...

> Хотя в Detail допустимо и удалять и создавать в одном процессе.

Вот это как раз и наводит на мысли...

> В таблицах, где идет связь один-ко-многим, триггер на удаление всегда
> Cascade

А вот это очень жестоко. Связь такого типа не только в многострочных
документах бывает - она ещё и между справочником и использующими его
таблицами бывает - там каскадность IMHO недопустима. Или имеется в виду, что
только для многострочных документов такие правила? А не для всех связей 1-M

Posted via ActualForum NNTP Server 1.3
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33432225
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Игорь, ну не буду же я тут всю постановку задачи расписывать! Просто прими на веру, что у меня описанная ситуация исключена. Система достаточно жесткая и прямолинейная. Отсюда и такая простота кода.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33433039
~Иван~
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Гость
автор
Твоя проблема в том, что ты пытаешся усидеть одновременно на 2 стульях. Точнее, даже на многих. Т.е. используешь несколько разных стилей программирования.

С одной стороны, у тебя есть метод, формирующий сообщения об ошибке. Но с другой, ты формируешь это сообщение напрямую в коде. Т.е. как минимум, дублируешь код.


Виноват, исправлюсь.
Но... Владимир, Вы и сами в рекомендациях обработки ошибок
рекомендовали мне следующее
автор2) Собственно сохранение получается примерно так:
LOCAL lcMessageText, laError(1), llOverWrite
FOR lnI=1 TO 2
llOverWrite = (m.lnI=2)
* По умолчанию, цикл выполняем только 1 раз
lnI=2
* Выполняем попытку сохранения
IF Save_Buffer("Table1,Table2","Table3,Table4",.T.,m.llOverWrite) = .F.
* Сначала определяю номер ошибки
=AERROR(laError)
* Теперь формирую сообщение об ошибке
lcMessageText = MessageError()
* Если ошибка - это модификация другим пользователем,
* то дополнительный запрос
IF laError[1,1]=1585
lcMessageText = m.lcMessageText +chr(13)+;
'Писать поверх?'
IF MessageBox(m.lcMessageText,4+32+256)=6
* Пошли на второй заход цикла
m.lnI=1
ENDIF
ELSE
MessageBox(m.lcMessage)
ENDIF
ENDIF
ENDFOR

То есть тоже наблюдается "смешение", дублирование кода
обработки ошибок.
Это не препирательства :-) , а попытка
научиться "писать как положено"

Далее, как я уже гворил, аботаю с представлениями в 5 буферизации,
модифицировал код метода saveBuffer следующим отразом
авторIF m.tlTransaction=.T.
IF m.llSuccess=.T.
END TRANSACTION
ELSE
ROLLBACK
FOR lnI=1 TO ALEN(laTableBuffer)
*IF RECNO(laTableBuffer[m.lnI])<-1
TABLEREVERT(.T.,laTableBuffer[m.lnI]) &&åñëè íåîáõîäèìî
*ENDIF
ENDFOR
ENDIF
ENDIF

то есть добавил откат изменений TABLEREVERT
потому что возникает 1545 ошибка при реквери,
но откат изменений во вьюхе при попытке сохранения

Код: plaintext
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
LOCAL lcMessageText, laError( 1 ), llOverWrite
FOR lnI= 1  TO  2 
llOverWrite = (m.lnI= 2 )
* По умолчанию, цикл выполняем только  1  раз
lnI= 2 
* Выполняем попытку сохранения
IF Save_Buffer("Table1,Table2","Table3,Table4",.T.,m.llOverWrite) = .F.
* Сначала определяю номер ошибки
=AERROR(laError)
* Теперь формирую сообщение об ошибке
lcMessageText = MessageError()
* Если ошибка - это модификация другим пользователем,
* то дополнительный запрос
IF laError[ 1 , 1 ]= 1585 
lcMessageText = m.lcMessageText +chr( 13 )+;
'Писать поверх?'
IF MessageBox(m.lcMessageText, 4 + 32 + 256 )= 6 
* Пошли на второй заход цикла
m.lnI= 1 
ENDIF
ELSE
MessageBox(m.lcMessage)
ENDIF
ENDIF
ENDFOR

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

Как же меня интересует вопрос, следующего содержания:
например сделали выборку-работаем, изменяем запись,
сбрасываем буфер, а другой юзер в сети уже удалил эту запись
возникает ошибка 1545, и соответствеено запись поверх
к чему приведет???? %-)
вообщем как эту ситуацию разрулить???
вообщем помогите победить у себя наследие самоучного
2.5 фокса
спасибо
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33436885
Фотография ВладимирМ
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Как мне кажется, твоя проблема в том, что ты слишком "зацикливаешся" на конкретной задаче. Т.е., несмотря на заявление о том, что "я хочу написать универсальный код", ты по прежнему, по сути, пишешь код для какой-то конкретной формы.

Возьмем первый фрагмент, где ты модифицировал Save_Buffer.

Зачем ты в этом коде делаешь откат по TableRevert()? Точнее, почему именно в этом методе?

Цель метода Save_Buffer() - это выдать последовательно ряд команд TableUpdate(), по необходимости, окружив их транзакцией. ВСЕ! На этом его задача заканчивается. Он только сообщает удалось это сделать или нет.

Если произошла неудача, то у тебя, как минимум, 2 сценария дальнейших действий:

1) Ошибка сохранения связана с некорректной модификацией или ошибка 1585.

В этом случае откатывать ничего не надо. Пользователь либо должен скоррекировать свою модификацию, либо запустить тот же метод Save_Buffer() с другим параметром.

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

Т.е., в первом сценарии окат вообще не нужен, а во-втором - не обязателен.

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

Я же уже приводил общую логику кода сохранения:

Код: plaintext
1.
2.
3.
4.
IF (НЕ удалось сбросить буфер)
	(Выясняем причину неудачи и сообщаем пользователю)
	(Возможный откат изменений выполненных на "Этапе 2")
ENDIF

Обрати внимание, "возможный откат" вынесен во вне попытки сброса буфера. Т.е. это 2 разных метода.

Вообще-то, мне просто непонятно, откуда у тебя появлась необходимость обновлять Local View в процессе сохранения данных ? Необходимо либо успешно сохранить все, либо откатить все. Причем это 2 разных процесса.


Теперь, с сообщениями об ошибке.

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

Почитай вот это

http://www.foxhelp.ru/OshibkiObrabotkaVFP56DugJEnnig?v=6s0

Это первод стать Дуга Хеннинга о способах обработки ошибко в VFP6.


А насчет, писать "поверх удаленной записи" есть несколько стратегий:

1) Если действительно "писать поверх", то запись будет восстановлена. Т.е. метка на удаление будет снята. Но! Это означает срабатывание триггера на вставку на данную запись.

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

2) Как правило, используют различные организационные способы "развести" пользователей так, чтобы вероятность редактирования одной и той же записи разными пользователями свести к минимуму.

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

В моих задачах подобная ситуация крайне мало-вероятна, а последствия - незначительны. Т.е. просто нет смысла как-то специально это обрабатывать. Если у тебя, в твоих задачах это не так, то при возникновении ошибки 1585 сделай анализ

Код: plaintext
CurVal("Deleted()")

Эта функция "читает" данные напрямую с диска, а не из буфера. Т.е. по ее значению можно понять, была ли запись удалена и выдать соответствующее сообщение.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33436956
Igor Korolyov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Hi Владимир!

> В этом случае откатывать ничего не надо. Пользователь либо должен
> скоррекировать свою модификацию, либо запустить тот же метод Save_Buffer()
> с другим параметром.

Вот чего чего, а редактировать буфер между 2-мя попытками его сброса совсем
не стоит. Обсуждалась возникающая при этом ситуация "порчи" индекса этого
курсора (к счастью только в памяти).

> 1) Если действительно "писать поверх", то запись будет восстановлена.

Это возможно исключительно при работе с таблицей непосредственно - ни
локальные ни тем более удалённые представления такой "странной"
функциональности не обеспечивают - там подобный конфликт "перебить" можно
только заменив команду обновления с UPDATE на INSERT.

> CurVal("Deleted()")

Опять-же это полезно только при прямой работе с самой таблицей (и её
буфером) - для случая представления/SPT запроса это неприменимо - там нужно
отдельным запросом извлекать информацию о том. а что-же реально мы имеем в
таблице на момент попытки сохранения...

P.S. А напрямую с таблицами (пусть и буферизованными) я стараюсь не работать
в интерфейсе.

Posted via ActualForum NNTP Server 1.3
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33437216
PaulWist
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Igor KorolyovОпять-же это полезно только при прямой работе с самой таблицей (и её
буфером) - для случая представления/SPT запроса это неприменимо - там нужно
отдельным запросом извлекать информацию о том. а что-же реально мы имеем в
таблице на момент попытки сохранения
...

Игорь.

Извлекать информацию куда? - на клиента - это тоже неправильный подход, ну извлекли, определили, что запись не удалена и что - это совсем не значит, что в момент команды UPDATE в таблице будет находится эта запись. Можно конечно попытаться использовать клиентскую транзакцию, НО - это тоже не выход.

На мой взгляд, общее решение - это вызов ХП сервера, где надо производить разбор "что е, что нема", а клиенту вернуть только ошибку.
...
Рейтинг: 0 / 0
Владимиру Максимову, вопрос
    #33439563
Igor Korolyov
Скрыть профиль Поместить в игнор-лист Сообщения автора в теме
Участник
Hi PaulWist!

В общем случае - наверное так... Просто весьма утомительно руками писать
подобные процедуры, а приемлемый генератор - поди отыщи (а свой писать тоже
как-то ломает - тем более что сложно учесть все тонкости). Ну и врождённая
проблема ХП - крайне сложно через неё менять ТОЛЬКО изменённые на клиенте
поля - т.е. либо мы пихаем все поля (несмотря на то что на клиенте поменяли
всего одно), либо удваиваем число парметров, строим внутри ХП нетривиальную
логику (по сути создаём динамически команду обновления и исполняем её - это
к тому-же наверняка не всякий SQL сервер позволит)...

Posted via ActualForum NNTP Server 1.3
...
Рейтинг: 0 / 0
18 сообщений из 18, страница 1 из 1
Форумы / FoxPro, Visual FoxPro [игнор отключен] [закрыт для гостей] / Владимиру Максимову, вопрос
Найденые пользователи ...
Разблокировать пользователей ...
Читали форум (0):
Пользователи онлайн (0):
x
x
Закрыть


Просмотр
0 / 0
Close
Debug Console [Select Text]