Главная страница
    Top.Mail.Ru    Яндекс.Метрика
Форум: "Потрепаться";
Текущий архив: 2004.09.26;
Скачать: [xml.tar.bz2];

Вниз

Ошибки БД и клиентское ПО   Найти похожие ветки 

 
KSergey ©   (2004-09-07 07:00) [0]

В свете ветки о построении БД-ориентированных приожений хочу задать такой вопрос: кто как организует "перевод" сообщений сервера БД на понятный человеку язык? Как именно применительно к дельфи приложению?
Например, нарушение ссылочной целостности, уникальности ключа и т.д.?

Я вижу 2 пути
1) проверять заранее перед непосредственно записью в БД
2) пытаться "распознавать" сообщения сервера и как-то "трансформировать" их к удобочитаемому виду.

По п.1:
+большая гибкость
-лишняя нагрузка на БД, а так же дублирование структуры БД в разных местах (уникальный ключ+проверка на уникальность в коде записи; что-то меняем - править 2 места)

По п.2:
+контроль целостности БД в одном централизованном месте, на сервере
-трудности перевода, не факт, что "перевод" будет всегда адекватен действительной проблеме
-некоторая сложнось с "вживлением" кода "перевода", не всегда собственно запись "контролируема" (в случае DB-aware компонент), значит перехват на уровне приложения, а это еще и проблемы с определением контекста (однозначости, нюансов "перевода")

Поделитесь, кому не жаль, кто что использует, какие проблемы имеются, как решаются.


 
0d08h   (2004-09-07 07:11) [1]

Так обработка сообщений лежит полностью на программисте. Ты сам должен пользователю дать сообщение что что то не так.
Например:

procedure TForm1.Make(query:string);
begin
 query1.close();
 query1.sql.add(query);
 try
   query1.open();
 except
  on ESomeError do ShowMessage("База упала блин");
 end;
end;

ну или так

procedure TForm1.Make(query:string);
var
errorMessage:string;
begin
 query1.close();
 query1.sql.add(query);
 try
   query1.open();
 except
  on ESomeError do
     Query1.close();
     Query1.sql.add("Select message from error_messages where id = "ESomeError"");
     Query1.open();
     errorMessage := GetMessage(Query1); // <--- берет сообщение об ошибке  
      ShowMessage(errorMessage);
 end;
end;

ps:
Ахтунг псевдо код!


 
KSergey ©   (2004-09-07 07:50) [2]

> [1] 0d08h   (07.09.04 07:11)
>   on ESomeError do ShowMessage("База упала блин");

Ну, это не более информативно, чем какое-нибудь "Error in text following query statement"

> [1] 0d08h   (07.09.04 07:11)
>  except
>   on ESomeError do
>      errorMessage := GetMessage(Query1); // <--- берет сообщение

А в ADO, например, все исключения одного класса. Т.е. для детализации "перевода" надо фактически парсить текст. Или есть другие предложения? (или использовать код ошибки? мысль, надо поискать, это надежнее, чем парсить текст).


 
Dok_3D ©   (2004-09-07 08:00) [3]

Не иди по второму пути. Проблемы этого пути ты сам описал, и преодолеть их ты наврядли сумеешь. Погрязнешь в нюансах.
Аккуратненько, по первому пути. И красивее, и гибче. Конечно, скорострельность может упасть, но при хорошем мастерстве этого бояться не стоит.


 
KSergey ©   (2004-09-07 08:05) [4]

> [3] Dok_3D ©   (07.09.04 08:00)
> Аккуратненько, по первому пути. И красивее, и гибче.

Фиг бы еще с этой скорострельностью.
Но вот "а так же дублирование структуры БД в разных местах" мне оочень не нравится, считаю это огромым минусом. Причем принципиально непреодолимым. Единственный путь его преодоления - собирание логики обеспечения целостности на сервере в ХП. Это как бы некий компромис между централизованностью и гибкостью, но мне сей путь меньше нравится - нет защиты от дурака (пусть и с административными правами), либо жуткие палки в колеса при необходимости, пусть и редкой, "нестандартной" администраторской правки информации прямо в таблицах БД...


 
0d08h   (2004-09-07 08:08) [5]

KSergey
А если SQL процедуры?
То есть ошибка такая то случилась запускаем процедуру та правит на какой то нормальный вариант?


 
HSolo ©   (2004-09-07 08:55) [6]

Могут выручить пользовательские исключения (если СУБД такое умеет): и проверка на сервере, и переводить не надо :)


 
Sergey_Masloff   (2004-09-07 09:49) [7]

Не вижу вообще никакой проблемы.
Конечно все проверки в базе. Везде где можно (в ХП) - пользовательские исключения.

На клиенте обработка следующая: Если сообщение пользовательское то исходное тоже в нем содержится помеченое флажком. Процедура обработчик умеет поделить сообщение на две части - "Свою" (понятную) и "неопознаную" (серверную). В диалоге сообщения об ошибке показывается "Понятное" сообщение и есть кнопка "Подробно"
которая показывает "серверное" сообщение. Та же процедура обработки при установке соотв. флажка пишет все в лог ошибок.

Да в диалоге у меня есть кнопка которая текст ошибки отправляет по email в службу поддержки.

P/S если "понятной" части не обнаружено естественно вместо нее показывается стандартное - например "внутренняя ошибка сервера" но это я думаю понятно и без комментариев


 
Sergey13 ©   (2004-09-07 10:05) [8]

Я никогда не писал "коробочных" продуктов, там наверное стОит это прорабатывать более детально.
Обычно я стараюсь не допускать "в принципе" таких ошибок. 8-)
Уникальность ключа - а нефиг отдавать это на волю юзера - только из последовательности или програмно.
Ссылочная целостность - проверь перед постом заполненость этого поля (не значение проверять на корректность, а есть ли оно вообще). А заполнять только выбором, а не ручным вводом.
Удаление родительских записей - так в соответствующем блоке программы и отловить исключение. Вряд ли при удалении получишь неуникальность ключа.


 
Mystic ©   (2004-09-07 11:13) [9]

Обычно я стараюсь не допускать "в принципе" таких ошибок. 8-)

Не всегда проходит.

Уникальность ключа - а нефиг отдавать это на волю юзера - только из последовательности или програмно.

Есть просто уникальные индексы. Например, Tax Code сотрудника, номер паспорта, ...

Удаление родительских записей - так в соответствующем блоке программы и отловить исключение. Вряд ли при удалении получишь неуникальность ключа.

1) Надо сказать из-за чего нельзя удалить запись.
2) Могут нарушаться сложные выражения CONSTRAINT CHECK. Дублировать оные не хочеться
3) Может произойти исключение при работе связаного триггера
4) В конечном счете может быть внутрення ошибка БД, связаная с ее настройкой (Snapshot too old)
5) Просто нехорошо ловить все ошибки и выводить на них одно сообщение --- устанешь искть причину. Например, выводит сообщение "Не могу удалить запись, нарушение ссылочной целостности", а на самом деле это TRANSACTION DEADLOCK.
6) Прогое перечисленое относится и к UPDATE.

..........


 
Sergey13 ©   (2004-09-07 11:22) [10]

2[9] Mystic ©   (07.09.04 11:13)
Повторюсь. Такие подробности скорее важны для "коробочных" продуктов. Если юзер может просто позвонить программеру по внутреннему телефону и коряво прочитать что у него "выскочило" - то в 99% программисту и так будет понятно что случилось. А узеру что "TRANSACTION DEADLOCK" что "Snapshot too old" все едино.


 
Sergey_Masloff   (2004-09-07 11:27) [11]

Sergey13 ©   (07.09.04 11:22) [10]
>Если юзер может просто позвонить программеру по внутреннему телефону
Может может. Только разница в часовых поясах у них такая что программист приходит на работу когда пользователь уже отбывает к жене и детям по причине окончившегося рабочего дня (самая обычная ситуация для нашего некоробочного продукта). И что толку если он позвонит программисту? И кому из 50-ти пограммистов?


 
KSergey ©   (2004-09-07 11:28) [12]

Спасибо sPrevExclWord Mystic"у [9], он описал верными словами как раз те ситуации, которые я подразумевал надеясь, что и так понятно.

> [5] 0d08h   (07.09.04 08:08)
> А если SQL процедуры?

1.Что это такое, я не понял термина?
2.Как это поможет в части определения что за ошибка.

Вообще меня итересовали в основном как раз те ситуации, о которых говорит [9] Mystic. Там, где это регулируется моей серверной процедурой/триггером - ясно, что я могу написть понятно. Что делать с тем же CONSTRAINT CHECK??

Жаль только, что Mystic, сотоль хорошо описав проблему, не поделился секретами мастерства... ;)


 
Sergey_Masloff   (2004-09-07 11:29) [13]

А про деадлок конечно говорить не стоит (я так и писал) а следует говорить внутренняя ошибка свжитесь со службой поддержки по тел. ХХХХ или нажмите кнопку отправить чтобы отправить сообщение в службу поддержки по почте.


 
KSergey ©   (2004-09-07 11:29) [14]

> [12] KSergey ©   (07.09.04 11:28)

sPrevExclWord - лишнее, звиняюсь ;)


 
Vlad ©   (2004-09-07 11:31) [15]

Не знаю как у кого, а моя практика показала, что перевод сообщений об ошибке не имеет никакого смысла.
Все равно в 99,9% случаев пользователь будет звонить программисту за разъяснениями.
А мне (как программеру) гораздо удобнее читать "родные" сообщения об ошибках, чем переведенные.


 
KSergey ©   (2004-09-07 11:33) [16]

> [13] Sergey_Masloff   (07.09.04 11:29)

Про "крутые" серверные проблемы - понятно. Считаем, что они не часты и, понятно, пользователь тут все равно бессилен.
Но вот что делать, напрмер, с уникальностью заполняемого человеком реквизита? Ведь это в его полномочиях либо внести еще раз внимательно либо согласиться, что оно уже есть, однако сервер пишет об этом "весьма тумманно" ;)


 
Sergey_Masloff   (2004-09-07 11:37) [17]

Mystic ©   (07.09.04 11:13) [9]
>1) Надо сказать из-за чего нельзя удалить запись.
Скажи это в триггере по русски

>2) Могут нарушаться сложные выражения CONSTRAINT CHECK. >Дублировать оные не хочеться
Не дублируй. Перехватывай исключение (на сервере) и говори что нарушил

>3) Может произойти исключение при работе связаного триггера
Ну и что? Это атомарная операция исключение точно так же ловится

>4) В конечном счете может быть внутрення ошибка БД, связаная с >ее настройкой (Snapshot too old)
Ну и что. Ошибка сервера в подробностях твой снапшот ту олд. Все равно ты его не переведешь так чтобы Марья Ивановна из бухгалтерии поняла

>5) Просто нехорошо ловить все ошибки и выводить на них одно >сообщение --- устанешь искть причину. Например, выводит >сообщение "Не могу удалить запись, нарушение ссылочной >целостности", а на самом деле это TRANSACTION DEADLOCK.
Не устанешь. Как я и предлагаю сообщение имеет 2 раздела - для пользователя и для тебя. Причем то что "для тебя" пользователь тоже может посмотреть если считает нужным

6) Прогое перечисленое относится и к UPDATE.
>Делай апдейт хранимой процедурой - оно и кошернее ;-)

Я так думаю


 
KSergey ©   (2004-09-07 11:43) [18]

> [17] Sergey_Masloff   (07.09.04 11:37)
> >1) Надо сказать из-за чего нельзя удалить запись.
> Скажи это в триггере по русски

Логика обеспечения целостности не обязательно лежит в триггере! Часто достаточно CONSTRAINT CHECK.

> Не дублируй. Перехватывай исключение (на сервере) и говори
> что нарушил

Про какой сервер тут речь, к стати?

> Делай апдейт хранимой процедурой

Какая разница? Это как с триггерами: не всегда в ХП обеспечение ссыл. цел.


 
Sergey_Masloff   (2004-09-07 11:50) [19]

KSergey ©   (07.09.04 11:43) [18]
>Логика обеспечения целостности не обязательно лежит в триггере!
Как проектировать
>Часто достаточно CONSTRAINT CHECK.
Который тоже триггер только системный. В чем его преимущество?

>> Не дублируй. Перехватывай исключение (на сервере) и говори
>> что нарушил
>Про какой сервер тут речь, к стати?
Про любой, видимо. Я использовал этот подход в проектах с Oracle и InterBase, точно знаю что это возможно в MS SQL и DB2.

>> Делай апдейт хранимой процедурой
>Какая разница? Это как с триггерами: не всегда в ХП обеспечение >ссыл. цел.
Сделай чтобы всегда. Кстати один из самых удобных подходов. Все же триггер на мой взгляд не место для бизнес-логики. Проставить default значения, проверить что дата не больше текущей  - это да, можно на триггер но логика - лучше в ХП. Делал (и видел) по-разному но вариант с ХП на мой взгляд самый гибкий.


 
KSergey ©   (2004-09-07 12:08) [20]

> [19] Sergey_Masloff   (07.09.04 11:50)
> >Часто достаточно CONSTRAINT CHECK.
> Который тоже триггер только системный. В чем его преимущество?

Нарисовал - и забыл ;) И работает. Наглядно.

> >> Не дублируй. Перехватывай исключение (на сервере) и говори
> >> что нарушил
> это возможно в MS SQL

Можно ключевые слова по теме?


 
Mystic ©   (2004-09-07 12:18) [21]

Жаль только, что Mystic, сотоль хорошо описав проблему, не поделился секретами мастерства... ;)

Частное решение для Oracle (путь №2): ошибка имеет формат ORA-######, который можно легко разпарсить и установить номер ошибки. Стандартные сообщения об ошибках (FOREIGN KEY) хрянят имя ключа или констреинта, который вызвал ошибку. Достаточно держать таблицу имя объекта --- сообщение об ошибке.

Все равно ты его не переведешь так чтобы Марья Ивановна из бухгалтерии поняла

Разные цели --- разные задачи. Я занимаюсь разработкой ПО, администрированием БД занимается сторона заказчика. Настроить базу, журналы, резервное копирование это их прямые обязаности.

Не устанешь. Как я и предлагаю сообщение имеет 2 раздела - для пользователя и для тебя. Причем то что "для тебя" пользователь тоже может посмотреть если считает нужным

Такое естественно давно реализовано.


 
Sergey_Masloff   (2004-09-07 12:32) [22]

KSergey ©   (07.09.04 12:08) [20]
> >> Не дублируй. Перехватывай исключение (на сервере) и говори
> >> что нарушил
> это возможно в MS SQL

>Можно ключевые слова по теме?
RAISERROR и справка по нему в TSQL Reference.


 
KSergey ©   (2004-09-07 13:04) [23]

> RAISERROR

Это когда я САМ генерю ошибки.
Вы говорили в контексте "Перехватывай исключение (на сервере) и говори  что нарушил"

Как RAISEERROR связан с перехватом на сервере ошибок, произошедших в системных триггерах, если угодно??


 
sniknik ©   (2004-09-07 13:04) [24]

сами себе проблему придумали...
у нас большая служба поддержки, много много клиентов. давно заметил, тот кто звонит тому пофигу на каком языке ошибка выдается, понятная она или нет, ее чаще всего вообще не читают (!!!). т.что. выдавайте стандартное
try
  ...
except
 on E: Exception do DoIfError(E.Message);
end;
причем в процедуре не только показать сообщение но и записать в лог (чаще всего это помогает, присылка лога), + можно еще E.ClassName записывать чтобы самим проще было разобратся...

а те кто пытается разобратся те и с английского переведут...


 
Sergey_Masloff   (2004-09-07 13:20) [25]

KSergey ©   (07.09.04 13:04) [23]
>> RAISERROR

>Это когда я САМ генерю ошибки.
>Вы говорили в контексте "Перехватывай исключение (на сервере) и >говори  что нарушил"

>Как RAISEERROR связан с перехватом на сервере ошибок, >произошедших в системных триггерах, если угодно??

Ну я не слишком силен в MS SQL но неужели там нет аналога?
MSG = "Error "||TO_CHAR(SQLCODE)||": "||SQLERRM;

По крайней мере уверен что реализовать это можно. SQLCODE точно в MSSQL есть, MESSAGE тоже значит достать можно даже если нет контекстной переменной типа оракловской SQLERRM


 
jack128 ©   (2004-09-07 13:52) [26]

sniknik ©   (07.09.04 13:04) [24]
а те кто пытается разобратся те и с английского переведут...

Крутые у тя пользователи, если они знаю, что такое FOREIGN KEY  и CONSTRAINT CHECK ;-)


 
sniknik ©   (2004-09-07 14:06) [27]

jack128 ©   (07.09.04 13:52) [26]
> Крутые у тя пользователи, если они знаю, что такое FOREIGN KEY  и CONSTRAINT CHECK ;-)
у меня таких ошибок нет (ну..., пока не отловили). и они не мои, компании.
а даже если и возникнет, то на то логи и нужны, чтобы разобратся и избавится от глюка.

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


 
Mystic ©   (2004-09-07 14:56) [28]

sniknik ©   (07.09.04 13:04) [24]

Насколько я понял, основная проблема поста состоит в том, что часть ошибок БД не являются ошибками с точки зрения пользователя.  в этом случае надо выводить пользователю понятное сообщение. Понятно также желание делать это централизовано а не оборачивать каждую операцию к БД хранимой процедурой, try-except блоком и т. д.


 
KSergey ©   (2004-09-07 15:45) [29]

> [28] Mystic ©   (07.09.04 14:56)
> Насколько я понял, основная проблема поста состоит в том,
> что часть ошибок БД не являются ошибками с точки зрения
> пользователя

Добавлю от себя: некоторые из них, однако, он может решить, если ему толково о них написать.


 
KSergey ©   (2004-09-07 15:47) [30]

> [25] Sergey_Masloff   (07.09.04 13:20)
> Ну я не слишком силен в MS SQL но неужели там нет аналога?
> MSG = "Error "||TO_CHAR(SQLCODE)||": "||SQLERRM;

Мы не понимаем друг дружку... Или я чего-то не знаю.
Куда такое (аналогичное функционально) выражние можно пихнуть и как оно сработает при нарушении CONSTRAINT CHECK в известных Вам серверах? Ну так, кратенько?

> [24] sniknik ©   (07.09.04 13:04)
> сами себе проблему придумали...
> try
>   ...
> except
>  on E: Exception do DoIfError(E.Message);
> end;

Мы не хотим понять друг друга...

Хорошо, рисую ситуацию подробно. Например:
DataSet, DB-aware компоненты.
Фактически даже Post возможно не вызывается явно в коде программы (он в VCL!, DB-навигатор)
Как тут быть?
Куда я try/except суну???

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


 
Sergey_Masloff   (2004-09-07 16:05) [31]

KSergey ©   (07.09.04 15:47) [30]
>Куда такое (аналогичное функционально) выражние можно пихнуть и >как оно сработает при нарушении CONSTRAINT CHECK
В процедуру на сервере осуществляющую изменение базы.
P/S У меня все действия с базой через процедуры не по этой причине. А то что обработку подобных ошибок удобно делать там это всего лишь следствие.

>Фактически даже Post возможно не вызывается явно в коде >программы
Ага. И событие OnPostError не происходит тоже? ;-)


 
sniknik ©   (2004-09-07 16:38) [32]

> Куда я try/except суну???

> Ага. И событие OnPostError не происходит тоже? ;-)

+ / вернее или, на Application.OnException, все что не закрыто try/except будет здесь. (для определения места глюка немного неудобно)

> Мы не хотим понять друг друга...
я честно пытаюсь... ;о))


 
Рамиль ©   (2004-09-07 17:07) [33]

под IB у меня, например, так:

 except
  on E: EIBError do
  begin
    case E.SQLCode of
      -530: ShowMessage("К анкете привязаны счета. Удаление невозможно!");
    else
      ShowMessage(E.Message);
    end;
    DM.dsViewNatural.CancelUpdates;
  end;
 end;


 
KSergey ©   (2004-09-07 17:14) [34]

> [31] Sergey_Masloff   (07.09.04 16:05)
> Ага. И событие OnPostError не происходит тоже? ;-)

На каждый DataSet один обработчик? ;) Нет уж... ;)

> [32] sniknik ©   (07.09.04 16:38)
> + / вернее или, на Application.OnException, все что не закрыто
> try/except будет здесь. (для определения места глюка немного
> неудобно
)

В чем вся и штука...
"Вы ввели значение, имеющееся в БД" А какого именно реквизита??

> я честно пытаюсь... ;о))

Признателен ;)


 
Sergey_Masloff   (2004-09-07 17:14) [35]

Рамиль ©   (07.09.04 17:07) [33]
А когда фразу эту нужно изменить ты перекомпилируешь программу и рассылаешь в 1500 мест где она стоит?
 Я серьезно. Подумай над такой перспективой. По крайней мере я в таком случае рассылаю скрипт который сама программа умеет загружать. Даже очень серьезные изменения это килобайт 50 которые любой оператор загружает нажатием одной кнопки и работает дальше с новой логикой.


 
KSergey ©   (2004-09-07 17:14) [36]

>  [33] Рамиль ©   (07.09.04 17:07)
> под IB у меня, например, так:

У вас есть явный вызов Post
Тут еще более-менее понятно


 
KSergey ©   (2004-09-07 17:16) [37]

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

Но [35] уже сказали.


 
Sergey_Masloff   (2004-09-07 17:18) [38]

KSergey ©   (07.09.04 17:14) [34]
>На каждый DataSet один обработчик? ;) Нет уж... ;)
Ну а как в твоем подходе?
Ну получай в единственном обработчике код ошибки и лазь за его текстом в базу каждый раз. Такой вариант тоже предлагали тебе ж.

А если ты думаешь решить все двумя строчками кода то не получится.

А у меня-то как раз обработчик один. Но ты упорно хочешь на свою уже реализованую схему новых бантиков. Вперед.


 
KSergey ©   (2004-09-07 17:26) [39]

> [38] Sergey_Masloff   (07.09.04 17:18)
> А у меня-то как раз обработчик один. Но ты упорно хочешь
> на свою уже реализованую схему новых бантиков. Вперед.

Ага ;)
Ну либо окончательно убедиться, что я не прав - и с чистой совестью в новом проекте применить иное... с иными, еще не веданными граблями ;)

> А если ты думаешь решить все двумя строчками кода то не
> получится.

Пусть тыща - но один раз! Вот наш девиз! ;)

А все же ссылочную целостност триггерами... Можно, конечно, но есть ведь готовые решения...


 
kaif ©   (2004-09-07 17:27) [40]

Давайте сначала договоримся не называть все ошибками, а различим "ошибки" и вполне прогнозируемые штатные исключительные ситуации. В большинстве случаев пользователю нужны осмысленные сообщения в случае именно штатной  исключительной ситуации ("документ с таким номером уже существует", "попытка удалить товар, используемый в документах", "попытка создания дубликата существующего контрагента" и т.п.). Я не считаю, что запись подобных "ошибок" в "лог" оправдана. А вот "обрыв соединения с сервером", "конфликт блокировок" (в оптимистической модели) и т.п. - это уже именно ошибки и здесь не так важно, понятны они пользователю или не понятны, так как: 1) они редки, 2) если они не редки, то в любом случае программисту о них следует как-то сообщить...
 Я обычно стараюсь сначала как-то классифицировать обычные частые исключительные ситуации. Например, я выделяю 2 группы наиболее частых ситуаций:
 1) попытки нарушения уникальности
 2) попытки нарушения ссылочной целостности (внешнего ключа)
 3) специальные "запрограммированные" ошибки, например, конфликт блокировок, провоцируемый таблицей итогов при попытке одновременно продать последний товар со склада двумя менеджерами.

 1)В отношении первой группы я считаю эффективным предварительный запрос и осмысленные сообщения, сильно завязанные на контекст того, что собирается сделать пользователь. Например, прежде, чем добавить документ с каким-то номером, отдельный SQL-запрос проверяет его на уникальность номера. Это не избавляет от необходимости уникального ключа и не гарантирует, что такая предварительная проверка всегда сработает. Но она сработает в 99% случаев и даст очень информативное и точное сообщение, например, "Счет фактура с таким номером уже существует (от 01.01.2003, фирма "Такая-то
"). А в 1% случаев (когда 2 пользователя одновременно, каждый в своей транзакции вводят документ с одним номером) достаточно будет и стандартного сообщения "Попытка нарушения уникального ключа".
 2) Вторая группа (и те ситуации в первой группе, которые не удалось разрулить предварительным запросом) нуждаются в автоматической обработке (вплоть до парсига текста сообщения). Обычно по коду ошибки (сервер IB, например, всегда присылает код ошибки и этот код доступен) можно выявить, что это за ошибка (нарушение уникальности или ссылочной целостности), затем уже парсинг текста ошибки с разрешением имен упомянутых констрейнтов в имена объектов (справочник такой-то, документ такой-то) позволяет соорудить более или менеее понятное сообщение. Информативность для пользователя здесь особенная и не требуется. Пользователь должен всего лишь понять, что он только что пытался удалить элемент, используемый другим объектом и система ему этого не позволила сделать (он и так рад, что ничего страшного сделать не сумел благодаря "умной системе"), а особенного выбора в дальнейшем поведении у него здесь, собственно, и нет.

3) "специально запрограммированные" ошибки должны обрабатываться в блоках try except end с проверкой типа ошибки сервера и очень осмысленным сообщением, например: "Конфликт одновременной работы двух менеджеров с одним остатком. Попробуйте еще раз".
--------------------
Так что я бы не стал ставить вопрос так: или-или. В каких-то случаях (уникальность) хорошо использовать предварительные проверки (эти запросы не занимают много времени, так что на них можно не экономить), зато очень важно детально понять, что здесь не так (совпадает номер паспорта или фамилия и дата рождения или еще что-то...). Часто даже приблизительные проверки здесь уместны. Например, пациента с точно такими данными в системе нет и уникальный ключ пропустит новую запись, но хитрый предварительный запрос показывает, что есть "очень похожий пациент", у которого отличается лишь номер паспорта или одна буква в отчестве...
А другие ИС (нарушение ссылочной целостности) часто неэффективно ловить с помощью предварительных запросов. Да и незачем. Пусть сервер даст ошибку, а потом - ее нужно максимально "причесать" и вывести на экран. Все равно с этим уже ничего не поделаешь.
И наконец третьи ситуации - вообще требуют, чтобы сначала сервер создал исключительную ситуацию и иначе здесь невозможно никак действовать в принципе.
Плюс к этим трем случаям - то, что собственно, следует называть ошибками и писать в "лог"-файлы (когда что-то где-то работает неправильно или вообще не работает).


 
Mystic ©   (2004-09-07 17:29) [41]

Рамиль ©   (07.09.04 17:07) [33]

под IB у меня, например, так:


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

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


 
by ©   (2004-09-07 17:41) [42]

to Sergey_Masloff   (07.09.04 16:05)
P/S У меня все действия с базой через процедуры не по этой причине.

А какая причина или надобность объясняет необходимость проводить все изменения через ХП?


 
Sergey_Masloff   (2004-09-07 17:46) [43]

by ©   (07.09.04 17:41) [42]
1) Безопасность. Никакого SQL на клиенте.
2) Большая масштабируемость.
3) Некоторые специфические фичи - в частности многие интерфейсные вещи пишут люди которые не знают структуру базы. Они просто кидают компоненты именованые в соответствии со стандартом а серверную часть пишет другой человек. И есть универсальный код который это автоматически сопрягает.


 
by ©   (2004-09-07 17:53) [44]

Т.е. на клиенте нет даже EXEC PROC SP_XXX(ID, CLIENT_NAME) а есть просто компонент типа TClient у которого свойства это поля таблички и методы типа Save Open? И сам компонент значет как и что куда сохранить?


 
Sergey_Masloff   (2004-09-07 18:06) [45]

by ©   (07.09.04 17:53) [44]
Не совсем. Хотя такое тоже делал.
Сейчас (упрощенно) есть датасет. И есть одна процедура CallProcWithDataSet() которой передается управление при инсертах апдейтах и др. махинациях. Она сама выбирает какую ХП вызвать и как ее параметры задать и как ее вызвать. Есть отдельная система управления контекстами транзакций.
 Допустим в интерфейсе понадобилось новое поле. Разработчик интерфейса смело его рисует и описывает ее поведение обращаясь к ней только как к полю датасета. Сервер про это ничего не знает и поле просто игнорирует.
 Потом разработчик серверной (СУБД) части дополняет хранимую процедуру для работы с еще одним полем. Все начинает работать с ранее скомпилированым клиентом.


 
by ©   (2004-09-07 18:16) [46]

Sergey_Masloff   (07.09.04 18:06)
Если я правильно понял, то при этом датасет это что-то типа TClientDataSet который держит запись в памяти, но не передает все автоматом серверу через ApplyUpdates а отдает все данные процедуре на клиенте, которая разбирается что с этими данными делать, так?
А сами тексты запросов вызова ХП хранятся на сервере или составляются динамически?


 
Sergey_Masloff   (2004-09-07 18:26) [47]

by ©   (07.09.04 18:16) [46]
На сервере все тексты. Клиент не может выполнить никакой SQL команды кроме вызова ХП. Все эти обращения к серверу выполняет процедура на клиенте.

Датасет это не обязательно ClientDataSet можно и обычный но с включенным режимом кэшированых изменений. Я делад в этом ключе с BDE, IBX, FIBPlus (пока "боевого" проекта не было), ODAC и DOA. В смысле с этими я делал без клиентдатасетов.
 Кстати процедуру эту очень удобно на OnApplyUpdates вызывать.


 
by ©   (2004-09-07 18:38) [48]

Sergey_Masloff   (07.09.04 17:46)
универсальный код который это автоматически сопрягает.

этот универсальный код делает маппинг полей датасета на параметры ХП?


 
Sergey_Masloff   (2004-09-07 18:42) [49]

by ©   (07.09.04 18:38) [48]
>этот универсальный код делает маппинг полей датасета на >параметры ХП?
Да, и это тоже.


 
by ©   (2004-09-07 18:47) [50]

Sergey_Masloff   (07.09.04 18:42)
Я сейчас тоже к подобному движусь, хотелось услышать подтверждение что это реально.
Единственное что я для себя не выяснил реально ли сделать универсальный доступ к разным БД, делал ли кто подобное или для каждой БД пишут свое.
Я пробовал поднять эту проблему в
http://delphimaster.net/view/14-1094475596/&web=1
но развития тема не получила.


 
Sergey_Masloff   (2004-09-07 18:52) [51]

Реально.
Насчет универсальности... Все же у каждой СУБД есть свои уникальные возможности которые лучше использовать. А универсальность к сожалению приводит к обрезанию всех фич. Так что я считаю если используешь СУБД то используй ее на полную катушку. Но это моя личная позиция. Я не верю в миграцию большой системы между разными СУБД. Идеология и технология могут быть универсальны но техническая реализация должна отличаться.


 
by ©   (2004-09-07 19:00) [52]

Sergey_Masloff   (07.09.04 18:52)
Идет речь не о миграции между СУБД, понятно что то что хорошо для Oracle не хорошо и не реализуемо для Access. Речь идет просто об обертке. Т.е. есть один класс TMyDataSet у которого есть метод Save. Так вот если этот датасет подключен к FireBird, то он внутри себя вызовет IBQuery.Post а если подключен к другому источнику данных, то его метод сохранения. Идея в том что прикладной разработчик вызывает TMyDataSet.Save для любого источника одинаково, а дело класса с этим разобраться как это делать конкретно. Вот такое и хочется реализовать.


 
Рамиль ©   (2004-09-07 22:56) [53]


> Sergey_Masloff   (07.09.04 17:14) [35]

Это было для БД с парой-тройкой клиентов, просто как раз писал этот код. А в общем случае, ИМХО, лучше как
> Mystic ©   (07.09.04 12:18) [21]
и обрабатывать исключения централизованно.

> KSergey ©   (07.09.04 17:14) [36]

А что это менеяет?

> Mystic ©   (07.09.04 17:29) [41]

Само собой.
> Насчет универсальности... Все же у каждой СУБД есть свои
> уникальные возможности которые лучше использовать. А универсальность
> к сожалению приводит к обрезанию всех фич. Так что я считаю
> если используешь СУБД то используй ее на полную катушку.
>

Согласен, если уж писать, то лучше под одну СУБД. Вон у меня под носом пример каждый день, почему не стоит делать под разные - Галактика. Она и под Betrive, и под Oracle, и под MS SQL. Парус, например, поступил в этом случае умнее, новые версии пошли только под Oracle.


 
KSergey ©   (2004-09-08 07:29) [54]

> [53] Рамиль ©   (07.09.04 22:56)
> > KSergey ©   (07.09.04 17:14) [36]
> > У вас есть явный вызов Post
> А что это менеяет?

Это меняет то, что явно известен контекст. На OnPostError - он еще более-менее тоже понятен, а вот где-нибудь на Alllication.OnException - понять его уже слишком трудно.

> [45] Sergey_Masloff   (07.09.04 18:06)
>  Допустим в интерфейсе понадобилось новое поле. Разработчик
> интерфейса смело его рисует и описывает ее поведение обращаясь
> к ней только как к полю датасета. Сервер про это ничего
> не знает и поле просто игнорирует.
>  Потом разработчик серверной (СУБД) части дополняет хранимую
> процедуру для работы с еще одним полем. Все начинает работать
> с ранее скомпилированым клиентом.

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

> [40] kaif ©   (07.09.04 17:27)

Большое спасибо за обстоятельность.


 
Sergey_Masloff   (2004-09-08 09:01) [55]

KSergey ©   (08.09.04 07:29) [54]
>Хотелось бы уточнить техническую деталь: как понимаю, поля >передаются ХП в параметрах. Новое поле - новый параметр.
Не всегда. Обычно да. Никакой проблемы нет - просто обработчик работает как итератор который обходит датасет и для каждого поля пытается найти параметр. Если не найден поле игнорируется. Все клиентское отлаживать можно - на клиенте-то поле это есть и обращаться к нему можно. Только не сохраняется.

Во всех отборах всегда количество параметров одинаково. Любое сколь угодно сложное условие передается через 1 параметр.


 
KSergey ©   (2004-09-08 10:03) [56]

> [55] Sergey_Masloff   (08.09.04 09:01)
> Любое сколь угодно сложное условие передается через 1 параметр.

Вы напрашиваетесь на вопрос "как"? ;)
Считайте, что я его задал! ;)  (псевдо/макро язык? ну не верить же мне, что строка, подставляемая во Where)


 
Sergey_Masloff   (2004-09-08 10:24) [57]

KSergey ©   (08.09.04 10:03) [56]
Довольно просто. В подробности реализации вдаваться не буду но примерно так:
pParam1=AAAA&pParam2=BBBB&pParam3=CCCC

Есть процедура (естественно одна в базовом классе) которая оббегает все контролы и заполняет эту структуру. Есть свои контролы которые допустим заполняются из справочника. Допустим сложный случай когда отбор по списку, допустим городов. Пользователь последовательно из справочника заполняет компонент внешне похожий на ListView который умеет отдать ключи выбраных объектов в виде строки (12345,634638,74656) которую можно подставить в запрос в виде IN

Сам текст запроса собирается на сервере, идет цикл по параметрам
и добавляются джойны и условия WHERE (и даже хинты оптимизатору). Результатом отбора всегда является НД состоящий из 1 поля (идентификаторы отобраных записей) которые отдаются потом другой процедуре которая джойнит результат с полями нужными для отображения пользователю.
 Кстати в процедуру отбора передается с клиента макс. число записей которое она может возвратить а также она умеет ругаться типа "Критерий поиска неэффективен!" и посылать поисковика подальше.

 Выглядит это (для IB например) вот так:

CREATE PROCEDURE AGREEMENT_SRCH (
   PTASKISN NUMERIC(15,4),
   PPARAMSTR VARCHAR(32000))
RETURNS (
   PSRCHSQL VARCHAR(10000))
AS
DECLARE VARIABLE VNAME VARCHAR(40);
DECLARE VARIABLE VVALUE VARCHAR(1000);
DECLARE VARIABLE VINSERT VARCHAR(100);
DECLARE VARIABLE VSELECT VARCHAR(100);
DECLARE VARIABLE VFROM VARCHAR(10000);
DECLARE VARIABLE VWHERE VARCHAR(2000);
begin
 /*
   &#238;&#242;&#225;&#238;&#240; &#228;&#238;&#227;&#238;&#226;&#238;&#240;&#238;&#226;
   &#192;&#226;&#242;&#238;&#240; &#204;&#224;&#241;&#235;&#238;&#226; &#209;.&#209;.
 */
 pSrchSQL = "";
 vInsert = "insert into ListIsn(TaskIsn,ObjIsn) ";
 vSelect = pTaskIsn||",a.isn ";
 vFrom   = "agreement a ";
 vWhere  = "";

 for select ParamName, ParamValue from split_param(:pParamStr) into :vName, :vValue
 do begin
   select fSelect,fFrom,fWhere from Dyn_StandartParam(:vName,:vValue,:vSelect,:vFrom,:vWhere)
   into :vSelect,:vFrom,:vWhere;
   /*-- &#243;&#241;&#235;&#238;&#226;&#232;&#255; &#238;&#242;&#225;&#238;&#240;&#224; --*/
   if (vName = "pid"       ) then select pOutStr from Dyn_AddText(:vWhere,"a.id like """||UPCASE(:vValue)||"%"""," and ") into vWhere;

   if (vName = "posagoser"       ) then
   BEGIN
     vFrom = UDF_Dyn_AddText(:vFrom," left join agrid i on i.agrisn = a.isn ", "");
     select pOutStr from Dyn_AddText(:vWhere,"i.docser = """||UPCASE(:vValue)||""" and i.classisn=767683800.0 "," and ") into vWhere;
   end
   if (vName = "posagonom"       ) then
   begin
     vFrom = UDF_Dyn_AddText(:vFrom," left join agrid i on i.agrisn = a.isn ", "");
     select pOutStr from Dyn_AddText(:vWhere,"i.docno = """||UPCASE(:vValue)||""" and i.classisn=767683800.0 "," and ") into vWhere;
   end
   /* &#196;&#224;&#242;&#251; */
   if (vName = "pdatebegs" ) then
     vWhere = UDF_Dyn_AddText(:vWhere,"a.datebeg >= """||:vValue||""""," and ");
   if (vName = "pdatebegf" ) then
     vWhere = UDF_Dyn_AddText(:vWhere,"a.datebeg <= """||:vValue||""""," and ");
   if (vName = "pdateends" ) then
     vWhere = UDF_Dyn_AddText(:vWhere,"a.dateend >= """||:vValue||""""," and ");
   if (vName = "pdateendf" ) then
     vWhere = UDF_Dyn_AddText(:vWhere,"a.dateend <= """||:vValue||""""," and ");
   if (vName = "pdateissues" ) then
     vWhere = UDF_Dyn_AddText(:vWhere,"a.dateissue >= """||:vValue||""""," and ");
   if (vName = "pdateissuef" ) then
     vWhere = UDF_Dyn_AddText(:vWhere,"a.dateissue <= """||:vValue||""""," and ");

   if (vName = "pdatecres" ) then vWhere = udf_dyn_addtext(:vWhere,"a.created >= """||:vValue||""""," and ");
   if (vName = "pdatecref" ) then vWhere = udf_dyn_addtext(:vWhere,"a.created <= """||:vValue||""""," and ");

   if (vName = "pemplisn" ) then vWhere = udf_dyn_addtext(:vWhere,"a.emplisn in "||:vValue||""," and ");

   if (vName = "pstatuscode"   ) then
     select pOutStr from Dyn_AddText(:vWhere,"a.status in "||UPCASE(:vValue)||""," and ") into vWhere;

   if (vName = "pruleisn" ) then select pOutStr from Dyn_AddText(:vWhere,"a.ruleisn in(select F_ISN from DICTI_DOWN(0,"||:vValue||"))","  and ") into vWhere;

   if (vName = "pclientsisn" ) then
   begin
     select pOutStr from Dyn_AddText(:vFrom, " left join agrrole r on r.agrisn = a.isn ", "") into :vFrom;
     select pOutStr from Dyn_AddText(:vWhere,"r.subjisn in "||:vValue||"","  and ") into vWhere;
   end

   if (vName = "prolesisn" ) then
   begin
     select pOutStr from Dyn_AddText(:vFrom, " left join agrrole r on r.agrisn = a.isn ", "") into :vFrom;
     select pOutStr from Dyn_AddText(:vWhere,"r.classisn in "||:vValue||"","  and ") into vWhere;
   end

   if (vName = "pobjname" ) then
   begin
     vFrom = UDF_Dyn_AddText("("||vFrom||")", " left outer join agrobject o on o.agrisn = a.isn " , "");
     vWhere = UDF_Dyn_AddText(:vWhere," o.name like """||:vValue||"%"""," and ");
   end

   if (vName = "pobjclassisn" ) then
   begin
     vFrom = UDF_Dyn_AddText("("||vFrom||")", " left outer join agrobject o on o.agrisn = a.isn " , "");
     vWhere = UDF_Dyn_AddText(:vWhere," o.classisn in (SELECT F_ISN FROM DICTI_DOWN(0,"||:vValue||"))"," and ");
   end

   if (vName = "pvin" ) then
   begin
     vFrom = UDF_Dyn_AddText("("||vFrom||")", " left outer join agrobject o on o.agrisn = a.isn " , "");
     vWhere = UDF_Dyn_AddText(:vWhere," exists (SELECT null from objcar c where c.isn = o.descisn and c.vinr like """||REVSTR(:vValue)||"%"")"," and ");
   end
   /* ~ &#243;&#241;&#235;&#238;&#226;&#232;&#255; &#238;&#242;&#225;&#238;&#240;&#224; */
 end
 if (vWhere<>"") then begin
   vWhere = vWhere||" group by a.isn";
   select fSrchSQL from Dyn_ExecSQL (:pTaskIsn,"", :vSelect, :vFrom, :vWhere, "S","AGREEMENT_SRCH") into pSrchSQL;
 end
 suspend;
end


 
Sergey_Masloff   (2004-09-08 10:30) [58]

Кстати материализация на сервере результатов запроса позволила сделать асинхронный механизм. Если запрос явно тяжелый а время у юзера есть то запрос уходит на асинхронное выполнение а потом (допустим после выходных) программа ему говорит - а помните вы искали? - так вот готово


 
by ©   (2004-09-08 12:13) [59]

Sergey_Masloff   (07.09.04 18:06)

Допустим в интерфейсе понадобилось новое поле. Разработчик интерфейса смело его рисует ...

И после того как разработчик нарисует это поле ему надо обновить программу во всех местах установок или это поле хранится в БД или конфигурационном файле каком-то?


 
Sergey_Masloff   (2004-09-08 12:30) [60]

by ©   (08.09.04 12:13) [59]
Обновляет. Все же появление новых полей это КРАЙНЕ редкая операция (в моих задачах и реализациях). Я видел реализации с хранением форм в БД но на мой взгляд тут овчинка выделки не стоит. Я анализировал этот подход но в моих задачах пользы от него особой нет. Мне чаще приходится иметь дело с изменением поведенческой стороны (скажем поле становится обязательным или наоборот) - это действительно в БД задается.


 
Карелин Артем ©   (2004-09-08 15:43) [61]

Мой подход:
1) Все проверки на правильность ввода, ссылочную целостность, и многое другое при вводе "руками" проходят в программе. Сохранить/удалить/изменить можно только в случае одобрения действий пользователя программой и проверки на корректность.
2) Целостность ключей обеспечивается генератором. Ключ формируется только сервером.
3) Вообще все ошибки отлавливаются в Application.OnException и записываются в файл со следующей инфой:
-тип ошибки
-сам текст ошибки
-активная форма, контрол
-время и дата
Ну и еще снимок экрана делается.



Страницы: 1 2 вся ветка

Форум: "Потрепаться";
Текущий архив: 2004.09.26;
Скачать: [xml.tar.bz2];

Наверх





Память: 0.78 MB
Время: 0.039 c
6-1089980510
VOVA
2004-07-16 16:21
2004.09.26
подкинте исходник передачи файла по сети


4-1092660212
Manfred
2004-08-16 16:43
2004.09.26
Какое сообщение генерится при нажатии CTRL+ALT+DEL?


6-1089857582
BoxTer
2004-07-15 06:13
2004.09.26
Проблема с TServerSocket


14-1094361325
Думкин
2004-09-05 09:15
2004.09.26
С днем рождения! 5 сентября


1-1094633769
Jay
2004-09-08 12:56
2004.09.26
MainMenu





Afrikaans Albanian Arabic Armenian Azerbaijani Basque Belarusian Bulgarian Catalan Chinese (Simplified) Chinese (Traditional) Croatian Czech Danish Dutch English Estonian Filipino Finnish French
Galician Georgian German Greek Haitian Creole Hebrew Hindi Hungarian Icelandic Indonesian Irish Italian Japanese Korean Latvian Lithuanian Macedonian Malay Maltese Norwegian
Persian Polish Portuguese Romanian Russian Serbian Slovak Slovenian Spanish Swahili Swedish Thai Turkish Ukrainian Urdu Vietnamese Welsh Yiddish Bengali Bosnian
Cebuano Esperanto Gujarati Hausa Hmong Igbo Javanese Kannada Khmer Lao Latin Maori Marathi Mongolian Nepali Punjabi Somali Tamil Telugu Yoruba
Zulu
Английский Французский Немецкий Итальянский Португальский Русский Испанский