Announcement

Collapse
No announcement yet.

Refresh oder FetchAll mit IBX

Collapse
X
  • Filter
  • Time
  • Show
Clear All
new posts

  • Refresh oder FetchAll mit IBX

    Wie kann ich erreichen, daß auf einem Client beim Betätigen der Refresh
    Taste des Navigators beim Client die aktuellen Datensätze des Servers
    angezeigt werden und nicht die aus dem Client Puffer?
    Was bedeutet in diesem Zusammenhang das TIBTable Property
    ForcedRefresh?
    Welchen Befehl soll ich benutzen? Refresh oder FetchAll?
    Anmerkung:
    Änderungen werden mit einem Soft Commit (CommitRetaining)
    eingetragen bzw. mit einem Soft RollBack (RollbackRetaining) verworfen.
    Ich arbeite mit InterBase 6.0 Release.

  • #2
    Hallo,

    auch wenn die Kompatibilitätskomponenenten TIBTable und TIBQuery am Anfang recht bequem sind, sollte man versuchen, gleich den Umstieg auf <b>TIBDataSet</b> zu machen. Dort steht die Eigenschaft <b>RefreshSQL</b> zur Verfügung, mit der eine SELECT-Abfrage für das Aktualisieren des angezeigten Datensatzes definiert werden kann. Und mit der Eigenschaft <b>ForcedRefresh</b> sorgt man dazu, das IBX intern bei jeder Änderung die in RefreshSQL definierte Abfrage neu ausführt, um die angezeigten Daten zu aktualisieren.

    Jeder Aufruf von <b>CommitRetaining</b> sorgt dafür, dass der InterBase die OIT (Oldest Interesting Transaction) nicht hochzählen darf und somit mit immer mehr werdenen Generationen von Datenbankseiten hantieren muss. Immer dann, wenn ein Dialog geschlossen oder eine Arbeitsaufgabe abgeschlossen wird, sollte Commit aufrufen werden. Nur dann, wenn die Position in der offenen Datenmenge gehalten werden muss, ist CommitRetaining tolerierbar. Bei CommitRetaining wird außerdem jeder neue Datensatz ans Ende der Datenmenge einsortiert und somit auch als letzter Datensatz im DBGrid angezeigt. Dies gilt auch dann, wenn die SELECT-Abfrage eine ORDER BY-Klausel enthielt.

    In einem Beispiel könnte das so aussehen: Vor dem Öffnen der Datenmenge wird eine Transaktion gestartet.
    <pre>
    procedure TForm1.IBDataSet1BeforeOpen(DataSet: TDataSet);
    begin
    IBDataSet1.Transaction.StartTransaction;
    end;
    </pre>
    Nach dem Posten wird - je nach Radiobutton-Einstellung - entweder Commit oder CommitRetaining aufgerufen:
    <pre>
    procedure TForm1.IBDataSet1AfterPost(DataSet: TDataSet);
    begin
    if FCommitRetaining then
    IBDataSet1.Transaction.CommitRetaining
    else
    begin
    IBDataSet1.Transaction.Commit;
    IBDataSet1.Active := True;
    end;
    end;
    </pre>
    Die verwendete <b>TIBDataSet</b>-Instanz definiert unter anderem die Eigenschaft <b>RefreshSQL</b>:
    <pre>
    object IBDataSet1: TIBDataSet
    Database = IBDatabase1
    Transaction = IBTransaction1
    AfterPost = IBDataSet1AfterPost
    BeforeOpen = IBDataSet1BeforeOpen
    BufferChunks = 1000
    CachedUpdates = False
    DeleteSQL.Strings = (
    'delete from INSDEMO'
    'where'
    ' ID = :OLD_ID')
    InsertSQL.Strings = (
    'insert into INSDEMO'
    ' (ID, WERT)'
    'values'
    ' (:ID,:WERT)')
    RefreshSQL.Strings = (
    'Select '
    ' ID,'
    ' WERT'
    'from INSDEMO '
    'where'
    ' ID = :ID')
    SelectSQL.Strings = (
    'SELECT ID, WERT FROM INSDEMO'
    'ORDER BY ID')
    ModifySQL.Strings = (
    'update INSDEMO'
    'set'
    ' WERT = :WERT'
    'where'
    ' ID = :OLD_ID')
    Left = 96
    Top = 128
    object IBDataSet1ID: TIntegerField
    FieldName = 'ID'
    end
    object IBDataSet1WERT: TIBStringField
    FieldName = 'WERT'
    end
    end
    </pre>
    Wenn nun zwei Benutzer dieses Programm starten, erhält der andere immer dann den neuen (vom anderen Benutzer geänderten Wert), wenn er <b>diesen Datensatz auswählt</b> (DBGrid) und den Refresh-Button im DBNavigator anklickt. Der Aufruf von FetchAll aktualisiert den Client-Puffer jedoch nicht. Wenn die Anzeige aller Datensätze im DBGrid aktualisiert werden soll (und nicht nur die Anzeige des aktuell ausgewählten Datensatzes), dann ist Commit die richtige Wahl (da dann die Datenmenge neu geöffnet werden muss).

    P.S: Das oben geschilderte Verhalten gilt nur für <b>ReadCommitted</b>, aber nicht für RepeatableRead!
    <pre>
    read_committed
    rec_version
    nowait
    </pre&gt

    Comment


    • #3
      Hallo Herr Kosch,

      zunächst einmal vielen Dank für Ihre Hilfestellung und das ausführliche Beispiel. Natürlich wäre eine
      Umstellung der TIBTable- u. der TIBQuery-Komponenten auf TIBDataSet wünschenswert. Diese
      Umstellung wollten wir seinerzeit bereits realisieren. Nun ist es jedoch so, daß unsere
      Geschäftsleitung uns einen sehr engen Entwicklungszeitrahmen vorgegeben hat (Marketing-
      und betriebwirtschaftliche Aspekte). Die Applikation wurde in der 1. Generation mit Delphi 1
      entwickelt und in der 2. Generation mit Delphi 3 weiterentwickelt. Zu jener Zeit gab es noch
      keine IBX-Komponenten. Somit bedienten wir uns der nativen Delphi-Komponenten und der
      BDE. Der Grund, weswegen wir die Komponenten mit den IBX-Komponenten ersetzt haben,
      ist der, daß die Vorgabe der Geschäftsleitung für die Weiterentwicklung der 3. Generation
      der Applikation lautete: Keine BDE mehr! Da liegt auch der Grund, weswegen wir die
      Befehle "CommitRetaining" und "RollbackRetaining" eingesetzt haben. In der BDE-Version
      bewirkte Commit und Rollback, daß die Datenmenge weiter geöffnet blieb und nicht,
      wie jetzt, bei den gleichnamigen Befehlen geschlossen wird. Kurzum, es fehlt uns die
      nötige Zeit, die Applikation jetzt auf TIBDataSet umzuschreiben. In der Applikation
      gibt es sehr viele Master/Detail-Verknüpfungen. Die Properties "MasterSource" und
      "MasterFields" sind jedoch in der Komponente TIBDataSet nicht vorhanden. Ausserdem
      arbeiten wir wegen der Zugriffsrechte im Programm mit sehr vielen Filtern. Nun könnte man
      hier erst die SQL-Befehle in "RefreshSQL" aufrufen und danach den Filter. Aber auch diese
      Ergänzung ist in dieser Applikation sehr zeitintensiv. Eine Umstellung auf TIBDataSet um
      letztendlich die gesamte Anwendung wegen der Schnelligkeit auf SQL umzustellen, ist
      vor Juli 2001 nicht denkbar. Aber auch TIBTable generiert effektive SQL-Befehle beim Öffnen
      der Datenmenge, so ist es im Moment noch vertretbar, daß die Applikation, wie bisher,
      mit diesen Komponenten arbeitet, bevor sie dann im nächsten Jahr umgestellt werden
      wird. Ihr Beispiel mit "Commit" (Transaction-Einstellung: read_committed, rec_version, nowait wird
      von uns schon benutzt) ist einleuchtend und wird sofort angewendet. Bitte nennen Sie mir aufgrund
      der oben aufgeführten Erklärung eine Lösung für das effektive und sichere Refreshen mit TIBTable.
      Ein Beispiel für TIBQuery haben wir in Ihrem Buch "Client/Server - Datenbankentwicklung mit
      Delphi" gefunden. Aber gibt es nicht eine elegantere oder ähnliche Methode, bspw. wie von Ihnen
      oben beschrieben

      Comment


      • #4
        Sehr geehrter Herr Kosch,
        nochmals vielen Dank für Ihre ausführliche Hilfestellung! Ich habe dazu noch eine zusätzliche Frage:
        Commit schließt sämtliche geöffnete Tabellen, was natürlich in unserer
        MDI- Anwendung fatale Folgen hat. Der Benutzer muß (Anforderung ans
        Programm) beliebig viele Erfassungsformulare gleichzeit öffnen und
        gleichzeitig darin erfassen bzw. ändern können (nur begrenzt durch seinen Speicher und seine Ressourcen). Zusätzlich benötigt die Applikation noch einige Systemtabellen, die ständig geöffnet sein müssen (Bsp.: LOGGIN, wo der Benutzer beim Einloggen erfaßt wird und beim Ausloggen ausgetragen wird, incl. Zeiten). All diese Tabellen
        werden durch Commit geschlossen. Die spontane Lösung die mir dazu einfällt ist, für jede Tabelle (> 100) eine eigene TransAction zuzuweisen. Meine Frage: Was hätte das für Auswirkungen auf die Applikation? Ich bedanke mich für Ihre Mühen

        Comment


        • #5
          Hallo,

          mit der Umstellung einer bereits vorhandenen Anwendung auf IBX haben Sie sich wirklich gleich den grössten Brocken um den Hals gehängt ;-)<br>
          Zumal ein eng gesteckter Terminrahmen dafür sorgt, dass sich die Entwickler nicht richtig in die neue IBX-Philosophie einarbeiten können. Das Problem "MasterSource und MasterField s fehlt bei TIBDataSet" ist eine Folge davon. Bei TIBDataset wird diese Verbindung nicht benötigt, wie das folgende Beispiel demonstriert:
          <pre>
          object IBDataSet1: TIBDataSet
          Database = IBDatabase1
          Transaction = IBTransaction1
          BufferChunks = 1000
          CachedUpdates = False
          DeleteSQL.Strings = (
          'delete from CUSTOMER'
          'where'
          ' CUST_NO = :OLD_CUST_NO')
          InsertSQL.Strings = (
          'insert into CUSTOMER'
          ' (CUSTOMER, CITY)'
          'values'
          ' (:CUSTOMER, :CITY)')
          RefreshSQL.Strings = (
          'Select '
          ' CUST_NO,'
          ' CUSTOMER,'
          ' CITY '
          'from CUSTOMER '
          'where'
          ' CUST_NO = :CUST_NO')
          SelectSQL.Strings = (
          'SELECT CUST_NO, CUSTOMER, CITY FROM CUSTOMER')
          ModifySQL.Strings = (
          'update CUSTOMER'
          'set'
          ' CUSTOMER = :CUSTOMER,'
          ' CITY = :CITY'
          'where'
          ' CUST_NO = :OLD_CUST_NO')
          Active = True
          Left = 128
          Top = 16
          object IBDataSet1CUST_NO: TIntegerField
          FieldName = 'CUST_NO'
          end
          object IBDataSet1CUSTOMER: TIBStringField
          FieldName = 'CUSTOMER'
          Size = 25
          end
          object IBDataSet1CITY: TIBStringField
          FieldName = 'CITY'
          Size = 25
          end
          end
          object DataSource1: TDataSource
          DataSet = IBDataSet1
          Left = 168
          Top = 16
          end
          object IBDataSet2: TIBDataSet
          Database = IBDatabase1
          Transaction = IBTransaction1
          BufferChunks = 1000
          CachedUpdates = False
          DeleteSQL.Strings = (
          'delete from SALES'
          'where'
          ' CUST_NO = :OLD_CUST_NO')
          InsertSQL.Strings = (
          'insert into SALES'
          ' (ORDER_DATE)'
          'values'
          ' (:ORDER_DATE)')
          RefreshSQL.Strings = (
          'Select CUST_NO, TOTAL_VALUE, ORDER_DATE '
          'from SALES '
          'where'
          ' CUST_NO = :CUST_NO')
          SelectSQL.Strings = (
          'SELECT CUST_NO, TOTAL_VALUE, ORDER_DATE '
          'FROM SALES'
          'WHERE CUST_NO = :CUST_NO')
          ModifySQL.Strings = (
          'update SALES'
          'set'
          ' ORDER_DATE = :ORDER_DATE'
          'where'
          ' CUST_NO = :OLD_CUST_NO')
          Active = True
          DataSource = DataSource1
          Left = 128
          Top = 56
          object IBDataSet2CUST_NO: TIntegerField
          FieldName = 'CUST_NO'
          end
          object IBDataSet2TOTAL_VALUE: TIBBCDField
          FieldName = 'TOTAL_VALUE'
          Precision = 9
          Size = 2
          end
          object IBDataSet2ORDER_DATE: TDateTimeField
          FieldName = 'ORDER_DATE'
          end
          end
          object DataSource2: TDataSource
          DataSet = IBDataSet2
          Left = 168
          Top = 56
          end
          </pre>
          Die zweite TIBDataSet-Instanz für die Detail-Tabelle stellt die Verknüpfung zur Master-Tabelle über zwei Punkte her: <br>
          1. Eigenschaft <b>DataSource</b> (DataSource = DataSource1) verweist indirekt auf die erste TIBDataSet-Instanz für die Master-Tabelle <br>
          2. <b>SelectSQL</b>: Der Parameter-Name <i>CUST_NO</i>, der auch als Spaltenname in der ersten TIBDataSet-Instanz vorkommt, stellt eine automatische Verbindung der beiden Datenmengen her. <br>
          Wird nun der Datensatz in der Master-TIBDataSet-Instanz gewechselt, führt IBX die SELECT-Abfrage der Detail-TIBDataSet-Instanz <b>automatisch</b> neu aus. Somit ist das Verhalten analog zu den über MasterSource/MasterFields verknüpften TIBTable-Instanzen.

          Eine Aktualisierung des aktuell ausgewählten Datensatzes mit TIBTable ist nur dann möglich, wenn eine neue Transaktion gestartet wird. In diesem Fall muss jedoch TIBTable geschlossen und neu geöffnet werden.
          Daher ist TIBDataSet für Ihren Verwendungszweck besser

          Comment


          • #6
            ... besser geeignet.

            Die Frage der vielen offenen Datenmenge ist ein anderes Problem. Denn nicht die offnene Datenmenge wird benötigt, sondern die Anzeige der Daten. Daher kann man drei Wege vorsehen:

            1. Verzicht auf die datensensitiven Komponenten (TDBGrid, TDBEdit), indem die Daten über TEdit usw. auch dann noch angezeigt werden, wenn die Datenmenge (SELECT) bereits wieder geschlossen wurde.

            2. Verwenden von TClientDatasets als "temp. Speichertabellen", die die Datenmenge für die datensensitiven Komponenten (TDBGrid, TDBEdit) bereitstellen. Auch hier kann die originale Datenmenge sofort nach dem Laden der Daten in die TClientDataSet-Instanz geschlossen werden.

            3. Verwenden von mehreren Transaktion-Kontexten, wobei es hier bestimmt Grenzen gibt, was die sinnvolle Anzahl angeht

            Comment


            • #7
              Hallo Herr Kosch,

              ich überlege gerade was sinnvoller ist:

              1. nur mit TEdit etc. Controls arbeiten oder
              2. Daten temporär in TClientDataSet speichern

              welche Methode halten Sie für Sinnvoller und welche verbraucht mehr Systemresourcen?

              Vielen Dank für Ihre Antwort

              Comment


              • #8
                Hallo,

                man hat es hier mit einer Abwägung zwischen Performance und Bequemlichkeit zu tun. Selbstverständlich sind Zugriffe ausschließlich über Stored Procedures (SELECT, INSERT, UPDATE, DELETE) und die Anzeige ausschließlich in TEdits etc. das schnellste und ressourcensparendste, was man heute bekommen kann. Dafür muss man jedoch auf die RAD-Vorteile (Rapid Application Development) von Delphi teilweise verzichten.

                Auf der anderen Seite ist das Zwischenpuffern in TClientDataSets für uns bequemer und in der Entwicklung schneller, dafür aber auch in der Ausführung deutlich langsamer (vom RAM-Verbrauch nicht zu reden). Allerdings verschiebt sich heute bei den üblichen CPUs die Waage täglich neu. Denn auch <b>DBExpress</b> (Borlands neue Komponenten für Kylix und Delphi 6) und <b>ADO</b> (clUseClient-RecordSet) verwenden auf der Client-Seite die Zwischenpufferung im Arbeitsspeicher (TClientDataSet-Prinzip).

                Ich würde daher, wenn die Darstellung im TDBGrid gefordert wird, auf die Zwischenpufferung (TClientDataSet, DBExpress oder ADO) zurückgreifen. Werden nur TDBEdits benötigt, können diese jedoch ohne großen Mehraufwand durch TEdits ersetzt werden, so dass in diesem Fall die Zwischenpufferung nicht unbedingt notwendig ist

                Comment


                • #9
                  Vielen Dank für Ihre Antwort.

                  Für den Einsatz von TClientDataSet als temp. Tabelle ist ja keine MIDAS Lizenz nötig. Unter http://community.borland.com/article/0,1410,22571,00.html steht das eine Two-Trier Anwendung in der einen TDataSetProvider Komponente benutzt wird, ebenfalls keine MIDAS Lizenz benötigt - können Sie das bestätigen

                  Comment


                  • #10
                    Hallo,

                    ja - dem ist so. Da ab Delphi 6 die TClientDataSet-Komponente auch in der Professional-Version von Delphi vorhanden sein muss, da DBExpress nur mit TClientDataset zusammen funktioniert, wird sich diese Auslegung auch in Zukunft nicht verschlechtern

                    Comment

                    Working...
                    X