Консультация № 184101
27.09.2011, 15:07
0.00 руб.
0 34 0
Здравствуйте, уважаемые эксперты! Прошу вас ответить на следующий вопрос:

Я делаю клиент-серверную программу, с которой должно работать около 25 человек одновременно. При тестировании программы появлялось множество глюков, когда с ней работало всего 3-6 человек. При работе одного человека, глюков не происходит :)
Все глюки связаны, с тем, что компоненты наборов данных не всегда отражают изменения, которые уже произошли в бд, хотя была выполнена процедура обновления набора (dataset.refresh).
Я использую mysql 5.5 и компонент с для работы с ней - zeoslib 7.0. Думаю, что все из-за компонентов zeoslib – либо что-то настроено не так у компонентов, которые я использую (TZTable, TZQuery, TZConnection), либо проблема в тонкостях использования процедур при обновлении датасета.

Пожалуйста, те кто работал с библиотекой zeoslib или разрабатывал многопользовательские приложения баз-данных, дайте совет :)

Обсуждение

давно
Мастер-Эксперт
425
4118
28.09.2011, 12:02
общий
Цитата: 31543
то есть, например так пользуемся

Ну да, примерно так.
Цитата: 31543
или ролбэк делается когда запись уже была сделана в бд и ее нужно отменить?

Пока Вы не подтвердили начатую транзакцию с помощью Commit, её можно отменять в любой момент с помощью RollBack.
Только не стремитесь подтверждать каждую сделанную запись, а лучше делайте это для группы записей. Сервер будет меньше напрягаться. Либо для группы операций над записью.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
28.09.2011, 17:03
общий
Адресаты:
спасибо.
буду тестировать. о результатах сообщую.
Неизвестный
01.10.2011, 16:29
общий
Адресаты:
обнаружил проблему при попытки редактирования записи, если ее новое значение ключа дублирует существующий ключ, при этом обрабатывается исключение (один раз), и после этого нельзя не отредактировать, ни создать новую запись в этой таблице с уникальными ключами...
// при редактировании
...
ZConnection.StartTransaction;
try
dm.tbBook.Edit;
dm.tbBook.FieldByName('id').AsInteger:= 10; // изменяю ключ на 10, хотя в таблице уже такой ключ есть
...
dm.tbBook.Post;
except on E: Exception do
begin
ZConnection.Rollback;
end;
end;
ZConnection.Commit;

после выполнения этого кода нельзя будет создать запись или отредактировать любую запись, и при этом будет выскакивать сообщение, что дублируюется ключ 10...как буд-то исключение не было правильно обработано и после этого никакой операции над таблицей не сделать...
давно
Мастер-Эксперт
425
4118
02.10.2011, 16:21
общий
Цитата: 31543
dm.tbBook.FieldByName('id').AsInteger:= 10; // изменяю ключ на 10, хотя в таблице уже такой ключ есть

У Вас запись неправильного значения и так не сработает, поэтому манипуляция транзакцией, совместно с try ... except, в этом случае, ни к селу, ни к городу.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
02.10.2011, 16:27
общий
Адресаты:
я не могу применять процедуры по транзакции когда редактирую запись? только при добавлении? try ... except мне нужно использовать полюбому, чтобы перехватывать исключения...
давно
Мастер-Эксперт
425
4118
02.10.2011, 16:36
общий
Цитата: 31543
try ... except мне нужно использовать полюбому, чтобы перехватывать исключения...

В данном случае, т.е. при манипуляции таким извращённым способом с главным ключом, исключение будет выдавать не программа, а сервер баз данных. Программа не может проверить, правильные Вы передаёте серверу данные или нет. Поэтому то, что Вы видите, это отголосок ошибки, которую передал обратно программе сервер.
Прежде, чем продолжить разговор - настолько ли у Вас жизненно необходима ситуация, когда нужно редактировать главный ключ?
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
02.10.2011, 16:45
общий
Цитата: Вадим Исаев ака sir Henry
настолько ли у Вас жизненно необходима ситуация, когда нужно редактировать главный ключ?

абсолютно необходимо. здесь я привел пример таблицы с одним ключем. на самом деле у меня таблица имеет два ключа, которые вместе являются id.
давно
Мастер-Эксперт
425
4118
02.10.2011, 17:00
общий
У Вас используется два способа, скажем так, кэширования данных:
1. Тот, который подтверждается методом Post. Это кэширование датасетом, который находится в программе.
2. Тот, который подтверждается методом Commit. Это кэширование непосредственно сервером.
Первый способ эмулирует транзакцию. Почему эмулирует? Потому, что он манипулирует только локальными данными. Этот способ не нужен. Методом Post Вы подтвердили изменение данных в локальном датасете, но после отсылки этих неправильных данных на сервер, сервер Вам выдал ошибку. А изменить Вы уже ничего не можете, т.к. в локальном наборе данных эти данные благополучно подтверждены методом Post. А вот сервер принять эти данные не может. Вы попали в дурной цикл и выхода из него нет, Вы и сами убедились, увидев, что не можете больше редактировать данные.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
02.10.2011, 17:12
общий
02.10.2011, 17:13
Адресаты:
вот оно че )
теперь мне нужно заменить
Код:
dm.tbBook.Edit;
dm.tbBook.FieldByName('id').AsInteger:= 10;
dm.tbBook.Post;
...

на запуск скрипта, который пытается обновить запись? как-то не удобно...
давно
Мастер-Эксперт
425
4118
02.10.2011, 17:22
общий
Прошлый век. Устарело.

Код:
ZConnection.ExecuteDirect('UPDATE таблица SET id='+Edit1.Text);
ZConnection.Commit;
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
02.10.2011, 17:29
общий
Адресаты:
а можно как-нибудь проще, используя типа FieldByName() сделать...
у меня около 30 таблиц, где нужно будет редактировать...то есть задолбаюсь кодировать...
или можно применять код, как у меня, если редактируются данные, а не ключи, а там где редактируется ключ - выполнять ExecuteDirect?
давно
Мастер-Эксперт
425
4118
02.10.2011, 17:34
общий
У Вас проблема не в этом. Вы до сих пор не можете отказаться от типа работы с данными, как работа с таблицами. А Вы должны работать непосредственно с данными. Пользователю программы совершенно наплевать, какие есть таблицы и какие поля в таблицах. Он работает с конкретными данными. Поймёте это - станет всё просто.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
02.10.2011, 17:44
общий
вы хотите сказать, что раз у меня многопользовательская программа, то мне нужно забыть про использование локальных датасетов и все делать с помощью запросов? я чекнусь переписывать программу
давно
Мастер-Эксперт
425
4118
02.10.2011, 18:01
общий
02.10.2011, 18:04
А в датасете у Вас данные получаются разве не из запросов? И я что-то не вижу, что Вы от этого чокнулись. Зато я ясно вижу, что работая с датасетом Вы не можете выбраться из круга проблем. Смотреть данные в датасете удобно, а вот изменять - нет. На порядок ухудшается время обработки данных, увеличивается количество кода, Вы не можете понять, с чем Вы работаете. Разве это правильно?
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
02.10.2011, 18:09
общий
02.10.2011, 18:11
Адресаты:
я чекнулся делать запросы на выборку, а тут еще на редактирование делать, а то и на вставку...
может как то все таки можно. я нашел статью:
Cached Updates

The developers of the ZEOS Library are intended to implement the functionality of the BDE components as
good as possible. This is why there is also the possibility of cached updates with ZEOS. You only have to set
the property CachedUpdates of a DataSet descendant (TZTable, TZQuery oder TZStoredProc) to true. From
this time on all changes in the result set will be cached and they can be easily committed to the database by
calling ApplyUpdates and CommitUpdates one after the other either automatically in your program code or
triggered manually by a user. CancelUpdates causes that the canges will not be committed to the database.
In this case all cached changes will be reset (like using a ROLLBACK). Here you will find a little code snippet
that tries to show you how cached updates are implemented (don't argue about the sense in this...).
AutoCommit mode of TZConnectin is turned on (True):
:
With DataSet do Begin
bEverythingOK := True;
CachedUpdates := True;
DataSet.First;
While (not DataSet.EOF) and (bEverythingOK) do Begin
DataSet.Edit;
:
// process record
:
DataSet.Post;
DataSet.Next;
:
bEverythingOK := AFunctionForValidation;
End;
If bEverythingOK Then Begin
ApplyUpdates;
CommitUpdates;
End
Else
CancelUpdates;
CachedUpdates := False;
End;
::
With DataSet do Begin
bEverythingOK := True;
CachedUpdates := True;
DataSet.First;
While (not DataSet.EOF) and (bEverythingOK) do Begin
DataSet.Edit;
:
// process record
:
DataSet.Post;
DataSet.Next;
:
bEverythingOK := AFunctionForValidation;
End;
If bEverythingOK Then Begin
ApplyUpdates;
CommitUpdates;
End
Else
CancelUpdates;
CachedUpdates := False;
End;
:

похоже это то, что мне нужно, но только не понятно как проверку делать, что все ок. то есть что на месте AFunctionForValidation
давно
Мастер-Эксперт
425
4118
02.10.2011, 18:21
общий
Вы собираетесь редактировать каждую запись по отдельности и данные ни разу не повторяются, не имеют никакой группировки?
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
02.10.2011, 18:31
общий
Адресаты:
не во всех таблицах, но кое какие поля повторяются, например значения внешних ключей
а что?
давно
Мастер-Эксперт
425
4118
03.10.2011, 03:13
общий
У SQL есть одна фишка, которая позволяет экономить на коде. И связано это с группировкой данных по тем или иным признакам. Например:
Код:
UPDATE таблица SET поле1=значение1. поле2=значение2 WHERE какое-то условие

Это будет намного быстрее и экономнее, чем перебирать записи датасета последовательно и у всех выбраных записей менять значения.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
03.10.2011, 10:28
общий
Адресаты:
не, мне не нужно перебирать значения...тот пример с перебором из статьи, который демонстрирует кеширование локальных данных, а не из моего проекта.
мне как раз наоборот нужно всегда редактировать только одну запись, но несколько полей. сейчас у меня везде реализовано как я показывал:
Код:
dm.tbBook.Edit;
dm.tbBook.FieldByName('id').AsInteger:= 10;
dm.tbBook.Post;

и если применять StartTransaction, Commit, Rollback, то мне нужно, как вы говорите, заменить приведенный пример кода с помощью выполнения sql-запроса, что для меня очень рутинно, так как мне придется переписывать очень много из за того, что у меня много таблиц где требуется редактирование. вот я и спрашивал, есть ли возможность сделать commit, но оставив технологию как я редактирую запись, то есть так:
Код:
dm.tbBook.FieldByName('id').AsInteger:= 10;


в приведенной статье было показано как работать с процедурами:
ApplyUpdates;
CommitUpdates;
CancelUpdates;
и видимо мне их и нужно использовать, но остается вопрос: как обрабатывать ошибки, обработка которых в статье показана
строчкой:
Код:
bEverythingOK := AFunctionForValidation;
давно
Мастер-Эксперт
425
4118
03.10.2011, 10:47
общий
Цитата: 31543
есть ли возможность сделать commit

Commit относится только к транзакциям, т.е. используется только в связке StartTransaction - Commit.
Цитата: 31543
как обрабатывать ошибки, обработка которых в статье показана
строчкой:
Код :

bEverythingOK := AFunctionForValidation;

Там в статье должен быть и пример этой функции - она не системная, поэтому должна описываться в той статье. У меня про неё спрашивать бессмысленно.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
03.10.2011, 10:56
общий
Адресаты:
вот статья полностью:
http://www.scribd.com/doc/58025032/A-ZEOS-Basics-Tutorial-Not-Only-for-Firebird

мне нужно просто знать. решат ли мои проблемы функции:
ApplyUpdates;
CommitUpdates;
CancelUpdates;

в моей многопользовательской программе или все же это не то, и мне нужно будет делать sql-запросы на все вставки и обновления...
давно
Мастер-Эксперт
425
4118
03.10.2011, 11:28
общий
Цитата: 31543
мне нужно просто знать. решат ли мои проблемы функции:
ApplyUpdates;
CommitUpdates;
CancelUpdates;

Я не знаю. Такой стиль написания программ при работе с SQL-серверами я не использую. Очень накладно. Вы будете ждать, когда я проверю или сами проверите?
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
03.10.2011, 11:35
общий
Адресаты:
проверить, что не будет глюков при одновременной работе больше десятка пользователей я сейчас не имею возможности, и даже не знаю как с эмулировать это.
давно
Мастер-Эксперт
425
4118
03.10.2011, 11:51
общий
Что-то у меня сильные сомнения в том, что функция AFunctionForValidation делает нечто полезное. Вот смотрите, она вызывается после того, как Вы зафиксировали изменения в локальном датасете и после того, как Вы перешли в локальном датасете на новую строку. По логике вещей на этой новой строке можно проверить какие там находятся данные. Но поскольку функция завязана на подтверждений именно кэша в локальном датасете (ApplyUpdates). После этого, зафиксировать изменения в самой базе данных должен уже CommitUpdates. Возникает логический парадокс - если ApplyUpdates не применялась, то какие изменения должен фиксировать CommitUdates? В любом случае, вместо трёх методов - Post, ApplyUpdates и CommitUpdates и ещё нечто непонятно AFunctionForValidation, по-моему, проще использовать один единственный метод Commit, подтверждающий транзакцию у компонента TZConnection.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
давно
Мастер-Эксперт
425
4118
03.10.2011, 11:52
общий
Цитата: 31543
и даже не знаю как с эмулировать это.

Запустите на своём компьютере 25 клиентских программ.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
03.10.2011, 12:00
общий
Цитата: Вадим Исаев ака sir Henry
по-моему, проще использовать один единственный метод Commit, подтверждающий транзакцию у компонента TZConnection.

но для этого мне придется много исправлять...
Цитата: Вадим Исаев ака sir Henry
Запустите на своём компьютере 25 клиентских программ.

а как мне на кнопки нажимать одновременно? )
давно
Мастер-Эксперт
425
4118
03.10.2011, 12:26
общий
03.10.2011, 12:27
Цитата: 31543
а как мне на кнопки нажимать одновременно?

Код:
Wind := FindWindow(nil, 'Form1');
Btn := FindWindowEx(Wind, 0, nil, 'Кнопка');
SendMessage(Btn, BM_CLICK, 0, 0);

В общем, первая функция ищет окно, у которой Caption:='Form1'. Далее, в этом окне ищется дочернее окно-кнопка, у которой надпись "Кнопка" и этой кнопке посылается сообщение о том, чтобы она нажалась. Поскольку у Вас будет много окон с одинаковыми заголовками, то необходимо применить функцию EnumWindows(), которая составляет список существующих окон. Как её применять - смотрите здесь.
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
03.10.2011, 14:45
общий
Адресаты:
если вы будете эксперементировать с этими процедурами, то сооющите мне о результатах пожалуйста
давно
Мастер-Эксперт
425
4118
03.10.2011, 15:17
общий
Надеюсь Вы не ждёте, что я начну эксперементировать прямо сейчас?
Об авторе:
Я только в одном глубоко убеждён - не надо иметь убеждений! :)
Неизвестный
07.10.2011, 14:55
общий
07.10.2011, 14:57
Адресаты:
взял за основу пример, ссылку на который вы дали.
нахожу окно, но не нахожу кнопку, чтобы на нее потом нажать...
Код:

unit u_tester;

interface

uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;

type
TForm1 = class(TForm)
Button1: TButton;
ListBox1: TListBox;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.dfm}

type
PFindWindowStruct = ^TFindWindowStruct;
TFindWindowStruct = record
Caption : string;
ClassName : string;
WindowHandle : THandle;
end;

function EnumWindowsProc(hWindow : hWnd;
lParam : LongInt) : Bool
{$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF}
var
lpBuffer : PChar;
WindowCaptionFound : bool;
ClassNameFound : bool;

begin
GetMem(lpBuffer, 255);
Result := True;
WindowCaptionFound := False;
ClassNameFound := False;

try
if GetWindowText(hWindow, lpBuffer, 255) > 0 then
begin
if Pos('Ограничение пользователей', StrPas(lpBuffer)) > 0
then WindowCaptionFound := true;
end;

if PFindWindowStruct(lParam).ClassName = '' then
ClassNameFound := True else
if GetClassName(hWindow, lpBuffer, 255) > 0 then
if Pos(PFindWindowStruct(lParam).ClassName, StrPas(lpBuffer))
> 0 then ClassNameFound := True;

if (WindowCaptionFound and ClassNameFound) then begin
PFindWindowStruct(lParam).WindowHandle := hWindow;
Result := False;
end;

finally
FreeMem(lpBuffer, sizeof(lpBuffer^));
end;
end;

function FindAWindow(Caption : string;
ClassName : string) : THandle;
var
WindowInfo : TFindWindowStruct;

begin
with WindowInfo do begin
Caption := Caption;
ClassName := ClassName;
WindowHandle := 0;
EnumWindows(@EnumWindowsProc, LongInt(@WindowInfo));
FindAWindow := WindowHandle;
end;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
TheWindowHandle, btn : THandle;
begin
TheWindowHandle := FindAWindow('', ''); // в первом параметре не передаю название окна ('Ограничение пользователей') так как подставил его вручную в ф-ии EnumWindowsProc, так как почему-то это имя не передовалось через параметр - было пусто всегда...
if TheWindowHandle = 0 then
ShowMessage('Window Not Found!') else
begin
Btn := FindWindowEx(TheWindowHandle, 0, '', 'Добавить'); // не находит кнопку
if btn = 0 then
ShowMessage('button Not Found!') else
begin
SendMessage(TheWindowHandle, BM_CLICK, 0, 0);
end;
end;
end;

end.
Форма ответа