![]()
De CursorAdapter
De komst van Visual FoxPro 8 heeft een aantal aangename nieuwe features met zich mee gebracht. Er zijn er wat mij betreft echter twee die er met kop en schouders bovenuit steken, dat zijn de cursoradapter (CA) en de BindEvents functie (met een eervolle vermelding voor de BindControls property van de form class). Deze twee hebben verstrekkende gevolgen voor de wijze van ontwikkelen in VFP, meer zelfs dan je in eerste instantie zou vermoeden. De BindEvents functie is echter onlangs nog besproken, dus zal ik mij in dit artikel beperken tot de CursorAdapter. Ik zal trachten te verduidelijken hoe de cursoradapter functioneert, aan te geven wat de toegevoegde waarde is boven de talloze data technieken waarover we reeds beschikken, maar vooral ook een poging doen een voorzet te geven voor een efficiënt gebruik ervan in de praktijk. Ook zal ik wat aanbevelingen doen t.b.v. de implementatie in een applicatie of, beter nog, in een raamwerk.
Hoewel de CursorAdapter in de VFP helpfile uitgebreid beschreven wordt qua interface, laat de informatie toch aan duidelijkheid te wensen over. Zo zouden we o.a. de volgende vragen kunnen stellen:
Hoewel we het antwoord op de eerste vraag allemaal wel ongeveer weten, is het toch interessant om even te kijken hoe de CursorAdapter door het VFP team beschreven en gepositioneerd wordt. Ik heb daar naar gezocht en twee duidelijke statements gevonden die de vraag adequaat beantwoorden:
Zoals bekend mag worden verondersteld, is VFP een data centrische ontwikkelomgeving waarin reeds een indrukwekkend aantal manieren bestaat om met data om te gaan. Zo onderscheiden we de bekende ‘native’ VFP data, de database container compleet met triggers en procedures, cursors, local views, remote views, queries, SQL pass through (SQLCONNECT, SQLEXEC enz.), ODBC, OLE DB, ADO en daar zijn dan nu de CursorAdapter en de XML adapter aan toegevoegd.
Dat maakt het er, zeker voor de beginnende VFP ontwikkelaar, niet
overzichtelijker op. Toch is het zo dat de CA ons het leven als ontwikkelaar een
stuk makkelijker gaat maken (hoewel het enige inspanning vergt alvorens we daar
de vruchten van kunnen plukken). In principe is de CA onder alle omstandigheden
te gebruiken, of we nu een eenvoudige kaartenbak willen maken of een complete
client-server applicatie.
Ook zal het ongetwijfeld zo zijn dat Microsoft en/of het VFP team met de CA
aansluiting zoekt bij .NET en de daarmee onvermijdelijk samenhangende ‘dataset’.
De CA lijkt qua ontwerp en benadering dan ook sterk op de dataset.
Hoewel de CA er veelbelovend uitziet, hoeft dat zeker niet te betekenen dat het gebruik van de reeds bestaande data technieken overboord moet worden gezet. Er zullen altijd scenario’s denkbaar zijn (denk bijvoorbeeld aan bulk copy of het bewerken van grote hoeveelheden records) waarbij het gebruik van bijvoorbeeld SQL pass through een betere (snellere) oplossing biedt.
De XML adapter verrijkt de XML mogelijkheden in VFP vergelijkbaar met die van de dataset in .NET. Zo kan een XML adapter gebruikt worden om diverse gerelateerde tabellen inclusief XML schema op te nemen in één ‘dataset’ en de uitwisseling tussen VFP data en XML vergemakkelijken. Het gebruik van de XML adapter is echter al een hoofdstuk op zich en is wellicht een interessant onderwerp voor een volgend artikel.
Alvorens in te gaan op de vraag hoe de CA geïntegreerd zou kunnen worden in de applicatie of een raamwerk, zullen we nog heel even stil staan bij de interface van de CA en de daarmee samenhangende mogelijkheden. Om daar een indruk van te krijgen volgt hier een opsomming van de PEM’s uit de helpfile van VFP:
| Alias | AllowDelete | AllowInsert |
| AllowSimultaneousFetch | AllowUpdate | Application |
| BaseClass | BatchUpdateCount | BreakOnError |
| BufferModeOverride | Class | ClassLibrary |
| Comment | CompareMemo | ConversionFunc |
| CursorSchema | CursorStatus | DataSource |
| DataSourceType | DeleteCmd | DeleteCmdDataSource |
| DeleteCmdDataSourceType | FetchAsNeeded | FetchMemo |
| FetchSize | Flags | InsertCmd |
| InsertCmdDataSource | InsertCmdDataSourceType | KeyFieldList |
| MaxRecords | Name | Parent |
| ParentClass | Prepared | SelectCmd |
| SendUpdates | Tables | Tag |
| UpdateCmd | UpdateCmdDataSource | UpdateCmdDataSourceType |
| UpdatableFieldList | UpdateGram | UpdateGramSchemaLocation |
| UpdateNameList | UpdateType | UseDeDataSource |
| UseMemoSize | WhereType |
| AddProperty | Attach | AutoOpen |
| CursorAttach | CursorDetach | CursorFill |
| CursorRefresh | ReadExpression | ReadMethod |
| ResetToDefault | WriteExpression |
| AfterCursorAttach | AfterCursorClose | AfterCursorDetach |
| AfterCursorFill | AfterCursorRefresh | AfterCursorUpdate |
| AfterDelete | AfterInsert | AfterUpdate |
| BeforeCursorAttach | BeforeCursorClose | BeforeCursorDetach |
| BeforeCursorFill | BeforeCursorRefresh | BeforeCursorUpdate |
| BeforeDelete | BeforeInsert | BeforeUpdate |
| Destroy | Error | Init |
Met de komst van Service Pack 1 voor VFP8 is deze lijst nog uitgebreid met de properties ConflictCheckType en ConflictCheckCmd (bedoeld om nauwkeuriger te kunnen sturen hoe update conflicten moeten worden afgehandeld).
Zoals je ziet zijn er niet alleen een hoop properties en methods waar we gebruik van kunnen maken, maar, belangrijker nog, een fiks aantal events waar we op in kunnen haken. Wel mis ik nog zoiets als een AfterChange event.
De makkelijkste manier om een CA aan het werk te zien is door te kijken naar
de Solution Samples. Echter, om de voorbeelden met een SQL Server datasource te
zien werken moeten we eerst even een fout rechtzetten die er voor zorgt dat ze
een error opleveren.
Start VFP8 en ga naar de solution samples, te bereiken middels de taskpane
manager. Klap dan de boomstructuur open onder ‘New in Visual Foxpro 8.0’. Het
voorbeeld dat we willen bekijken heet ‘Managing Data Access Using
CursorAdapters’.
Afhankelijk van hoe SQL Server geïnstalleerd is moeten er een paar dingen
aangepast worden om het voorbeeld goed te laten werken:

Als je alles goed gedaan hebt zouden alle drie de tabbladen moeten zijn gevuld met data. De eerste met lokale VFP data, de tweede met via ODBC verkregen SQL Server data en de derde met via ADO verkregen SQL Server data.
Als je de code in het voorgaande voorbeeld bekijkt, dan lijkt het alsof er behoorlijk veel code en property instellingen nodig zijn om een cursoradapter werkend te krijgen. Niets is minder waar. Laten we even van scratch af aan een cursoradapter maken in een testform. Dat doen we in dit voorbeeld op basis van een ODBC SQL Server datasource, omdat deze de eenvoud van de CA het beste illustreert. Voor een CA met native VFP data zouden we immers net zo makkelijk rechtstreeks de tabel aan de Data Environment toe kunnen voegen. Als je niet de beschikking hebt over SQL Server, dan kun je ook MSDE gebruiken (de SQL Server desktop versie die met VFP meegeleverd wordt). Doe lekker mee en volg onderstaande stappen:




Dit lijkt misschien een triviaal voorbeeld maar de doorgewinterde client-server programmeur zal dit zeker op waarde weten te schatten.
Ter overweging
Misschien programmeer je normaliter alleen met VFP data en denk je ‘laat maar
waaien’. Echter, ook in combinatie met VFP data biedt de CA voordelen en niet
alleen de flexibiliteit om probleemloos te kunnen ‘upsizen’ naar SQL Server. De
CA kan namelijk ook als class gedefinieerd worden en aan de dataenvironment van
een form worden toegevoegd waar hij als object aanspreekbaar is en beschikt over
een breed scala events (zie Interface) waar op kan worden ingehaakt. In feite
zou je de CA ook een object georiënteerde tabel kunnen noemen.
Even terugkomend op de connectie mogelijkheden die we in de CA builder hebben
gezien; we kunnen vaststellen dat het mogelijk is om gebruik te maken van een
bestaande connectie in plaats van het telkens definiëren van de connectie string
in onze applicatie of ons raamwerk.
Het is goed denkbaar dat je in je raamwerk een connection class definieert waarmee je de connection properties vastlegt. Je hoeft dan in het geval dat je server hernoemd of verplaatst wordt, niet alle connection strings aan te passen maar kunt dan volstaan met het aanpassen van je connection object. Om in dat geval toch te kunnen profiteren van het gemak van de CA builder is er op het eerste tabblad een checkbox toegevoegd waarmee je kunt aangeven dat de ingestelde connection string alleen moet worden gebruikt in de builder. Op runtime kun je dan de CA van een connectie voorzien door de connection handle toe te kennen aan de DataSource property van de CA. Ook zou je kunnen overwegen om de connection string te vullen op basis van een constante.
Misschien vraag je je af wat er gebeurt als de structuur van de onderliggende
tabel wordt gewijzigd. Goed punt. In principe kun je de builder net zo vaak
draaien als wilt maar moet je er wel rekening mee houden dat je dan geen
gegenereerde code aanpast, omdat die anders overschreven zou kunnen worden.
Wanneer je dus de onderliggende structuur van een tabel aanpast, dan moet je dit
aan de CA kenbaar maken door de builder opnieuw te draaien of de properties
zoals SelectCmd, UpdateCmd en DeleteCmd handmatig aan te passen. Dit raad ik
echter af, omdat je daarbij heel gemakkelijk fouten kunt maken. Natuurlijk is
het zo dat een tabel in een applicatie vaak meer dan eens gebruikt wordt en je
er wel erg moe van zou worden als je voor iedere aanpassing van de
tabelstructuur alle CA’s weer aan zou moeten passen. Om deze reden alleen al
pleit ik ervoor om voor iedere tabel een afzonderlijke CA class te definiëren
die bij voorkeur is gesubclassed van een cadAbstract/cadBase parent class.
Standaard maakt de CA gebruik van optimistic row buffering, maar je kunt er
afhankelijk van de situatie ook voor kiezen om optimistic table buffering te
gebruiken. De CA werkt dan ook samen met de TABLEUPDATE() en TABLEREVERT()
functies.
Belangrijk om te onthouden is dat je validatie kunt doen in de BeforeUpdate (record niveau) en in de BeforeCursorUpdate (tabel niveau) en dat je kunt verhinderen dat de wijzigingen die ongeldig zijn worden doorgevoerd door in genoemde methods een RETURN .F. te geven.
Stel nu dat we aan ons testform een knop toe willen voegen waarmee we de data uit de tabel opnieuw ophalen, dan kunnen we dit doen door de volgende objectreferentie te gebruiken:
Zoals je ziet is het daarbij noodzakelijk om de recordsource van de grid te ontkoppelen en weer te koppelen omdat de cursor kortstondig gesloten wordt. Dit geldt overigens niet voor de CursorRefresh() maar die kun je niet altijd gebruiken. Als je bijvoorbeeld een zoekveld boven de grid hebt staan, dan wil je toch echt een nieuwe dataset ophalen. Een goede oplossing zou kunnen zijn om de CursorAdapter class zo aan te passen dat hij automatisch de controlsources van de controls die bij hem horen ontkoppelt en weer koppelt (dat kan bijvoorbeeld door met een lus langs de objecten op het form te gaan of door het gebruik van zichzelf registrerende controls).
Een ander punt van overweging betreft de plaats waar de validatie code is ondergebracht. Vaak wordt dit deels in de database gedaan middels triggers en stored procedures en deels in de UI zelf. Wat ikzelf erg handig vind, is om alle validatie code onder te brengen in een (aan de cadBase class toegevoegde) validatie method. Zodoende kunnen we desgewenst reeds tijdens dataentry valideren en hebben we een centrale plek voor de validatie code waardoor deze overal geldt waar de betreffende CA wordt ingezet. Bovendien, en dat kan met validatie code in de database niet, kun je in specifieke forms de validatie code aanvullen of zelfs overrulen. Een ander bijkomend voordeel is dat de validatie code niet afhankelijk is van de taal van de gebruikte database. En, last but not least, kun je de CA laten communiceren met de UI, al dan niet via een mediator, waardoor je bijvoorbeeld op generieke wijze in de UI zichtbaar kunt maken welke controls niet aan de validatie regels voldoen.
Hoewel de mogelijkheden van de CA bijzonder uitgebreid zijn en ik slechts een tipje van de sluier heb kunnen oplichten, hoop ik dat je toch overtuigd geraakt bent van het nut en de kracht van de cursoradapter en dat je op zondagmiddag, als de rest buiten aan het wandelen is, met rooie oortjes van genoegen je framework componenten aan het perfectioneren bent. Bekijk zeker ook nog de voorbeelden in de Solution Samples van de XML adapter.
Resumerend kunnen we vaststellen dat de CA een zeer waardevolle toevoeging is aan onze gereedschapskist die ongeacht de onderliggende datasource kan worden ingezet en met name het gebruik van remote data even gemakkelijk maakt als ware het ‘native’ VFP data.
Ook is duidelijk geworden dat, juist omdat de CA zoveel mogelijkheden biedt, het zinvol is om na te denken over de wijze waarop je hem gaat inzetten en de wijze waarop je hem laat communiceren met de UI.