Registreren   Inloggen   Zoeken:  Nu zoeken
vrijdag 21 november 2008
     
Visual Basic Magazine 2004-00 | januari 2004

Aan de slag met ADO.NET

Visual Basic 6 is en wordt nog steeds veel gebruikt om databasetoepassingen mee te ontwikkelen. In de loop der jaren is Visual Basic juist op dit gebied zo populair geworden. Een aantal technologieën zijn de revue gepasseerd en allemaal zijn ze nog in gebruik.

Allereerst maakten we kennis met DAO. Voor het serieuzere werk heeft Microsoft toentertijd RDO gereleased. Beide concepten zijn zo langzamerhand bij nieuwe projecten wel van het toneel verdwenen. Hiervoor is een paar jaar terug ADO in de plaats gekomen. Versie 2.7 is momenteel de meest actuele versie  ADO is prima geschikt om binnen een standalone applicatie gebruikt te worden om een Microsoft Access database te benaderen, maar ADO is tevens geschikt om zware client-server toepassingen te bouwen op Microsoft SQL-Server. Met de lancering van Visual Studio .NET heeft ook ADO 2.X een opvolger gekregen: ADO.NET. Hoewel de naamgeving anders doet vermoeden is ADO.NET toch zeer verschillend van zijn oudere broer ADO 2.X. De grootste overeenkomst zit hem dus voornamelijk in de naam. Dit artikel is bedoeld voor mensen die kennis hebben van ADO 2.X en die snel aan de slag willen met ADO.NET.

(D)COM
Binnen Visual Studio .NET kunt u nog steeds gebruik maken van ADO 2.X. Dat is wellicht een geruststelling voor u. Toch is het gebruik ervan niet aan te bevelen. VS.NET kan ADO 2.X blijven ondersteunen door middel van de ‘COM-Interop Services’. Via deze services worden COM gebaseerde toepassingen mogelijk, waaronder dus ADO 2.X. Over ‘COM-Interop Services’ valt zelf al een geheel artikel te schrijven, maar u dient in ieder geval te onthouden dat de techniek niet erg snel is. Gebruik deze techniek dus met mate voor bijvoorbeeld migratietrajecten van VB6 naar VB.NET applicaties. Omwille van performance is het dus raadzaam om zo snel mogelijk over te stappen op ADO.NET.
Daarnaast hebben COM-objecten het nadeel dat ze erg moeilijk via het Internet te distribueren zijn. Men kan binnen ASP niet eenvoudig een ADODB.Recordset overgeven tussen twee pagina’s, simpelweg omdat de browser het niet mogelijk maakt of omdat een firewall dat type objecten niet doorlaat. De oplossing hiervoor is XML en het SOAP protocol. SOAP staat voor ‘Simple Object Access Protocol’ en maakt het mogelijk om eenvoudig objecten te distribueren over het Internet. ADO.NET is ontwikkelt rond XML en XML maakt het eenvoudig om bijvoorbeeld hiërarchische data te transporteren tussen een webserver en een client.
De vraag naar deze technieken neemt alleen maar toe, waardoor ADO.NET een steeds logischere keuze wordt. Ook het feit dat COM-toepassingen geen echt ‘managed-codemodules’ zijn is een reden om serieus naar ADO.NET te kijken. Hierover later meer.
Tevens zou ik mij zeker niet prettig voelen wanneer ik nieuwe applicaties zou bouwen in VS.NET, die gebaseerd zijn op technologieën die vanwege ‘backwards-compability’ onderdeel uit maken van de ontwikkelomgeving. Een zeer reële vraag die bij velen zal opkomen: “Hoelang zal Microsoft die techniek blijven ondersteunen?”

Disconnected
ADO.NET is ontwikkelt om onafhankelijk van de oorspronkelijke databron te kunnen functioneren. Wanneer data eenmaal is opgehaald, wordt de connectie verbroken en ook eventuele informatie over de databron wordt eenvoudig gezegd ‘vergeten’. Hoewel het binnen ADO 2.X ook tot de mogelijkheden behoorde om disconnected recordsets aan te maken, is het binnen ADO.NET min of meer de enige manier van werken. Doordat men in principe altijd disconnected werkt, zijn zogenaamde ‘serverside cursors’ ook niet meer mogelijk. In grote lijnen komt het disconnected werken er op neer dat in eerst de benodigde data wordt ingelezen. Daarna wordt de verbinding met de databron verbroken. Vervolgens kan men de data die in het geheugen staat muteren en eventueel als laatste wordt alle data weer teruggeschreven naar de oorspronkelijke databron of een andere databron. Vanzelfsprekend dient men wel eventuele conflicten op een adequate wijze af te handelen.

XML
Hoewel men binnen ADO 2.X kon werken met XML was de ondersteuning ronduit gebrekkig. Dit heeft te maken met het feit dat XML nooit onderdeel heeft uitgemaakt van de oorspronkelijke architectuur van ADO 2.X. Men kon data vanuit een recordset wel direct exporteren als XML en ook als zodanig weer inlezen, maar wilde men echt wat met XML gaan doen dan moest men zich gaan verdiepen in het Document Object Model (DOM). Het DOM is zodanig ingewikkeld dat het erg veel tijd kost wil men hier efficiënt en adequaat mee kunnen programmeren. Met ADO.NET is hier verandering in gekomen; XML wordt native ondersteunt. ‘Internet-communicatie’ verloopt ook allemaal met behulp van XML. Met het oog op de grotere vraag naar gedistribueerde toepassingen zie ik de uitgebreide ondersteuning van XML als één van de meest belangrijke vernieuwingen binnen ADO.NET.

Data Providers
Een van de eerste grote verschillen met ADO 2.X is de implementatie van zogenaamde ‘data providers’. ADO.NET geeft u in eerste instantie de beschikking over twee verschillende ‘provider-objecten’: de VS.NET Data Provider for SQL-Server en de VS.NET Data Provider for OleDb. Inmiddels kan men van de website van Microsoft ook de VS.NET Data Provider for ODBC downloaden. Al deze Data Providers ondersteunen de minimale specificaties van een data-provider waardoor deze objecten qua properties en methods erg op elkaar lijken, maar ook zeker van elkaar kunnen verschillen.
De VS.NET Data Provider for SQL-Server mag men beschouwen als de enige echte ‘managed’ provider aangezien de andere twee gebaseerd zijn op COM of, bij de ODBC variant, direct op de Win32 API. Dit feit zegt in principe ook iets over de performance, alleen de VS.NET Data Provider for SQL-Server ‘praat’ niet via een extra laag met de database en zal hierdoor efficiënter zijn. Hierdoor promoot Microsoft indirect het gebruik van Microsoft SQL-Server of MSDE. Vanzelfsprekend mag men op redelijk korte termijn VS.NET Data Providers verwachten voor andere databases zoals Oracle etc.
Een ‘Connection’ is verantwoordelijk voor de fysieke communicatie met de database. Dit is een reden waarom elke Data Provider zijn eigen versie heeft geïmplementeerd en de reden waarom het Connection-object geen los, ‘top-level’ object meer is, zoals het in ADO 2.X met de ADODB.Connection wel het geval was. Het maakt onderdeel uit van bijvoorbeeld de SQLClient of de OleDbClient.

Connection
Een kenmerk van het hele .NET Framework is het gebruik van zogenaamde ‘namespaces’. Dit is een eenvoudige en gestructureerde manier om een groot aantal objecten te ordenen binnen de ontwikkelomgeving. ADO.NET is onderdeel van de System.Data namespace. Wanneer we dus iets met ADO.NET willen gaan doen moeten we een soort referentie maken naar die functionaliteit (in ADO 2.X moest u ook een referentie maken naar bijvoorbeeld ADO 2.7). Een soortgelijke referentie binnen VS.NET kunt u bereiken door het gebruiken van het keyword Imports, in dit geval:


 Imports System.Data
 
Codevoorbeeld 1.

Deze namespace bevat een groot aantal objecten, die enerzijds onafhankelijk zijn van een Data Provider, of anderzijds zelf een Data Provider zijn. Functionaliteit afhankelijk van een Data provider bevindt zich bijvoorbeeld in System.Data.SqlClient (de SQL-Server provider).
Zoals u al eerder heeft kunnen lezen is een Connection afhankelijk van de Data Provider. In dit geval betekent dat, dat u ook een referentie moet maken naar System.Data.SqlClient om de SqlConnection direct te kunnen benaderen (Anders .SQLClient.SqlConnection). Verder komt het Connection-object redelijk overeen met het ADODB.Connection object. Bekijkt u de volgende code eens:


 Imports System.Data
 Imports System.Data.SQLClient

 Dim cnnConnection As New SqlConnection

 With cnnConnection
  .ConnectionString = "data source=OBELINK_PR;initial catalog=ADM;" & _
                      "persist security info=True;user id=sa;" & _
                      "workstation id=OBELINK_DT;packet size=4096"

  .Open()

    ...

  .Close()

 End With

 cnnConnection = Nothing
 

Codevoorbeeld 2.

U ziet dat deze code redelijk lijkt op code die u ook met ADO 2.X kon tegenkomen. Ik maak hier bewust geen gebruik om met behulp van zogenaamde ‘overload’ functies om  bijvoorbeeld het object al tijdens het dimensioneren te initialiseren met de ConnectionString. De reden hiervoor is om u aan te geven dat het Connection-object niet erg afwijkend is van zijn voorganger.

Recordset
Nu we een connectie hebben met de database ligt het voor de hand om eens een recordset met data te vullen en deze te tonen of door middel van code te doorlopen. Dit is op zich een goed idee, ware het niet dat Recordsets in ADO.NET niet meer bestaan. Hiervoor in de plaats is de DataSet en de DataReader gekomen. Vooral de DataReader heeft erg veel weg van een (bepaald type) recordset. De DataSet, het meest indrukwekkende object binnen ADO.NET, lijkt in de verste verte niet meer op de recordset zoals we die in ADO 2.X hebben leren kennen.
De reden dat Microsoft van de recordset is afgestapt is, dat bij nader inzien het toch niet zo’n handig object was wanneer het ging om relationele data. Velen onder u, waaronder mijzelf, waren in veel gevallen genoodzaakt om data met behulp van joins bij elkaar te schrapen en vervolgens als één resultset verder te verwerken. Alle ‘relationele informatie’ van de onderliggende data was verdwenen en dat is eigenlijk toch jammer. De DataSet is een zeer goede vervanger en wordt verderop in dit artikel nog nader besproken. Het ligt nu meer voor de hand om de laag die tussen de fysieke connectie en de DataSet inzit eens nader te bekijken: namelijk de DataAdapter.

DataAdapter en Command
Net zoals het Connection- en het Command-object, maakt de DataAdapter onderdeel uit van de Data Provider. U kunt een DataAdapter zien als de ‘lijm’ tussen de fysieke databron en het DataSet object. Om data te kunnen bekijken en muteren heeft de DataAdapter de beschikking over een viertal Data Commands; voor elke mogelijke actie: de SelectCommand, de UpdateCommand, de InsertCommand en de DeleteCommand. Deze commands bevatten dus de code en SQL-statements om de desbetreffende taken te kunnen uitvoeren. De DataAdapter maakt het mogelijk om zeer efficiënt te werken met disconnected data. Tijdens het ontwikkelen weet u namelijk al veel informatie over de gezochte data, zoals databasetype, SQL-Statements of de type ‘recordset’ zoals u die terug gaat krijgen. Deze ‘metadata’ werd door ADO 2.X tijdens het uitvoeren van uw programma allemaal ontrafeld en stond vooral het werken met disconnected data erg in de weg. Met ADO.NET kunt ‘at design time’ al heel veel van die informatie gaan aangeven, waardoor de performance erg kan gaan toenemen.
U zou eens op een form een tabel moeten slepen en vervolgens kijken naar de code die automatisch in het form wordt gecreëerd. In dit voorbeeld sleep ik de tabel ‘Functie’ uit de ‘ADM’ database op een leeg form.

Afbeelding 1

Afbeelding 1. Server Explorer

U ziet dat dat op het form een SqlConnection1 en een SqlDataAdapter1 zijn toegevoegd. Kijken we vervolgens naar (een deel) van de code die is toegevoegd in de region: ‘Windows Form Designer generated code’ dan zien we de kracht van die objecten.


  'SqlSelectCommand1
  Me.SqlSelectCommand1.CommandText = "SELECT FunctieID, Omschrijving FROM Functie"
  Me.SqlSelectCommand1.Connection = Me.SqlConnection1
 
  'SqlInsertCommand1
  Me.SqlInsertCommand1.CommandText = "INSERT INTO Functie(FunctieID, Omschrijving) VALUES (@FunctieID, @Omschrijving); " & _
  "SELECT FunctieID, Omschrijving FROM Functie WHERE (FunctieID = @FunctieID)"
  Me.SqlInsertCommand1.Connection = Me.SqlConnection1
  Me.SqlInsertCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@FunctieID", System.Data.SqlDbType.Int, 4, "FunctieID"))
  Me.SqlInsertCommand1.Parameters.Add(New System.Data.SqlClient.SqlParameter("@Omschrijving", System.Data.SqlDbType.VarChar, 30, "Omschrijving"))
 
Codevoorbeeld 3.

Het Command-object lijkt in grote lijnen op het Command-object zoals we dat kennen uit ADO 2.X. Voor elk Command-object kan men desgewenst een aparte connectie aangeven; dit hoeft natuurlijk niet. Een handig hulpmiddel om de DataAdapter te bekijken is de ‘Preview’ mogelijkheid. Klik met de rechtermuisknop op SqlDataAdapter1 en kies voor ‘Preview Data…”. Een soortgelijk dialoogkader verschijnt.

Afbeelding 2

Afbeelding 2. Data Adapter preview

DataSet
Het meest indrukwekkende object binnen ADO.NET is de DataSet. U kunt dit object het best zien als een soort relationele database die u in het geheugen kan aanmaken. U kunt tabellen, kolommen, relaties en beperkingen (constraints) definiëren. Het bestaat uit een tweetal collecties (DataTables en de DataRelations). Een DataTable bestaat weer uit Columns, Rows en eventueel Constraints.

Afbeelding 3

Afbeelding 3. DataSet

ADO.NET kent twee typen DataSets; zogenaamde Typed en UnTyped DataSets. Typed wil eigenlijk niets meer zeggen dat het datatype is vastgesteld. Dit heeft als voordeel dat u tijdens het programmeren met behulp van Intellisense eenvoudig tegen het object aan kan programmeren omdat de namen van de velden (Columns) als properties van het object worden getoond. Daarnaast biedt het ook de zekerheid dat eventuele toekenning van een foutief datatype niet eens gecompiled kan worden (strong typing). Bekijkt u de volgende code eens. De eerste regel code betreft een Typed DataSet, de tweede regel een Untyped DataSet.


 dstLeverbonnen.LeverbonKop.Rows(0).LeverbonDatum = “31-12-2003”
 dstLeverbonnen.Tables(“LeverbonKop”).Rows(0).Item(“LeverbonDatum”) = _
                                       “De laatste dag van 2003”
 
Codevoorbeeld 4.

Bij de eerste regel ziet u dat we direct naar de naam van de tabel en veld kunnen refereren. Zouden we de waarde, die we in de tweede code hadden toegekend, op de eerste regel toepassen, dan had de compiler een foutmelding gegenereerd. Het datatype komt namelijk niet overeen met een ‘Datum’.

DataTable
Uit de vorige paragraaf is gebleken dat een DataTable onderdeel uit kan maken van een DataSet. U kunt echter een DataTable ook als eigen, los object creëren. De interne opbouw van collecties en andere objecten is echter precies hetzelfde. We onderscheiden dus ondermeer kolommen en rijen.


   Dim dtbIdols As New DataTable("Idols")
   Dim drwRow As DataRow

   With dtbIdols
      .Columns.Add("Naam")
      .Columns.Add("Eindstand")

      drwRow = .NewRow
      drwRow.Item("Naam") = "Jamai"
      drwRow.Item("Eindstand") = 1
      .Rows.Add(drwRow)

      drwRow = .NewRow
      drwRow.Item("Naam") = "Jim"
      drwRow.Item("Eindstand") = 2
      .Rows.Add(drwRow)

  End With

  DataGrid.DataSource = dtbIdols
 

Codevoorbeeld 5.

We kunnen bovenstaande code eenvoudig met constraints uitbreiden door de onderstaande code net boven de ‘End With’ toe te voegen. Hierdoor is het, in dit geval, niet meer mogelijk om dezelfde eindstanden in toe voeren. Ik wijs er nog eens op dat we in dit voorbeeld nog helemaal niets met een fysieke database hebben gedaan. Deze mogelijkheden zullen door velen onder u erg gewaardeerd gaan worden.


 Dim ucnUniqueConstraint As New UniqueConstraint(.Columns.Item("Eindstand"))

 .Constraints.Add(ucnUniqueConstraint)
 

Codevoorbeeld 6.

Afbeelding 4

DataView
Het DataView object is nauw verbonden aan de DataTable. Degenen die al eens met Views op SQL-Server hebben gewerkt kennen het begrip ‘View’. Het is een representatie van data in een onderliggende tabel. Binnen Microsoft Access worden Views ook wel Queries genoemd.
Een DataView is een gefilterde en/of gesorteerde weergave van data in een DataTable. Men kan meerdere DataViews voor een DataTable aanmaken; elke DataTable heeft altijd minimaal één DataView. Deze is beschikbaar en aan te passen via de .DefaultDataView property.
Hoewel de rijen in een DataView erg lijken op de eerder genoemde DataRows, zijn zij van het type DataRowView. We zouden ons eerder beschreven ‘Idols’- voorbeeld kunnen uitbreiden met de volgende code. Hierbij dient opgemerkt te worden dat de eerste 3 regels uitbreidingen zijn en de laatste een aanpassing van de laatste coderegel. We willen immers de gefilterde data in het grid tonen, en niet de gehele DataTable.


  Dim dvwFilterNaam As New DataView(dtbIdols)
  
  dvwFilterNaam.RowFilter = "Naam LIKE 'J%'"
  dvwFilterNaam.Sort = "Eindstand DESC"

  DataGrid.DataSource = dvwFilterNaam
 

Codevoorbeeld 7.

U zou eens kunnen experimenteren de .RowFilter property. Deze lijkt sterk op de .Filter property zoals wij die nu kennen op ADODB.Recordset object. Ook hier geldt dat men voor eventuele vergelijken op velden van type ‘Date’ de waarde moet converteren naar het Amerikaanse formaat en dat de datum tussen hekjes wordt geplaatst, bijvoorbeeld: #12/31/2003#. Afhankelijk van de ingestelde properties op het DataView object kunt u data toevoegen, aanpassen of zelfs verwijderen.

DataReader
Het object dat het meest overeen lijkt te komen met de klassieke ADODB.Recordset is de DataReader. Het is een object dat een forward-only en een read-only resultset teruggeeft. Hiermee kunt u snel een DataGrid vullen of een Combobox met een lijst van waarden uit de database.

Tot slot
ADO.NET is sterk gewijzigd in vergelijking tot ADO 2.X. Een concept waar men sterk aan moet wennen is het werken met ‘disconnected’ data. Deze manier van werken is voor webdevelopers natuurlijk gesneden koek, maar voor programmeurs zoals ik, die zich het meeste bezig houden met het bouwen en onderhouden van desktop applicaties, wordt toch een omslag van denken verwacht. De performance van de data-access in het algemeen is zeer zeker bevredigend en soms zelfs erg goed te noemen. Voor databasetoepassingen is ADO.NET zeker een vooruitgang ten opzichte van ADO 2.X.
Het vergt echter wel wat leertijd.

Advertenties
Leer ook Visual Basic!

Visual Basic 2005 - de Basis | André Obelink

 


Microsoft Certified Solution Developer

Microsoft Certified Professional

Microsoft Most Valuable Professional

VP Speakers Bureau - INETA Europe

Brainbench Certified Master Visual Basic 6.0 Programmer

Gebruiksovereenkomst   Privacybeleid   Copyright 2005 André Obelink