Historia zmian na formie - jak to sprytnie robicie?

Odpowiedz Nowy wątek
2019-03-14 20:06
0

Cześć,
w program w których czasami muszę udowadniać że klient w trakcie używania sam narozrabiał a nie jest to problem systemu stosuję zapisywanie zmian jakie zaszły na formie. Robię to już od dawna i jakoś to działa chociaż kosztuje mnie to dużo pracy.

Wygląd to mnie więcej tak że w bazie tworzę tabelę o nazwie historia a w niej kilka kolumn mniej więcej takich jak:

  • data_utworzenia (typu timestamp),
  • uzytkownik (zalogowany do programu),
  • uzytkownik_system (uzytkownik zalogowany do Windows),
  • komputer (nazwa stanowiska Windows),
  • panel (numer formy która "wpisuje" rekord do bazy),
  • operacje (czyli zapisane otwartym tekstem historie zmian lub ich brak jeśli klient wycofa się ze zmian).

Tak naprawdę wiem wszystko i wiem jakie dane były wcześniej. W ten sposób mogę dokładnie prześledzić co się działo z danymi i kto rozrabiał.

Oczywiście wiem że takie rozwiązanie powoduje bardzo duży przyrost danych w bazie ale robię to z pełną świadomością i nie o to tutaj chodzi.

Gdy tworzony jest rekord taka operacja wygląda mniej więcej tak (przykład z ostatniego programu):

procedure Tnowy_model.dodaj;
var
  licznik: integer;
  historia: string;
begin
  try
    with DM.qrytemp, SQL do
    begin
      Close;
      Clear;
      Add('INSERT INTO modele (id_marka, model, opis, utworzyl)');
      Add('VALUES (:id_marka, :model, :opis, :utworzyl)');
      ParamByName('id_marka').AsInteger := id_marka;
      ParamByName('model').AsString := edtnazwa.Text;
      ParamByName('opis').AsString := mmoopis.Text;
      ParamByName('utworzyl').AsString := main.uzytkownik;
      ExecSQL;
    end;

    with DM.qrytemp, SQL do
    begin
      Close;
      Clear;
      Add('SELECT LAST_INSERT_ID()');
      Open;
      First;
      licznik := DM.qrytemp.Fields[0].AsInteger;
    end;
  except
    ShowMessage('Błąd! Nie dodano modelu. Sprawdź dane!');
  end;

  try
    historia := 'Utworzenie modelu' + #13#10;
    historia := historia + 'Nazwa: ' + edtnazwa.Text + #13#10;
    historia := historia + 'Marka: ' + cbbmarka.Text + #13#10;
    historia := historia + 'Opis: ' + mmoopis.Text + #13#10;

    with DM.qryhistoria, SQL do
    begin
      Close;
      Clear;
      Add('INSERT INTO historia (panel, utworzyl, rekord, operacja, stanowisko) VALUES (:panel, :utworzyl, :rekord, :operacja, :stanowisko)');
      ParamByName('panel').AsInteger := 2;
      ParamByName('utworzyl').AsString := main.uzytkownik;
      ParamByName('rekord').AsInteger := licznik;
      ParamByName('operacja').AsString := historia;
      ParamByName('stanowisko').AsString := main.komputer;
      ExecSQL;
    end;
  except
    ShowMessage('Błąd! Nie zapisano zmian w historii operacji!');
  end;

  with DM.qrymodele, SQL do
  begin
    Clear;
    Close;
    Add('select mo.id, mo.id_marka, mo.model, mo.opis, mo.usun, mo.data_utworzenia, mo.utworzyl, ma.marka from modele mo');
    Add('inner join marki ma on mo.id_marka = ma.id where mo.usun = :usun order by ma.marka, mo.model');
    ParamByName('usun').AsInteger := 0;
    Open;
  end;

  DM.qrymodele.Locate('id', licznik, []);
end;

Nie kosztowało to zbyt wiele pracy ale też i panel zawierał tylko kilka pól edycyjnych.

W przypadku edycji już jest troszkę gorzej. Pokazując panel muszę najpierw uzupełnić dane w kontrolkach:

procedure Tmodele.btnedycjaClick(Sender: TObject);
begin
  edit_model.cbbmarka.Items.Clear;

  with DM.qrytemp, SQL do
  begin
    Close;
    Clear;
    Add('select marka from marki order by marka ASC');
    Open;
    First;
  end;

  while not DM.qrytemp.Eof do
  begin
    edit_model.cbbmarka.Items.Add(DM.qrytemp.FieldByName('marka').AsString);
    DM.qrytemp.Next;
  end;

  edit_model.cbbmarka.ItemIndex := edit_model.cbbmarka.Items.IndexOf(DM.qrymodele.FieldByName('marka').AsString);

  edit_model.id := DM.qrymodele.FieldByName('id').AsInteger;
  edit_model.id_marka := DM.qrymodele.FieldByName('id').AsInteger;
  edit_model.edtnazwa.Text := DM.qrymodele.FieldByName('model').AsString;
  edit_model.mmoopis.Text := DM.qrymodele.FieldByName('opis').AsString;
  edit_model.lbltworca.Caption := DM.qrymodele.FieldByName('utworzyl').AsString;
  edit_model.lbldata_utworzenia.Caption := DateTimeToStr(DM.qrymodele.FieldByName('data_utworzenia').AsDateTime);
  edit_model.Left := modele.Left + 20;
  edit_model.Top := modele.Top + 20;
  edit_model.Show;
end;

Następnie na formie edit_model tworzę zmienne dla historii:

  private
    h_marka, h_nazwa, h_opis: string;

Następnie w zdarzeniu OnShow zapamiętuję aktualny stan kontrolek:

procedure Tedit_model.FormShow(Sender: TObject);
begin
  h_marka := cbbmarka.Text;
  h_nazwa := edtnazwa.Text;
  h_opis := mmoopis.Text;
  edtnazwa.SetFocus;
end;

No i teraz jest już z górki, wystarczy w trakcie update rekordu użyć if-ów i wszystko staje się jasne:

procedure Tedit_model.btnzapiszClick(Sender: TObject);
var
  historia: string;
begin
  try
    with DM.qrytemp, SQL do
    begin
      Close;
      Clear;
      Add('UPDATE modele SET id_marka=:id_marka, model=:model, opis=:opis WHERE id=:id');
      ParamByName('id_marka').AsInteger := id_marka;
      ParamByName('model').AsString := edtnazwa.Text;
      ParamByName('opis').AsString := mmoopis.Text;
      ParamByName('id').AsInteger := id;
      ExecSQL;
    end;
  except
    ShowMessage('Błąd! Nie zaktualizowano modelu. Sprawdź dane!');
  end;

  try
    historia := 'Aktualizacja modelu' + #13#10;
    if cbbmarka.Text <> h_marka then
      historia := historia + 'Marka: było: ' + h_marka + ' jest: ' + cbbmarka.Text + #13#10;
    if edtnazwa.Text <> h_nazwa then
      historia := historia + 'Model: było: ' + h_nazwa + ' jest: ' + edtnazwa.Text + #13#10;
    if mmoopis.Text <> h_opis then
      historia := historia + 'Opis: było: ' + h_opis + ' jest: ' + mmoopis.Text + #13#10;
    if ((edtnazwa.Text = h_nazwa) and (cbbmarka.Text = h_marka) and (mmoopis.Text = h_opis)) then
      historia := historia + 'Nic nie zmieniono!';

    with DM.qryhistoria, SQL do
    begin
      Close;
      Clear;
      Add('INSERT INTO historia (panel, utworzyl, rekord, operacja, stanowisko) VALUES (:panel, :utworzyl, :rekord, :operacja, :stanowisko)');
      ParamByName('panel').AsInteger := 2;
      ParamByName('utworzyl').AsString := main.uzytkownik;
      ParamByName('rekord').AsInteger := id;
      ParamByName('operacja').AsString := historia;
      ParamByName('stanowisko').AsString := main.komputer;
      ExecSQL;
    end;
  except
    ShowMessage('Błąd! Nie zapisano zmian w historii operacji!');
  end;

  with DM.qrymodele, SQL do
  begin
    Clear;
    Close;
    Add('select mo.id, mo.id_marka, mo.model, mo.opis, mo.usun, mo.data_utworzenia, mo.utworzyl, ma.marka from modele mo');
    Add('inner join marki ma on mo.id_marka = ma.id where mo.usun = :usun order by ma.marka, mo.model');
    ParamByName('usun').AsInteger := 0;
    Open;
  end;

  DM.qrymodele.Locate('id', id, []);

  Close;
end;

Jak napisałem - jakoś to działa ale powiem szczerze że wyjątkowo mi się nie podoba. Po pierwsze te ify po drugie wszystko jest ok gdy pól edycyjnych jest nie więcej niż paręnaście (właśnie mam na pulpicie program w którym jest prawie 200 checkbox-ów).

Podzielcie się pomysłem jak to zrobić bardziej zgrabnie, może jakaś uniwersalna klasa która sama "wykryje" wszystkie pola edycyjne i zapamięta ich stan, następnie porówna przed opuszczeniem formy czy coś się zmieniło i zapisze zmiany? W taki powiedzmy sprytniejszy sposób ustawiam stan kontrolek na tej 200 elementowej formie:

  for i := 0 to ComponentCount - 1 do
  begin
    if components[i] is TCheckBox then
      TCheckBox(Components[i]).Checked := False;

    if components[i] is TEdit then
      TEdit(Components[i]).Text := '';
  end;

A może jest jakiś gotowiec do tego a ja go nie znam?

Zapraszam do dyskusji.
Robert

edytowany 1x, ostatnio: furious programming, 2019-03-14 20:08

Pozostało 580 znaków

2019-03-15 08:34
1

Rozważ DataBinding i operuj na abstrakcji, nie na komponentach bezpośrednio.

Pozostało 580 znaków

2019-03-15 12:17
3
robertz68 napisał(a):

Cześć,
w program w których czasami muszę udowadniać że klient w trakcie używania sam narozrabiał a nie jest to problem systemu stosuję zapisywanie zmian jakie zaszły na formie. Robię to już od dawna i jakoś to działa chociaż kosztuje mnie to dużo pracy.

Wygląd to mnie więcej tak że w bazie tworzę tabelę o nazwie historia a w niej kilka kolumn mniej więcej takich jak:

  • data_utworzenia (typu timestamp),
  • uzytkownik (zalogowany do programu),
  • uzytkownik_system (uzytkownik zalogowany do Windows),
  • komputer (nazwa stanowiska Windows),
  • panel (numer formy która "wpisuje" rekord do bazy),
  • operacje (czyli zapisane otwartym tekstem historie zmian lub ich brak jeśli klient wycofa się ze zmian).

Ten zapis otwartym tekstem mi się nie podoba, a jeśli jest to tak, jak mi się wydaję że jest, to hmmm... do wyrzucenia :D
Może pokaż jak ten log wygląda?

Tak naprawdę wiem wszystko i wiem jakie dane były wcześniej. W ten sposób mogę dokładnie prześledzić co się działo z danymi i kto rozrabiał.

Tak, ale ten otwarty tekst to jest dobry co najwyżej do czytania.
Ale jak byśmy chcieli coś takiego parsować, bo trzeba zrobić cokolwiek z przetwarzaniem takich danych (np. wyszukiwanie) to jest słabo.
Wolałbym trzymać te dane w jakiś strukturyzowany sposób (relacyjny, XML albo JSON), byleby nie czystym tekstem.

Oczywiście wiem że takie rozwiązanie powoduje bardzo duży przyrost danych w bazie ale robię to z pełną świadomością i nie o to tutaj chodzi.

Gdy tworzony jest rekord taka operacja wygląda mniej więcej tak (przykład z ostatniego programu):


procedure Tnowy_model.dodaj;
var
  licznik: integer;
  historia: string;
begin
  try
    with DM.qrytemp, SQL do
    begin
      Close;
      Clear;
      Add('INSERT INTO modele (id_marka, model, opis, utworzyl)');
      Add('VALUES (:id_marka, :model, :opis, :utworzyl)');
      ParamByName('id_marka').AsInteger := id_marka;
      ParamByName('model').AsString := edtnazwa.Text;
      ParamByName('opis').AsString := mmoopis.Text;
      ParamByName('utworzyl').AsString := main.uzytkownik;
      ExecSQL;
    end;

/ciach ten koszmar.../
Dlaczego koszmar?
To chyba jasne, nakład pracy na utrzymanie tego potworka musi być znaczny.
Poza tym - boziu, jak ty obsługujesz błędy? Naprawdę pokazujesz wyjątek w ShowMessage?

Jak napisałem - jakoś to działa ale powiem szczerze że wyjątkowo mi się nie podoba. Po pierwsze te ify po drugie wszystko jest ok gdy pól edycyjnych jest nie więcej niż paręnaście (właśnie mam na pulpicie program w którym jest prawie 200 checkbox-ów).

200 checkboxów?
Nieważne co to jest, ale to zdecydowanie porażka.
Miałem do czyniania dawno temu z pewnym ERPem, który miał takie UI.
To jest nie do używania.

Podzielcie się pomysłem jak to zrobić bardziej zgrabnie, może jakaś uniwersalna klasa która sama "wykryje" wszystkie pola edycyjne i zapamięta ich stan, następnie porówna przed opuszczeniem formy czy coś się zmieniło i zapisze zmiany? W taki powiedzmy sprytniejszy sposób ustawiam stan kontrolek na tej 200 elementowej formie:

  for i := 0 to ComponentCount - 1 do
  begin
    if components[i] is TCheckBox then
      TCheckBox(Components[i]).Checked := False;

    if components[i] is TEdit then
      TEdit(Components[i]).Text := '';
  end;

Zupełnie nie tak!
Piszesz o historii zmian danych, to co tu robią kontrolki?
Po co i dlaczego?
Zakłam, że używasz DataSetów (zresztą pełno ich w przykładowym kodzie, to pewnie są) i kontrolek dbAware (czyli DataSet->DataSource->Kontrolka) - nie LiveBindings.
Skoro tak, to dlaczego nie śledzisz zmian na poziomie zmiany danych?
To jest stosunkowo proste...
Zauważ, ze kontrolka musi się łączyć z datasetem przez TDataSource.
A TDataSource ma ciekawe zdarzenie TDataSource.OnDataChange
I tam w parametrze Field dostaniesz referencję na pole, w którym właśnie zmieniły się dane.
Uważaj, bo to zdarzenie rzuca się często i w różnych przypadkach - np. zmiana aktywnego rekordu.
W takim przypadku parametr Field będzie równy nil.
Musisz potestować.

Pozostaje zabranie tych danych, zapisanie do lokalnej struktury i jeśli user potwierdzi zmiany - zapis zmian do logu.

Robiłem kiedyś coś podobnego w oparciu o triggery w bazie danych.
Sprowadziło się to do napisania generatora triggerów, czyli miałem jakieś GUI w którym mogłem wyklikać co dokładnie chcę śledzić z dokładnością do tabeli i pola.
Gdybyś używał FireDAC'a, to są jeszcze inne sposoby, niektóre na tyle ciekawe, że da się doprowadzić do stanu, aby program pokazał stan dowolnego datasetu z dowonego punktu w historii zmian.
Innymi słowy - możemy sobie np. zobaczyć jak wyglądał jakiś konkretny dokument w danym punkcie czasu.
Ale to oczywiście trochę skomplikowane...

A może jest jakiś gotowiec do tego a ja go nie znam?

Pewnie by się znalazł.. Ale to chyba nie o tu tu chodzi :)
Nie jestem do końca pewny, ale chyba Context Database Extensions pozwalał na coś takiego.

Pozostało 580 znaków

2019-03-15 13:02
0

odpowiadając @wloochacz

dzięki za dołączenie do dyskusji, odpowiadam na twoje pytania i sugestie:

Może pokaż jak ten log wygląda?

Log w bazie łatwo sobie wyobrazić - zwykły rekord ale na pewno nie o to pytałeś. Jeśli chodzi o użyteczność to pomysł zaczerpnąłem z jednego z programów na rynku. Wydaje mi się że całkiem dobrze się sprawdza.
Polega to na tym że mamy podgląd operacji dla wybranego rekordu, czyli nawet w ekstremalnych sytuacjach ile razy można edytować ten sam rekord? 10 - 20 - 50? To i tak jest później do ogarnięcia.
Przykładowo wygląda to tak:
screenshot-20190315123232.png

Oczywiście, historia operacji dostępna jest tylko dla użytkownika o odpowiednich uprawnieniach ale to nie ma znaczenia.

Ale jak byśmy chcieli coś takiego parsować, bo trzeba zrobić cokolwiek z przetwarzaniem takich danych (np. wyszukiwanie) to jest słabo.
Wolałbym trzymać te dane w jakiś strukturyzowany sposób (relacyjny, XML albo JSON), byleby nie czystym tekstem.

Jak pisałem, nie ma potrzeby pracować na tych danych. Wyszukanie jest proste, prawy klawisz myszy na rekordzie i wszystko widać. No może chwila przewijania.

Poza tym - boziu, jak ty obsługujesz błędy? Naprawdę pokazujesz wyjątek w ShowMessage?

Wiem że istnieje inny sposób ale uwierz mi że zamiast wielkiego komunikatu z mnóstwem informacji o numerze wyjątku itp. znacznie lepiej jest powiadomić klienta że coś po prostu się nie udało. To akurat rozwiązanie jest dla mechaników samochodowych dla których przeczytanie komunikatu dłuższego niż jedno zdanie po polsku graniczy z cudem - nie obrażając nikogo.

Mam klienta u którego jest jakaś optima i czasami jak wyskakuje błąd dzwonią do mnie że coś poszło nie tak. Proszę o odczytanie komunikatu a oni mi mówią że już kliknęli i zamknęli komunikat zanim go przeczytali ale na pewno jest coś nie tak. Powód - po prostu komunikat jest zbyt duży.

200 checkboxów?

Tutaj nie będę dyskutował, takie jest zlecenie, tak było w przypadku ręcznej rejestracji zleceń i tak ma być w programie. Chodzi o to że mechanicy chcą po prostu wyklinać wszystko co mają zrobić na jednej formie. Na szczęście tylko kilka pól podlega jakiejkolwiek walidacji.

Nieważne co to jest, ale to zdecydowanie porażka.

Jak pisałem, nie moja decyzja - klient jest zadowolony i to jest najważniejsze.

  for i := 0 to ComponentCount - 1 do
  begin
    if components[i] is TCheckBox then
      TCheckBox(Components[i]).Checked := False;

    if components[i] is TEdit then
      TEdit(Components[i]).Text := '';
  end;

Zupełnie nie tak!
Piszesz o historii zmian danych, to co tu robią kontrolki?
Po co i dlaczego?

Ja chyba po prostu źle zacząłem. Niepotrzebnie wtrąciłem o bazach, kontrolkach, wyjątkach itp.

Jakbym miał zacząć od nowa to napisał bym temat: Jak rejestrować zmiany na kontrolkach. Bo właśnie to mnie interesuje a że później zapisuję to do bazy to już rzecz wtórna.

Zakłam, że używasz DataSetów (zresztą pełno ich w przykładowym kodzie, to pewnie są) i kontrolek dbAware (czyli DataSet->DataSource->Kontrolka) - nie LiveBindings.
Skoro tak, to dlaczego nie śledzisz zmian na poziomie zmiany danych?

Jak powyżej, interesuje mnie zmiany na kontrolkach. Zapisuję nawet że klient wszedł w edycję ale nic nie zmienił - taka trochę totalna inwigilacja.

To jest stosunkowo proste...
Zauważ, ze kontrolka musi się łączyć z datasetem przez TDataSource.
A TDataSource ma ciekawe zdarzenie TDataSource.OnDataChange
I tam w parametrze Field dostaniesz referencję na pole, w którym właśnie zmieniły się dane.
Uważaj, bo to zdarzenie rzuca się często i w różnych przypadkach - np. zmiana aktywnego rekordu.
W takim przypadku parametr Field będzie równy nil.
Musisz potestować.

Dzięki za sugestię, na pewno zerknę na to. Czuję że może mi się w przyszłości przydać.

Podsumowując:
Szukam prostego sposobu na zapisanie co się zmieniło na formie od momentu OnShow do OnClose (czy tam OnHide)?

edytowany 1x, ostatnio: robertz68, 2019-03-15 13:04

Pozostało 580 znaków

2019-03-15 13:14
1

Chyba szukasz rozwiązania o które pytałem jakiś czas temu w tym wątku
Raportowanie interakcji użytkownika.
Możesz sprawdzić proponowane rozwiązania, ja niestety odłożyłem temat i pewnie koło wakacji będe do tego wracał.

Pozostało 580 znaków

2019-03-15 13:25
0
robertz68 napisał(a):

Piszesz o historii zmian danych, to co tu robią kontrolki?
Po co i dlaczego?

Ja chyba po prostu źle zacząłem. Niepotrzebnie wtrąciłem o bazach, kontrolkach, wyjątkach itp.

Jakbym miał zacząć od nowa to napisał bym temat: Jak rejestrować zmiany na kontrolkach. Bo właśnie to mnie interesuje a że później zapisuję to do bazy to już rzecz wtórna.

Nie zrozumiałeś mnie.
Nie pisałem o tym, jak zapisać dane do bazy, tylko jak śledzić zmianę wartości na warstwie danych.
Nie na kontrolce, bo ta co do zasady ma zapewnić interakcję z użytkownikiem.
Poza tym, zawsze możesz wymienić kontrolkę, a wtedy...

Jak rozumiem, w tym zrzucie ekranowym (pok oknem z informacją o logu), widzimy jakiegoś DBGrida, który jest podpięty przez TDataSource do TDataSet?
Jeżeli tak, to po kiego interesują cię kontrolki?

edytowany 1x, ostatnio: wloochacz, 2019-03-15 13:27
podejrzewam, że dodawanie nowego elementu/dokumentu to TForm.Show i kontrolki tam. - lampasss 2019-03-15 13:35
Wybacz, ale nie rozumiem jak ma się to co napisałeś, do postu? - wloochacz 2019-03-15 13:38
Mówiąc kontrolki mam na myśli wszystko co leży na formie, TEdit, TCheckBox, TComboBox itp. Interesuje mnie aby w czasie OnShow formy zanotować ich stan czy też przechowywaną zawartość a przed zamknięciem formy sprawdzić co się zmieniło (jeśli się zmieniło) i odnotować te zmiany w jakimś logu (nie ważne jakim). Ale uwaga - nie chcę tego robić ręcznie jak teraz tylko użyć może właśnie czegoś z mojego przykładu, czyli pętli po wszystkich TComponent? Dlatego właśnie rozpocząłem ten temat, nieśmiało prosząc o pomoc i sugestię. - robertz68 2019-03-15 13:41
W sumie niech autor się wypowie, bo nie chcę dalej zgadywać, ale podejrzewam że nie działa na DataSecie bezposrednio. Czyli odpowiedź na Twoje pytanie może brzemieć, że autor OWSZEM używa "DBGrida, który jest podpięty przez TDataSource do TDataSet" ale dane do niego (dataseta) dostarcza z editów i checkboxów a nie dbgrida bezposrednio. Zresztą mało ważne. - lampasss 2019-03-15 13:42
@robertz68 Proszę, nie odpowiadaj w komentarzu... I odpowiedz mi na moje pytanie. Czy to jest na pewno TEdit czy TDBEdit? A skoro nie TDBEdit, to dlaczego tak? - wloochacz 2019-03-15 13:43

Pozostało 580 znaków

2019-03-15 13:34
0
robertz68 napisał(a):

Poza tym - boziu, jak ty obsługujesz błędy? Naprawdę pokazujesz wyjątek w ShowMessage?

Wiem że istnieje inny sposób ale uwierz mi że zamiast wielkiego komunikatu z mnóstwem informacji o numerze wyjątku itp. znacznie lepiej jest powiadomić klienta że coś po prostu się nie udało. To akurat rozwiązanie jest dla mechaników samochodowych dla których przeczytanie komunikatu dłuższego niż jedno zdanie po polsku graniczy z cudem - nie obrażając nikogo.

Mam klienta u którego jest jakaś optima i czasami jak wyskakuje błąd dzwonią do mnie że coś poszło nie tak. Proszę o odczytanie komunikatu a oni mi mówią że już kliknęli i zamknęli komunikat zanim go przeczytali ale na pewno jest coś nie tak. Powód - po prostu komunikat jest zbyt duży.

Zupełnie nie o to chodzi...
Powiedz co tobie, jako programiście odpowiedzialnym za ten system, daje informacja o tym, że coś się nie udało?
Niewiele, a praktycznie nic.
Mi chodziło raczej o jakiś mechanizm łapania błędów, jak. EurekaLog, MadExcept czy inne.

Ja np. używam Slacka do monitorowania serwerów on-line u klientów. Ale to jest informacja dla nas, nie dla klienta.
I też dostaję info o tym, ze coś się nie udało.
a wygląda to np. tak (no fuckup normalnie :D):
dfSlack.png

Zgadza się z tobą całkowicie, odpowiednie informacje o błędach to przydatna sprawa. Jednak w tym przypadku aplikacja jest naprawdę prosta i mi chodziło tylko o poinformowanie klienta że nie udał się zapis do bazy - nawet nie ważne dlaczego. Tylko tyle. Jak bym miał problemy z aplikacją to na pewno bym dołączył jakąś klasę Exception i logowałbym sobie błędy. Tego nie potrzebuję ale dziękuję jak zawsze za podpowiedź. Niestety Eureki nie mam na razie. - robertz68 2019-03-15 13:46
Ale jest JCL, a tam są (poniekąd) podobne możliwości. Tak czy siak, jest to na tyle pomocne, że decydowanie warto. Zobacz np. tu: https://blog.dummzeuch.de/2014/03/08/using-jcldebug/ - wloochacz 2019-03-15 16:43

Pozostało 580 znaków

2019-03-15 13:34
0

Ja mam rozwiązane logowanie na dwa sposoby, jeden podobny jak podał autor tego wątku. Natomiast drugi to mam pozakładane „trigery” na bd oracle które to logują zmiany do tablicy audytującej.
Postaram się później zamieścić przykład.
Oczywiści logowanie konfigurowane na parametr.

Pozostało 580 znaków

2019-03-15 14:02
0

No i zapomniałem o ważnej sprawie która na pewno mocno zmieni sytuację.

Od dość dawna pisząc aplikację nie używam kontrolek bazodanowych (poza DBGridami). Wszystko inne robię na kontrolkach zwykłych. Dlaczego zapytacie? A no dlatego że jak zaczynałem pisać swoje pierwsze programiki lata temu to nawet nie słyszałem o transakcjach itp. Powodowało to że otwarcie formy edycyjnej z tym samych rekordem na dwóch stanowiskach robiło wielki bałagan.
Teraz po prostu pobierając rekord do edycji robię kopię jego danych w zwykłych kontrolkach edycyjnych i pracuję na nimi jak długo chcę.
Inny użytkownik także może pobrać ten rekord do edycji (mogę go powiadamiać że już gdzieś jest edytowany lub nie, w zależności czy tego potrzebuję czy nie). W każdym razie każdy z nich może zapisać swoją kopię danych a obowiązywać będzie i tak ostatnia zmiana. Oczywiście, historia zapisana w logu bardzo wtedy się przydaje.

Tutaj uwaga, ten sposób nie do końca się sprawdza z tabelami podrzędnymi. Tam po prostu blokuję edycję już otwartego dokumentu. Pozwalam tylko na podgląd.

Uwaga dodatkowa, nigdy nic nie usuwam z bazy, rekordy usunięte po prostu oznaczam jako usunięte ale je pozostawiam. Tak więc nawet otwarcie rekordu na jakimś stanowisku i trzymanie takiej formy przez kilka dni nic nie zmieni nawet gdy ktoś inny go w tym czasie "niby" usunie. Użytkownik zapisze swoją kopię bez żadnego wyjątku, tylko że już go nie zobaczy w gridzie po odświeżeniu danych.

Takie rozwiązanie jest bardzo bezpieczne w moim mniemaniu dla danych (w końcu tylko przez chwilę wykonuję na nich operacje zapisu) ale kosztuje trochę pracy niestety. Już się do tego przyzwyczaiłem ale nie mogę przeżyć tej historii operacji że zajmuje mi tyle pracy.
Dlatego właśnie proszę o pomoc.

Pozostało 580 znaków

2019-03-15 14:53
1
robertz68 napisał(a):

No i zapomniałem o ważnej sprawie która na pewno mocno zmieni sytuację.

Od dość dawna pisząc aplikację nie używam kontrolek bazodanowych (poza DBGridami). Wszystko inne robię na kontrolkach zwykłych. Dlaczego zapytacie? A no dlatego że jak zaczynałem pisać swoje pierwsze programiki lata temu to nawet nie słyszałem o transakcjach itp.

No to ja się wypisuję.
Do drutem wiązanych problemów, nadają się tylko drutem wiązane rozwiązania.

Ale tak sobie to czytam i...
Powiedz, Ty nigdy nie używałeś CachedUpdates?
Wiesz co to jest i do czego służy?
Gdybyś wiedział, to raczej nie zrobiłbyś tego tak, jak to zrobiłeś...

Powodowało to że otwarcie formy edycyjnej z tym samych rekordem na dwóch stanowiskach robiło wielki bałagan.

Nie do pojęcia, jak to możliwe?

Teraz po prostu pobierając rekord do edycji robię kopię jego danych w zwykłych kontrolkach edycyjnych i pracuję na nimi jak długo chcę.

A na Datasetach nie możesz?
Oczywiście, ze możesz.
Możesz pracować całkowicie w trybie bez połączenia z bazą danych.
Ba, możesz mieć nawet undo/redo.

Inny użytkownik także może pobrać ten rekord do edycji (mogę go powiadamiać że już gdzieś jest edytowany lub nie, w zależności czy tego potrzebuję czy nie). W każdym razie każdy z nich może zapisać swoją kopię danych a obowiązywać będzie i tak ostatnia zmiana. Oczywiście, historia zapisana w logu bardzo wtedy się przydaje.

To jest, oczywiście, standardowe podejście z blokadami optymistycznymi. Czyli ten lepszy, kto ostatni.
Ale to nie jest dobre podejście dla wielodostępu do edycji danych.
Chyba nie trzeba tłumaczyć dlaczego?

Tutaj uwaga, ten sposób nie do końca się sprawdza z tabelami podrzędnymi. Tam po prostu blokuję edycję już otwartego dokumentu. Pozwalam tylko na podgląd.

Moim zdaniem, to się w ogóle nie sprawdza, jeśli do tej samej instancji danych, może mieć dostęp wielu użytkowników w tym samym czasie, którzy chcą ją zmienić.
Czy to są tabele podrzędne, to bez znaczenia.

Uwaga dodatkowa, nigdy nic nie usuwam z bazy, rekordy usunięte po prostu oznaczam jako usunięte ale je pozostawiam. Tak więc nawet otwarcie rekordu na jakimś stanowisku i trzymanie takiej formy przez kilka dni nic nie zmieni nawet gdy ktoś inny go w tym czasie "niby" usunie. Użytkownik zapisze swoją kopię bez żadnego wyjątku, tylko że już go nie zobaczy w gridzie po odświeżeniu danych.

Nigdy nie rozumiałem takiego podejścia, ale OK.
Ja pozwalam na usuwanie danych, ale tylko takich, które z punktu widzenia logiki biznesowej nie mają (najczęściej - jeszcze nie mają) żadnego znaczenia.

Takie rozwiązanie jest bardzo bezpieczne w moim mniemaniu dla danych (w końcu tylko przez chwilę wykonuję na nich operacje zapisu) ale kosztuje trochę pracy niestety. Już się do tego przyzwyczaiłem ale nie mogę przeżyć tej historii operacji że zajmuje mi tyle pracy.
Dlatego właśnie proszę o pomoc.

To nie jest ani wygodne, ani jakieś tam bezpieczne.
Tak naprawdę zrobiłeś sobie kawał roboty zupełnie niepotrzebnie.

A teraz inaczej - pokaż proszę jak uzupełniasz te kontrolki wartościami z bazy?
Ale jeśli po prostu czytasz dane z datasetu i mozolnie jest przepisujesz do poszczególnych kontrolek, to jak dla mnie nic nie musisz pokazać.
Z takim "patentem" da się zrobić dwie rzeczy:

  1. Wywalić to w cholerę, bo jest to istny horror-szoł.
  2. Dalej drutować w podobny sposób...

Pozostało 580 znaków

2019-03-15 15:20
0

Cóż mam powiedzieć. Robię wszystko dokładnie w taki sposób jaki zganiłeś w poście powyżej. Nie mogę nawet nic napisać na swoją obronę no może poza tym że sam do tego kiedyś doszedłem i wyrobiłem sobie pewne reguły, ale jak widać nie zrobiłem tego zgodnie z zasadami.

Mógłbym poprosić o jakieś sugestie, jak robić to prawidłowo ale ... chyba już sobie odpuszczę - nie mam siły :).
W każdym razie dziękuję za to co napisałeś.

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

1 użytkowników online, w tym zalogowanych: 0, gości: 1, botów: 0