[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ]

SQL Server Isolation Levels

Die alles entscheidende Frage ist nun: "Wenn ein Lesevorgang innerhalb einer Transaktion einen "Read" Lock absetzt, wie lange soll dieser Lock bestehen bleiben?" Je länger ein "Read" Lock bestehen bleibt, desto eher ist meine eigene Transaktion "isoliert" von anderen. Als Nachteil erweist sich natürlich einmal mehr die Verfügbarkeit des Systems für andere Benutzer.

Genau aus dem Grund ist es möglich, selber zu beeinflussen, welchen "Isolation" Level man bzgl. der Locks wünscht:
Isolation Level  Description 
Read Uncommitted 
Die Transaktion kann alle Daten lesen, unabhängig davon, ob nun ausstehende "Write" Locks vorhanden sind oder nicht. Zusätzlich wird die Transaktion in diesem Modus keine eigenen "Read" Locks absetzen. Dieser Isolation Level bringt natürlich die beste Performance ist aber anfälliger auf Dateninkonsistenz denn dieser Isolation Level ist der am wenigsten Restriktive und kann zu sogenannten "Dirty Reads" führen, d.h. dass Daten, welche eigentlich "nicht stimmig sind" und von anderen Transaktionen noch nicht bestätigt wurden (und deshalb evtl wieder zurückgestellt werden!) trotzdem gelesen werden können! 
Read Committed 
Hier kann die Transaktion nur Daten lesen, welche bereits "committed" wurden, d.h. es muss gewartet werden, bis offene "Write" Locks abgeschlossen sind. Für das Lesen von Daten werden neue "Read" Locks abgesetzt, welche aber bereits nach Abschluss des SQL Statements (d.h. vor Ende der Transaktion!) wieder freigegeben werden. Es ist nicht sichergestellt, dass ein erneutes Lesen derselben Daten innerhalb der Transaktion zu demselben Resultat führt. 
Dieser Isolation Level ist die Standardeinstellung für ADO und via VFP(!) wenn NICHT aus MTS heraus betrieben! 
Repeatable Read 
Analog zu "Read Committed" mit der Ausnahme, dass die "Read" Locks bis ans Ende der Transaktion aufrecht erhalten werden. SQL Server 6.5 unterstützt diesen Level nicht, er stellt in einem solchen Fall auf "Serializable" um. Beim Isolation Level "Repeatable Read" werden alle in einer Query bezogenen Daten gelockt ("Read" bzw. "Write" Locks) aber andere Benutzer und Transaktionen können neue Datensätze in die betroffenen Tabellen weiterhin einfügen, weshalb ein erneutes Lesen innerhalb der Transaktion diese neuen Datensätze sehen würde. 
Serializable 
Analog zu "Repeatable Read" wobei zusätzlich sichergestellt wird, dass selbst bei mehrmaligem Ausführen von ein und demselben SQL Statement immer dieselben Resultate gesehen werden. Es wird verhindert, dass neue Datensätze angelegt werden (dass bestehende nicht entfernt werden können, wird ja bereits durch die Locks sichergestellt), solange die Transaktion läuft. Dies wird durch "Table" oder "Index" Locks sichergestellt. 
Dieser Isolation Level ist der Standard bei allen MTS Komponenten!

Das Setzen des Transaction Isolation Levels erfolgt mit folgender TSQL Anweisung, welche pro Connection via SQL Pass Through gesetzt werden kann:

    SET TRANSACTION ISOLATION LEVEL REPEATABLE READ

SQL Server Transaktionen aus MTS

Für diejenigen, welche in die n-Tier Entwicklung unter MTS einsteigen, ist es wichtig zu verstehen, dass für das Sicherstellen von Transaktionen aus dem MTS heraus der DTS (Distributed Transaction Coordinator) verwendet wird. DTS wird auf den Maschinen, auf welchen der entsprechende SQL Server läuft, als Service betrieben. Zusammen mit der oben gewonnenen Erkenntnis, dass der Isolation Level "Serializable" der Standard für alle Transaktionen aus MTS heraus ist, ist sofort klar, dass Performance Aspekte im MTS Umfeld sehr wohl ein Thema sind, ist doch der Isolation Level "Serializable" derjenige der am "verschwenderischsten" mit der Verfügbarkeit umgeht oder anders gesagt, auf der vorsichtigen Seite operiert, was die eigene Transaktionssicherheit angeht, den anderen Benutzern und Transaktionen aber entsprechend weniger Spielraum lässt.

VFP spezifisches C/S Wissen

SQL Server Transaktionen aus VFP heraus steuern

Nun haben Sie bereits ein sehr solides Fundament erhalten, um wagemutig Ihre ersten SQL Server basierten Datenbank Transaktionen aus VFP heraus ansteuern zu können. Sie werden sehen, das geht ganz einfach. Bevor wir beginnen, halten wir uns aber nochmals folgendes vor Augen:

  • Alles, auch die Transaktionsverarbeitung, hängt an der jeweiligen Connection, d.h. der Verbindung zum SQL Server, ab. Vergessen Sie das nie!
  • Der standard SQL Server Isolation Level "Read Committed" gelangt zur Anwendung, ausser Sie würden mit einer SPT Anweisung diesen für die gewünschte Connection explizit anders setzen.

Um eine Verbindung zum SQL Server herzustellen, können wir aus VFP heraus folgendermassen vorgehen:

    lncon = sqlconnect("fibu")

Wenn die Verbindung erfolgreich war, dann erhalten wir eine gültige Connection Nummer zurück. Diese müssen wir uns natürlich abspeichern, damit wir bequem damit arbeiten können. Dies erfolgt in obigem Beispiel in der Variablen lncon. Der Paramerer "fibu" ist der Name des ODBC Data Source Names (DSN).

Um nun eine Transaction zu beginnen, muss folgende Anweisung eingegeben werden

    sqlsetprop(lncon, "Transactions", 2)

Diese Anweisung stellt sicher, dass die Transaktionsverarbeitung manuell gesteuert werden kann. Erst wenn mit der Anweisung SQLCOMMIT(nConnectionHandle) die Transaktion bestätigt, bzw. mit SQLROLLBACK(nConnectionHandle) verworfen wird, ist die Transaktion beendet. In unserem Beispiel können wir das folgendermassen zeigen:

    ?sqlexec(lncon, "update hinweis set bez = 'aaa' where hinweis = 1")

Wenn nun ein anderer Benutzer denselben Datensatz lesen möchte, dann kann er das, entsprechend dem, was wir eingangs dieser Session gelernt haben, NICHT, da unsere Transaktion den betroffenen Datensatz gesperrt hat. Man kann das selber ganz einfach nachvollziehen, indem man eine neue Connection eröffnet (nicht vergessen: alles läuft pro connection!) und versucht, auf diesen Datensatz zuzugreifen. Natürlich ohne Erfolg! Erst wenn die erste Connection die Transaktion beendet, entweder durch SQLCOMMIT oder SQLROLLBACK, ist die Tabelle wieder frei und kann von einer anderen Connection bezogen werden.

Quizfrage: Was passiert, wenn ich oben statt einer "update" Anweisung ein "select from" abgesetzt hätte? Wenn Sie diese Frage nicht beantworten können, habe ich meine Aufgabe nicht erfüllt oder Sie waren mit Ihren Gedanken anderswo. Ich hoffe natürlich das zweite. Ganz im Ernst: Der Isolation Level "Read Committed" stellt sicher, dass die "Read" locks (auch "shared" Locks genannt) gleich nach Beendigung des SQL Statements wieder frei gegeben werden. Zudem würden sich zwei "Read" Locks sowieso nicht gegenseitig behindern. Nur die "Write" Locks, (auch "exclusive" Locks genannt) behindern sich gegenseitig. Ich betone hier diese Begriffe ganz bewusst nochmals, insbesondere auch die Begriffe "Shared" Locks und "Exclusive" Locks, da diese sehr häufig verwendet werden und man sehr gut daran tut, sich deren Bedeutung immer wieder vor Augen zu führen.

Die alles steuernde Connection

Wir haben nun gelernt, dass die Connection das Mass aller Dinge ist. Dieser Sachverhalt kann gar nicht genug hervorgehoben werden. In der Praxis bedeutet dies, dass man sich über jede Connection, unter welcher man Transaktionen betreiben will, klar werden muss und man das Verwenden von "irgendwelchen" Connection Nummern auf gar keinen Fall dem Zufall überlassen kann.

Wenn man nun alle Connctions von Hand durch explizite SQLCONNECT() Anweisungen herstellen würde, könnte man selbst steuern, welche Connections verwendet werden und was damit gemacht wird. In VFX haben wir für diesen Sachverhalt eigens einen Connection Manager geschrieben, der das Erstellen und Verwalten von Connections auf Applikationsebene vereinfacht.

Will man nun aber mit den sehr praktischen VFP remote views operieren, so muss man auf folgendes achten:

Die Connection Definition im DBC

Wird eine remote view erstellt, so muss man zunächst eine Connection aus dem DBC auswählen. Doch Achtung: Diese DBC Connection ist nur die Definition, wie auf den SQL Server zugegriffen werden muss und hat mit den effektiven Connections, welche später durch das Öffnen von verschiedenen remote views verwendet werden, nichts zu tun. Die Connection Definition wird im DBC abgespeichert.

Die im DBC zu definierende Connection beinhaltet folgende Informationen:

Um zur Laufzeit die Connection den aktuellen Bedingungen beim Kunden am besten anpassen zu können empfiehlt es sich, mit der Option "Connections string" zu arbeiten. Ein ausgewachsener Connection String beinhaltet folgendes:

    DSN=Fibu;SERVER=(local);UID=;APP=Microsoft® Visual FoxPro®;WSID=HINOTE2000;DATABASE=Fibu;AutoTranslate=No; Trusted_Connection=Yes;UseProcForPrepare=0;QuotedId=No;AnsiNPW=No

Wobei die einzelnen Optionen im Wesentlichen denjenigen Einstellungen entsprechen, welche auch beim Definieren eines ODBC-DSN Eintrages vorkommen.

TIP:Um obigen Connection String zu erhalten, kann man auf "Verify Connection" klicken.

WICHTIG: Um beim Einsatz Ihres Programmes beim Kunden keine Probleme zu haben, müssen Sie unbedingt sicherstellen, dass keine umgebungsspezifischen Einstellungen im Connection String vorkommen. Am sichersten ist es, wenn Sie hierzu nur den DSN=DsnName Eintrag, wie in obigem Bild dargestellt, verwenden. Das gibt Ihnen die Möglichkeit, beim Kunden vor Ort die Einstellungen in der DSN Definition, d.h. beim Definieren der ODBC Verbindung, vorzunehmen. Um zur Laufzeit den Connection String zu manipulieren, z.B. weil der Kunde mit einem anderen DSN Eintrag arbeiten muss, oder diesen zur Laufzeit ändern will, bietet VFP folgende Möglichkeit:

    dbsetprop("conFibu", "CONNECTION", "ConnectString", "dsn=" + alltrim(m.gs_dsn)) 

    In VFX bietet sich z.B. die Methode onpostlogin() an, um die gewünschten Einstellungen vorzunehmen. Hier ein Beispiel: 
    PROCEDURE onpostlogin 
    dodefault() 
    *--dsn ggfl. setzen 
    if !empty(m.gs_dsn) 
    dbsetprop("conFibu", "CONNECTION", "ConnectString", "dsn=" + alltrim(m.gs_dsn)) 
    else 
    dbsetprop("conFibu", "CONNECTION", "ConnectString", "dsn=fibu") 
    endif 
    … 
    endproc

In obigem Beispiel wird der Connection String entsprechend dem Eintrag in der System Variablem m.gs_dsn gesetzt sowie weitere, applikationsspezifische Prüfungen vorgenommen.

Nachdem wir die Connection in unserem DBC richtig eingestellt haben und auch wissen, wie wir diese zur Laufzeit manipulieren können, sind wir bereit für den nächsten Schritt:

HINWEIS: Für das Betreiben Ihrer Anwendung ohne DSN Eintrag siehe weiter unten!

Connections und Remote Views

Beim Erstellen einer Remote View muss immer zuerst eine im DBC vorhandene Connection ausgewählt werden.

Wird die Remote View anschliessend erstellt, ist in der View Definition die in obigem Dialog ausgewählte Connection Definition fest abgespeichert. Wird die Remote View erstellt, muss im Menu "Query"

die Option "Advanced Options…" angewählt werden, um folgende Einstellungen im "Advanced Options" Dialog vornehmen zu können:

Die Checkbox "Share Connection" ist mitunter eine der wichtigsten überhaupt. Nur wenn diese angeklickt ist, kann sichergestellt werden, dass eine neu geöffnete Remote View versucht, eine bereits vorhandene Connection einer zuvor durch eine andere Remote View geöffnete Connection, zu nutzen.

ACHTUNG: Eine manuell zuvor geöffnete Connection wird nie hierfür in Betracht gezogen! Das ist auch gut so, denn VFP kann ja nicht wissen, was mit dieser "fremden" Connection alles bewerkstelligt wurde oder noch wird. Vielleicht wurde damit ja bereits ein SQL Prepare String an den Server gesendet oder es läuft bereits eine Transaktion darauf.

Was VFP aber kann, ist das Verwenden einer Connection, welche VFP selbst für das Erstellen einer anderen Remote View bereits eröffnet hat. Das und nur das bedeutet "Share Connection".

Transaktion mit verschiedenen Remote Views und SPT

Will ich nun eine Transaktionale Verarbeitung mit verschiedenen vorhandenen, updatable Remote Views, sowie evtl. eigener SQL Pass Through (SPT) Routinen verwenden, dann muss als Voraussetzung folgendes erfüllt sein: Alle Remote Views müssen mit ein und derselben Connection arbeiten. Andernfalls ist es unmöglich, diese in ein und denselben Transaktionsprozess einzubinden.

Diese wichtige Voraussetzung ist nur dann erfüllbar, wenn alle beteiligten Remote Views die Option "Share connection", so wie weiter oben beschrieben, angewählt haben. Um mit der weiter oben beschriebenen Methode SQLSETPROP(nConnectionhandle, "TRANSACTIONS", 2) Anweisung eine manuelle Transaktion zu starten, ist es erforderlich, die aktuelle Connection der beteiligten Remote Views zu kennen. VFP bietet uns hierzu folgende Möglichkeit an:

    lnconnection = CURSORGETPROP("ConnectHandle")

Durch obige Anweisung wird vom aktuellen Arbeitsbereich (optional könnte auch noch der Alias übergeben werden) die Connection Nummer ermittelt und zurückgegeben.

Ist dies einmal geschehen, habe ich eigentlich Tür und Tor offen, um selbst zu bestimmen wie es weiter gehen soll. Zunächst einmal kann ich die Transaktion starten:

    sqlsetprop(lnconnection, "Transactions", 2)

Und meine benötigte transaktionale Business Logik durchlaufen um am Schluss wieder mit

    sqlcommit(lnconnection)

die Transaktion im Erfolgsfall permanent zu beenden, oder aber mit

    sqlrollback(lnconnection)

die Transaktion zurückzufahren, und alle in der Transaktion vollzogenen Änderungen auf der Datenbank wieder rückgängig zu machen.

In VFX kann dies z.B. durch das Überschreiben der onsave() Methode in einem Form folgendermassen aussehen:

    parameters tlNotRefresh, tlAllTable, tlFromChild 

    local laError[7], lViewMessage, lSaved, lValid, lCanSave, oMatEingang 
    local lcoldpoint 
    lcoldpoint = SET("POINT") 
    set point to "." 

    if thisForm.lUseHook and !thisForm.OnEventHook("OnSave",this,thisForm) 
    set point to (lcoldpoint) 
    return .t. 
    endif 

    select (thisform.cWorkAlias) 

    if !empty(tlFromChild) 
    tlAllTable = .f. 
    else 
    tlAllTable = .t. 
    endif 

    lSaved = .f. 
    lValid = .f. 
    lCanSave = .t. 

    if thisform.nFormStatus == ID_EDIT_MODE 
    lCanSave = thisForm.OnPostEdit() 
    endif 

    this.lcanedit = this.lactcanedit 

    lValid = thisForm.Valid() 

    this.SetObjectFocus() 

    this.OnWriteBuffer() 

    if lCanSAve and lValid 
    select (thisform.cWorkAlias) 

    if type("goProgram")=="O" 
    goProgram.ClearAllIdx() 
    else 
    this.ClearIDX() 
    endif 

    do case 
    case thisform.nformstatus = 2 
    replace erf with gu_user, ; 
    erfdat with datetime() in rvEingang 
    case thisform.nformstatus = 1 
    replace mut with gu_user, ; 
    mutdat with datetime() in rvEingang 

    endcase

    * Transaction
    LOCAL lnConnection, lntransok 
    lnconnection = CURSORGETPROP("ConnectHandle") 
    sqlsetprop(lnconnection, "Transactions", 2) 
    ** for remote views do the same in VFP 
    BEGIN TRANSACTION 

    * Applikationsspezifische Business Logik… 
    * Prozessstatus setzen 

    local lcUpdateSQL, lcTempCursor 
    lcTempCursor = sys(2015) 
    ** Bei neuer Prozessid muss auch eine neuer Record erzeugt werden 
    lcUpdateSQL = "SELECT Prozessid FROM Prozess"+; 
    " WHERE Prozessid = "+ str(nvl(rveingang.prozessid,0)) 
    if vfxsqlexec(lnconnection,lcUpdateSQL,(lcTempCursor)) <= 0 
    aerror(laerror) 
    =messagebox("Vom Datenbank Server ist folgender Fehler zurückgemeldet worden:" + CHR(13) 
    + CHR(13) + ; 
    laerror[1,3] + CHR(13) + CHR(13) + ; 

    "Falls Sie durch Anpassen Ihrer Eingabe den Fehler nicht verhindern können, " + ; 
    "nehmen Sie bitte mit Ihrem Systemadministrator Kontakt auf!",0+16,"Fehler bei 
    Eingang/Prozess!") 
    lntransok = .f. 
    else 
    select(lcTempCursor) 
    if reccount() < 1 
    lcUpdateSQL = "INSERT INTO Prozess (Prozessid,Stufe,erf,erfdat,"+; 
    "einstieg,prozessstatus,prozessdatum) "+; 
    " VALUES ("+ str(nvl(rveingang.prozessid,0))+",'"+; 
    rveingang.stufe+"','"+; 
    rveingang.erf+"','"+; 
    dtos(datetime())+" "+ttoc(datetime(),2)+"',"+; 
    str(5)+","+; 
    str(5)+",'"+; 
    dtos(datetime())+" "+ttoc(datetime(),2)+"')" 
    else 
    lcUpdateSQL = "UPDATE Prozess SET Prozessstatus = 5 "+; " 
    WHERE Prozessid = "+ str(nvl(rveingang.prozessid,0))+" and"+; 
    " (prozessstatus is null or Prozessstatus < 5)" 
    endif 
    select (thisform.cWorkAlias) 
    if vfxsqlexec(lnconnection,lcUpdateSQL,(lcTempCursor)) <= 0 
    aerror(laerror) 
    =messagebox("Vom Datenbank Server ist folgender Fehler zurückgemeldet worden:" + 
    CHR(13) + CHR(13) + ; 
    laerror[1,3] + CHR(13) + CHR(13) + ; 
    "Falls Sie durch Anpassen Ihrer Eingabe den Fehler nicht verhindern können, " + ; 
    "nehmen Sie bitte mit Ihrem Systemadministrator Kontakt auf!",0+16,"Fehler bei 
    Eingang/Prozess!") 
    lntransok = .f. 
    else 
    lntransok = tableupdate(.t., .t., "rveingang") 
    if !lntransok 
    aerror(laerror) 
    =messagebox("Vom Datenbank Server ist folgender Fehler zurückgemeldet worden:" + 
    CHR(13) + CHR(13) + ; 
    laerror[1,3] + CHR(13) + CHR(13) + ; 

    "Falls Sie durch Anpassen Ihrer Eingabe den Fehler nicht verhindern können, " 
    + ; 
    "nehmen Sie bitte mit Ihrem Systemadministrator Kontakt auf!",0+16,"Fehler 
    bei Eingang/rvEingang!") 
    else 
    * Busineslogik 
    oMatEingang = createobject("cMatEingang") 
    lntransok = oMatEingang.Eintragen(lnconnection,"rveingang","rveingangpos") 
    oMatEingang.Release() 
    oMatEingang = .NULL. 
    if !lntransok 
    aerror(laerror) 
    =messagebox("Vom Datenbank Server ist folgender Fehler zurückgemeldet worden:" 
    + CHR(13) + CHR(13) + ; 
    laerror[1,3] + CHR(13) + CHR(13) + ; 
    "Falls Sie durch Anpassen Ihrer Eingabe den Fehler nicht verhindern 
    können, " + ; 
    "nehmen Sie bitte mit Ihrem Systemadministrator Kontakt 
    auf!",0+16,"Fehler bei Eingang/busineslogic!") 
    endif 
    endif 
    endif 
    endif 
    if lntransok 
    sqlcommit(lnconnection) 
    ** for remote views do the same in VFP 
    END TRANSACTION 

    lsaved = .t. 
    else 
    sqlrollback(lnconnection) 
    ** for remote views do the same in VFP 
    ROLLBACK 

    lsaved = .f. 
    endif 
    sqlsetprop(lnconnection, "Transactions", 1) 
    * end Transaction 
    endif 

    if !lSaved 
    lViewMessage = .T. 
    laError[1] = 0 

    =aerror(laError) 

    if laError[1] != 0 
    lViewMessage = !this.ErrorHandler(laError[1]) 
    endif 

    if lViewMessage and lValid 
    = messagebox(MSG_NOT_SAVED,MB_ICONINFORMATION,MSG_ATTENTION) 
    endif 
    else 
    if thisForm.nPageList >0 
    this.pgfPageFrame.pages[thisForm.nPageList].enabled = .t. 
    ** 
    this.pgfPageFrame.pages[thisForm.nPageList].grdgrid.enabled = .t. 
    endif 

    if tlAllTable 
    thisForm.Caption = thisForm.cOldTitle 
    this.nFormStatus = ID_NORMAL_MODE 
    this.lEmpty = .f. 
    endif 

    select (thisform.cWorkAlias) 
    if !eof() 
    go recno() 
    else 
    thisForm.oRecord.GoTop() 
    endif 

    this.RefreshToolBar(.t.) 
    this.OnFormStatusChange() 
    if !tlNotRefresh 
    this.refresh() 
    endif 

    unlock all 
    endif 
    *!* End of code from cdataform.onsave. 
    set point to (lcoldpoint)

    return lSaved

In obigem Beispiel wird schön ersichtlich, dass in der gesamten Speicherlogik, sowohl SPT Anweisungen, welche UPDATE oder INSERT Befehle absetzen neben ganz normalen TableUpdate Anweisungen verwendet werden.

Der Vorteil eines solchen Ansatzes liegt auf der Hand: Man kann die sehr praktischen Remote Views ohne weiters in eine Transaktionsverarbeitung integrieren, selbst wenn sich erst nach Fertigstellung eines Forms der Bedarf ergeben sollte, dass das ganze Abspeichern transaktional zu erfolgen hat.

Aufgepasst mit busy connections

Auf ein Risiko beim Arbeiten mit ein und derselben Connection bei mehreren Remote Views sei noch hingewiesen. Nämlich das Problem, dass eine Connection auch busy sein kann. Die häufigste Ursache liegt darin, dass nicht alle selektierten Datensätze bezogen wurden, da die Fetchsize nicht auf -1 eingestellt wurde. Es ist sehr wichtig, dass man die Views so parametrisiert, dass es immer Sinn macht, alle selektierten Datensätze sofort zu beziehen, und nicht mit dem Datenbezug zuzuwarten. Nur so kann verhindert werden, dass eine View "busy" bleibt.

Connections

Um es gleich vorweg zu nehmen. Ein sehr grosses Optimierungspotential liegt darin, seine Client/Server Anwendung bzgl. der Verwendung von Connections zum Datenbankserver, zu optimieren. Ein allzu sorgloses Verwenden von Connections kann sich bitter rächen.

Es drängt sich hierbei der Ansatz auf, dass man im Wesentlichen pro Benutzer und Applikation nur eine einzige Connection vom Client auf den Datenbankserver zulässt. Nur dann, wenn es wirklch nicht anders geht, dürfen zusätzliche Connections erstellt werden.

Wenn man sorglos mit Remote Views und manuell erstellten Connections für SPT Anweisungen umgeht, ist es mit der Performance früher oder später nicht weit her. Man stelle sich einmal vor, wieviele Connections geöffnet werden, wenn man ein Form lädt, das bereits in der Datenumgebung eine Reihe von Remote Views besitzt und zusätzlich noch eine nicht zu unterschätzende Anzahl Comboboxen oder Listboxen, alle natürlich wieder mit eigenen Remote Views als Row Sources und folglich wieder Connections. Es wird relativ schnell klar, das das sicher nicht die resourcenschonendste Art und Weise der Client/Server Programmierung darstellt.

Neben der Performance sind bei der Verwendung von Connections vom Client zum Server natürlich auch lizenzrechtliche Aspekte zu berücksichtigen.

[ 1 ] [ 2 ] [ 3 ] [ 4 ] [ 5 ] [ 6 ] [ 7 ] [ 8 ] [ 9 ]