7. Dostęp do baz danych
7.1. Informacje ogólne
Istnieje wiele baz danych przeznaczonych dla platform Windows. Aby uzyskać do nich dostęp, aplikacje korzystają z programów zwanych sterownikami (drivers).
![]() |
Na powyższym schemacie sterownik posiada dwa interfejsy:
- interfejs I1 udostępniany aplikacji
- interfejs I2 skierowany do bazy danych
Aby uniknąć konieczności przepisywania aplikacji napisanej dla bazy danych B1 w przypadku migracji do innej bazy danych B2, podjęto działania normalizacyjne dotyczące interfejsu I1. W przypadku korzystania z baz danych wykorzystujących „znormalizowane” sterowniki baza B1 zostanie dostarczona wraz ze sterownikiem P1, baza B2 wraz ze sterownikiem P2, a interfejs I1 obu tych sterowników będzie identyczny. Dzięki temu nie będzie konieczne przepisywanie aplikacji. W ten sposób będzie można na przykład przenieść bazę danych ACCESS do bazy danych MySQL bez konieczności wprowadzania zmian w aplikacji.
Istnieją dwa rodzaje standardowych sterowników:
- sterowniki ODBC (Open DataBase Connectivity)
- sterowniki OLE i DB (Object Linking and Embedding DataBase)
Sterowniki ODBC umożliwiają dostęp do baz danych. Źródła danych dla sterowników OLE i DB są bardziej zróżnicowane: bazy danych, systemy pocztowe, katalogi... Nie ma żadnych ograniczeń. Każde źródło danych może stać się przedmiotem sterownika OLE lub DB, jeśli zdecyduje się na to wydawca. Korzyści są oczywiście ogromne: uzyskujemy jednolity dostęp do szerokiej gamy danych.
Platforma .NET jest dostarczana z dwoma typami klas dostępu do danych:
- klasy SQL i Server.NET
- klasy OLE i Db.NET
Pierwsze klasy umożliwiają bezpośredni dostęp do serwera Microsoft SGBD SQL bez pośredniego sterownika. Drugie klasy umożliwiają dostęp do źródeł danych OLE DB.

Platforma .NET jest dostarczana (maj 2002) z trzema sterownikami OLE, DB przeznaczonymi odpowiednio dla: SQL Server, Oracle i Microsoft Jet (Access). Jeśli chcemy pracować z bazą danych, która posiada sterownik ODBC, ale nie posiada sterowników OLE ani DB, nie jest to możliwe. Nie można zatem pracować z SGBD ani MySQL, które (w maju 2002 r.) nie zapewniają sterowników OLE ani DB. Istnieje jednak zestaw klas umożliwiających dostęp do źródeł danych, takich jak ODBC i odbc.net. Nie są one dostarczane w standardzie wraz z SDK i należy je pobrać ze strony internetowej firmy Microsoft. W poniższych przykładach będziemy korzystać głównie z tych klas ODBC, ponieważ większość baz danych w systemie Windows jest dostarczana z takim sterownikiem. Oto na przykład lista sterowników ODBC zainstalowanych na komputerze z systemem Windows 2000 (Menu Start/Ustawienia/Panel sterowania/Narzędzia administracyjne):

Wybieramy ikonę Źródło danych ODBC:

7.2. Dwa tryby korzystania ze źródła danych
Platforma .NET umożliwia korzystanie ze źródła danych na dwa różne sposoby:
- tryb online
- tryb offline
W trybie online aplikacja
- otwiera połączenie ze źródłem danych
- współpracuje ze źródłem danych w trybie odczytu/zapisu
- zamyka połączenie
W trybie offline aplikacja
- nawiązuje połączenie ze źródłem danych
- pobiera kopię pamięciową wszystkich lub części danych ze źródła
- zamyka połączenie
- pracuje z kopią danych w pamięci, umożliwiającą odczyt i zapis
- gdy praca zostanie zakończona, otwiera połączenie, wysyła zmodyfikowane dane do źródła danych, aby zostały uwzględnione, a następnie zamyka połączenie
W obu przypadkach to właśnie operacja przetwarzania i aktualizacji danych zajmuje najwięcej czasu. Wyobraźmy sobie, że aktualizacje te są wprowadzane przez użytkownika wprowadzającego dane – operacja ta może trwać kilkadziesiąt minut. Przez cały ten czas, w trybie online, połączenie z bazą danych jest utrzymywane, a zmiany są natychmiast odzwierciedlane. W trybie offline podczas aktualizacji danych nie ma połączenia z bazą danych. Zmiany są wprowadzane wyłącznie w kopii pamięciowej. Są one odzwierciedlane w źródle danych jednorazowo, gdy wszystko zostanie zakończone.
Jakie są zalety i wady obu metod?
- Połączenie wymaga znacznych zasobów systemowych. Jeśli występuje wiele jednoczesnych połączeń, tryb offline pozwala skrócić ich czas trwania do minimum. Dotyczy to aplikacji internetowych obsługujących tysiące użytkowników.
- Wadą trybu offline jest skomplikowane zarządzanie równoczesnymi aktualizacjami. Użytkownik U1 pobiera dane w momencie T1 i zaczyna je modyfikować. W momencie T2 użytkownik U2 również uzyskuje dostęp do źródła danych i pobiera te same dane. W międzyczasie użytkownik U1 zmodyfikował niektóre dane, ale nie przesłał ich jeszcze do źródła danych. U2 pracuje zatem z danymi, z których część jest błędna. Klasy .NET oferują rozwiązania pozwalające zarządzać tym problemem, jednak nie jest to łatwe do rozwiązania.
- W trybie połączonym jednoczesna aktualizacja danych przez wielu użytkowników zazwyczaj nie stanowi problemu. Ponieważ połączenie z bazą danych jest utrzymywane, to sama baza danych zarządza tymi równoczesnymi aktualizacjami. W ten sposób Oracle blokuje wiersz w bazie danych, gdy tylko użytkownik go zmodyfikuje. Pozostanie on zablokowany, a więc niedostępny dla innych użytkowników, dopóki użytkownik, który go zmodyfikował, nie zatwierdzi (commit) swojej zmiany lub jej nie cofnie (rollback).
- Jeśli dane muszą być przesyłane przez sieć, należy wybrać tryb odłączony. Pozwala on na uzyskanie migawki danych w obiekcie zwanym zestawem danych (dataset), który sam w sobie stanowi bazę danych. Obiekt ten może być przesyłany przez sieć między komputerami.
Najpierw omówimy tryb połączony.
7.3. Dostęp do danych w trybie połączonym
7.3.1. Bazy danych z przykładu
Rozważamy bazę danych o nazwie ACCESS, zwaną articles.mdb, zawierającą tylko jedną tabelę o nazwie ARTICLES o następującej strukturze:
nazwa | typ |
kod | 4-znakowy kod artykułu |
nazwa | nazwa (ciąg znaków) |
cena | jego cena (rzeczywista) |
stock_actuel | aktualny stan magazynowy (liczba całkowita) |
stock_minimum | minimalny stan magazynowy (liczba całkowita), poniżej którego należy uzupełnić zapasy danego artykułu |
Jego początkowa zawartość jest następująca:

Będziemy korzystać z tej bazy zarówno w ramach projektu pilotażowego ODBC, jak isterownika OLE DB, aby pokazać podobieństwo obu podejść oraz dlatego, że dysponujemy tymi dwoma typami sterowników dla ACCESS.
Wykorzystamy również bazę danych o nazwie MySQL i DBARTICLES, zawierającą tę samą, jedyną tabelę o nazwie ARTICLES, o tej samej zawartości i dostępnej za pośrednictwem sterownika ODBC, aby pokazać, że aplikacja napisana z myślą o korzystaniu z bazy ACCESS nie wymaga modyfikacji, aby korzystać z bazy MySQL. Dostęp do bazy DBARTICLES ma użytkownik o nazwie admarticles z hasłem mdparticles. Poniższy zrzut ekranu przedstawia zawartość bazy MySQL:
C:\mysql\bin>mysql --database=dbarticles --user=admarticles --password=mdparticles
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 3 to server version: 3.23.49-max-debug
Type 'help' for help.
mysql> show tables;
+----------------------+
| Tables_in_dbarticles |
+----------------------+
| articles |
+----------------------+
1 row in set (0.01 sec)
mysql> select * from articles;
+------+--------------------------------+------+--------------+---------------+
| code | nom | prix | stock_actuel | stock_minimum |
+------+--------------------------------+------+--------------+---------------+
| a300 | vÚlo | 2500 | 10 | 5 |
| b300 | pompe | 56 | 62 | 45 |
| c300 | arc | 3500 | 10 | 20 |
| d300 | flÞches - lot de 6 | 780 | 12 | 20 |
| e300 | combinaison de plongÚe | 2800 | 34 | 7 |
| f300 | bouteilles d'oxygÞne | 800 | 10 | 5 |
+------+--------------------------------+------+--------------+---------------+
6 rows in set (0.02 sec)
mysql> describe articles;
+---------------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------------+-------------+------+-----+---------+-------+
| code | text | YES | | NULL | |
| nom | text | YES | | NULL | |
| prix | double | YES | | NULL | |
| stock_actuel | smallint(6) | YES | | NULL | |
| stock_minimum | smallint(6) | YES | | NULL | |
+---------------+-------------+------+-----+---------+-------+
5 rows in set (0.00 sec)
mysql> exit
Bye
Aby zdefiniować bazę ACCESS jako źródło danych ODBC, wykonaj następujące czynności:
- uruchom menedżera źródeł danych ODBC zgodnie z powyższą instrukcją i wybierz zakładkę „User” DSN (DSN = nazwa źródła danych)

- dodaj źródło za pomocą przycisku Add, wskaż, że to źródło jest dostępne za pośrednictwem sterownika Access i wykonaj Terminer:

- Nadaj źródłu danych nazwę articles-access, wprowadź dowolny opis i użyj przycisku Sélectionner, aby wskazać plik .mdb z bazy. Zakończ na OK.

Nowe źródło danych pojawi się wówczas na liście źródeł użytkownika DSN:

Aby zdefiniować bazę danych MySQL DBARTICLES jako źródło danych ODBC, wykonaj następujące czynności:
- uruchom menedżera źródeł danych ODBC zgodnie z powyższą instrukcją i wybierz zakładkę „User” (Użytkownik) DSN. Dodaj nowe źródło danych o nazwie Add i wybierz sterownik ODBC z MySQL.

- Wprowadź Terminer. Pojawi się wówczas strona konfiguracji źródła MySQL:
54321

- w polu (1) nadajemy nazwę naszemu źródłu danych ODBC
- w polu (2) należy wskazać komputer, na którym znajduje się serwer MySQL. W tym miejscu wpisujemy localhost, aby wskazać, że znajduje się on na tym samym komputerze co nasza aplikacja. Gdyby serwer MySQL znajdował się na zdalnym komputerze M, wpisalibyśmy tutaj jego nazwę, a nasza aplikacja działałaby wówczas z zdalną bazą danych bez konieczności wprowadzania zmian.
- W polu (3) wpisujemy nazwę bazy danych. Tutaj nazywa się ona DBARTICLES.
- W polu (4) wpisujemy nazwę użytkownika admarticles, a w polu (5) hasło mdparticles.
7.3.2. Korzystanie ze sterownika ODBC
W aplikacji korzystającej z bazy danych w trybie połączonym zazwyczaj wykonuje się następujące kroki:
- Połączenie z bazą danych
- Wysyłanie zapytań SQL do bazy
- Odbiór i przetwarzanie wyników tych zapytań
- Zamknięcie połączenia
Kroki 2 i 3 są wykonywane wielokrotnie, a zamknięcie połączenia następuje dopiero po zakończeniu pracy z bazą danych. Jest to stosunkowo klasyczny schemat, z którym być może są Państwo zaznajomieni, jeśli korzystali Państwo z bazy danych w trybie interaktywnym. Kroki te są takie same niezależnie od tego, czy baza danych jest wykorzystywana za pośrednictwem sterownika ODBC, czy też sterownika OLE lub DB. Poniżej przedstawiamy przykład z wykorzystaniem klas .NET do zarządzania źródłami danych ODBC. Program nosi nazwę liste i przyjmuje jako parametr nazwę DSN źródła danych ODBC zawierającego tabelę ARTICLES. Następnie wyświetla zawartość tej tabeli:
dos>liste
syntaxe : pg dsnArticles
dos>liste articles-access
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
a300 vélo 2500 10 5
b300 pompe 56 62 45
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
e300 combinaison de plongée 2800 34 7
f300 bouteilles d'oxygène 800 10 5
dos>liste mysql-artices
Erreur d'exploitation de la base de données (ERROR [IM002] [Microsoft][ODBC Driver Manager] Data source name not found and no default driver specified)
dos>liste mysql-articles
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
a300 vélo 2500 10 5
b300 pompe 56 62 45
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
e300 combinaison de plongée 2800 34 7
f300 bouteilles d'oxygène 800 10 5
Na podstawie powyższych wyników widać, że program wyświetlił zawartość zarówno bazy ACCESS, jak i bazy MySQL. Przyjrzyjmy się teraz kodowi tego programu:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports Microsoft.VisualBasic
Module db1
Sub main(ByVal args As String())
' konsola aplikacji
' wyświetla zawartość tabeli ARTICLES z bazy DSN
', której nazwa została przekazana jako parametr
Const syntaxe As String = "syntaxe : pg dsnArticles"
Const tabArticles As String = "articles" ' la table des articles
' sprawdzanie parametrów
' czy mamy 1 parametr
If args.Length <> 1 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe)
' koniec
Environment.Exit(1)
End If
' pobieranie parametru
Dim dsnArticles As String = args(0) ' la base DSN
' przygotowanie połączenia z bazą danych
Dim articlesConn As OdbcConnection = Nothing ' la connexion
Dim myReader As OdbcDataReader = Nothing ' le lecteur de données
' próba uzyskania dostępu do bazy danych
Try
' ciąg połączenia z bazą danych
Dim connectString As String = "DSN=" + dsnArticles + ";"
articlesConn = New OdbcConnection(connectString)
articlesConn.Open()
' wykonanie polecenia SQL
Dim sqlText As String = "select * from " + tabArticles
Dim myOdbcCommand As New OdbcCommand(sqlText)
myOdbcCommand.Connection = articlesConn
myReader = myOdbcCommand.ExecuteReader()
' Analiza pobranej tabeli
' wyświetlanie kolumn
Dim ligne As String = ""
Dim i As Integer
For i = 0 To (myReader.FieldCount - 1) - 1
ligne += myReader.GetName(i) + ","
Next i
ligne += myReader.GetName(i)
Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
' wyświetlanie danych
While myReader.Read()
' przetwarzanie bieżącego wiersza
ligne = ""
For i = 0 To myReader.FieldCount - 1
ligne += myReader(i).ToString + " "
Next i
Console.WriteLine(ligne)
End While
Catch ex As Exception
Console.Error.WriteLine(("Erreur d'exploitation de la base de données " + ex.Message + ")"))
Environment.Exit(2)
Finally
' zamknięcie czytnika
myReader.Close()
' zamknięcie połączenia
articlesConn.Close()
End Try
End Sub
End Module
Klasy służące do zarządzania źródłami ODBC znajdują się w przestrzeni nazw Microsoft.Data.Odbc, którą należy zatem zaimportować. Ponadto pewna liczba klas znajduje się w przestrzeni nazw System.Data.
Imports System.Data
Imports Microsoft.Data.Odbc
Przestrzenie nazw używane przez program znajdują się w różnych zestawach. Program kompiluje się za pomocą następującego polecenia:
dos>vbc /r:microsoft.data.odbc.dll /r:microsoft.visualbasic.dll /r:system.dll /r:system.data.dll db1.vb
7.3.2.1. Faza nawiązywania połączenia
Połączenie o nazwie ODBC wykorzystuje klasę OdbcConnection. Konstruktor tej klasy przyjmuje jako parametr tzw. ciąg połączenia. Jest to ciąg znaków, który określa wszystkie parametry niezbędne do nawiązania połączenia z bazą danych. Parametrów tych może być bardzo wiele, dlatego ciąg ten może być złożony. Ciąg ma postać "param1=valeur1;param2=valeur2;...;paramj=valeurj;". Oto kilka możliwych parametrów paramj:
nazwa użytkownika, który będzie miał dostęp do bazy danych | |
hasło tego użytkownika | |
nazwa bazy danych DSN, jeśli taka istnieje | |
nazwa bazy danych, do której uzyskano dostęp | |
Jeśli zdefiniuje się źródło danych jako źródło danych ODBC za pomocą menedżera źródeł danych ODBC, parametry te zostały już podane i zapisane. Wystarczy wówczas przekazać parametr DSN, który określa nazwę źródła danych DSN. Tak właśnie postąpiono w tym przypadku:
' przygotowanie połączenia z bazą danych
Dim articlesConn As OdbcConnection = Nothing ' la connexion
Dim myReader As OdbcDataReader = Nothing ' le lecteur de données
Try
' próba uzyskania dostępu do bazy danych
' ciąg połączenia z bazą danych
Dim connectString As String = "DSN=" + dsnArticles + ";"
articlesConn = New OdbcConnection(connectString)
articlesConn.Open()
Po utworzeniu obiektu OdbcConnection nawiązuje się połączenie za pomocą metody Open. Nawiązanie połączenia może zakończyć się niepowodzeniem, podobnie jak każda inna operacja wykonywana w bazie danych. Dlatego cały kod dostępu do bazy danych znajduje się w obiekcie try-catch. Po nawiązaniu połączenia można wysyłać zapytania SQL do bazy danych.
7.3.2.2. Wysyłanie zapytań SQL
Aby wysłać zapytania SQL, potrzebujemy obiektu Command, a dokładniej obiektu OdbcCommand, ponieważ korzystamy ze źródła danych ODBC. Klasa OdbcCommand posiada kilka konstruktorów:
- OdbcCommand(): tworzy pusty obiekt Command. Aby z niego skorzystać, należy później określić różne właściwości:
- CommandText: tekst zapytania SQL, które ma zostać wykonane
- Connection: obiekt OdbcConnection reprezentujący połączenie z bazą danych, w której zostanie wykonane zapytanie
- CommandType: typ zapytania SQL. Istnieją trzy możliwe wartości
- CommandType.Text: właściwość CommandText zawiera tekst zapytania SQL (wartość domyślna)
- CommandType.StoredProcedure: właściwość CommandText zawiera nazwę procedury przechowywanej w bazie danych
- CommandType.TableDirect: właściwość CommandText zawiera nazwę tabeli T. Odpowiada zapytaniu select * from T. Istnieje tylko dla sterowników OLE i DB.
- OdbcCommand(string sqlText): parametr sqlText zostanie przypisany do właściwości CommandText. Jest to tekst zapytania SQL, które ma zostać wykonane. Połączenie należy określić we właściwości Connection.
- OdbcCommand(string sqlText, OdbcConnection połączenie): parametr sqlText zostanie przypisany do właściwości CommandText, a parametr connexion do właściwości Connection.
Aby wysłać zapytanie SQL, dostępne są dwie metody:
- OdbcdataReader ExecuteReader(): wysyła zapytanie SELECT z CommandText do połączenia Connection i tworzy obiekt OdbcDataReader umożliwiającydostęp do wszystkich wierszy tabeli wynikowej select
- int ExecuteNOnQuery(): wysyła żądanie aktualizacji (INSERT, UPDATE, DELETE) z CommandText do połączenia Connection i zwraca liczbę wierszy, których dotyczy ta aktualizacja.
W naszym przykładzie, po otwarciu połączenia z bazą danych, wysyłamy zapytanie SQL SELECT w celu uzyskania zawartości tabeli ARTICLES:
' wykonanie polecenia SQL
Dim sqlText As String = "select * from " + tabArticles
Dim myOdbcCommand As New OdbcCommand(sqlText)
myOdbcCommand.Connection = articlesConn
myReader = myOdbcCommand.ExecuteReader()
Zapytanie jest zazwyczaj zapytaniem typu:
select col1, col2,... from table1, table2,...
where condition
order by expression
...
Obowiązkowe są tylko słowa kluczowe z pierwszego wiersza, pozostałe są opcjonalne. Istnieją również inne słowa kluczowe, które nie zostały tutaj przedstawione.
- Dokonuje się połączenia ze wszystkimi tabelami, które znajdują się za słowem kluczowym „from”
- Zachowane są wyłącznie kolumny występujące po słowie kluczowym „select”
- Zachowane są tylko te wiersze, które spełniają warunek określony przez słowo kluczowe „where”
- Wynikowe wiersze uporządkowane zgodnie z wyrażeniem zawartym w słowie kluczowym „order by” tworzą wynik zapytania.
Wynikiem zapytania select jest tabela. Jeśli weźmiemy pod uwagę poprzednią tabelę ARTICLES i chcemy uzyskać nazwy artykułów, których aktualny stan magazynowy jest poniżej minimalnego progu, wpiszemy:
select nom from articles where stock_actuel<stock_minimum
Jeśli chcemy uzyskać wyniki w porządku alfabetycznym według nazw, wpiszemy:
select nom from articles where stock_actuel<stock_minimum order by nom
7.3.2.3. Wykorzystanie wyniku zapytania SELECT
Wynikiem zapytania SELECT w trybie offline jest obiekt DataReader, a w tym przypadku obiekt OdbcDataReader. Obiekt ten pozwala na sekwencyjne pobieranie wszystkich wierszy wyniku oraz uzyskanie informacji o kolumnach tych wyników. Przyjrzyjmy się kilku właściwościom i metodom tej klasy:
liczba kolumn w tabeli | |
Item(i) reprezentuje kolumnę nr i w bieżącym wierszu wyniku | |
wartość kolumny nr i w bieżącym wierszu przedstawiona jako typ XXX (Int16, Int32, Int64, Double, String, Boolean, ...) | |
nazwa kolumny nr i | |
zamyka obiekt OdbcdataReader i zwalnia powiązane zasoby | |
przechodzi o jeden wiersz dalej w tabeli wyników. Zwraca wartość fałsz, jeśli nie jest to możliwe. Nowy wiersz staje się bieżącym wierszem czytnika. |
Przetwarzanie wyniku funkcji select odbywa się zazwyczaj sekwencyjnie, podobnie jak w przypadku plików tekstowych: można jedynie przesuwać się do przodu w tabeli, a nie do tyłu:
While myReader.Read()
' mamy wiersz – przetwarzamy go
....
' kolejny wiersz
end while
Te wyjaśnienia wystarczą, aby zrozumieć poniższy kod z naszego przykładu:
' Przetwarzanie pobranej tabeli
' wyświetlanie kolumn
Dim ligne As String = ""
Dim i As Integer
For i = 0 To (myReader.FieldCount - 1) - 1
ligne += myReader.GetName(i) + ","
Next i
ligne += myReader.GetName(i)
Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
' wyświetlanie danych
While myReader.Read()
' przetwarzanie bieżącego wiersza
ligne = ""
For i = 0 To myReader.FieldCount - 1
ligne += myReader(i).ToString + " "
Next i
Console.WriteLine(ligne)
End While
Jedyna trudność dotyczy instrukcji, w której łączone są wartości z różnych kolumn bieżącego wiersza:
For i = 0 To myReader.FieldCount - 1
ligne += myReader(i).ToString + " "
Next i
Zapis wiersz+=myReader(i).ToString jest przekształcany na wiersz+=myReader.Item(i).ToString(), gdzie Item(i) jest wartością kolumny i bieżącego wiersza.
7.3.2.4. Zwolnienie zasobów
Klasy OdbcReader i OdbcConnection posiadają metodę Close(), która zwalnia zasoby powiązane z zamkniętymi obiektami.
7.3.3. Korzystanie ze sterownika OLE DB
Wróćmy do tego samego przykładu, tym razem z bazą danych, do której dostęp uzyskuje się za pomocą sterownika OLE DB. Platforma .NET udostępnia taki sterownik dla baz danych ACCESS. Wykorzystamy więc tę samą bazę danych articles.mdb, co poprzednio. Chcemy tutaj pokazać, że nawet jeśli zmieniają się klasy, koncepcje pozostają takie same:
- Połączenie jest reprezentowane przez obiekt OleDbConnection
- zapytanie SQL jest wysyłane za pomocą obiektu OleDbCommand
- jeśli zapytanie to jest klauzulą o nazwie SELECT, w odpowiedzi otrzymamy obiekt o nazwie OleDbDataReader, umożliwiający dostęp do wierszy tabeli wynikowej
Klasy te znajdują się w przestrzeni nazw System.Data.OleDb. Powyższy program można łatwo zmodyfikować, aby obsługiwał bazę OLE DB:
- wszędzie zastępuje się OdbcXX na OleDbXX
- zmienia się ciąg połączenia. W przypadku bazy danych o nazwie ACCESS bez loginu i hasła ciąg połączenia wygląda następująco: Provider=Microsoft.JET.OLEDB.4.0;Data Source=[fichier.mdb]. Częścią tego ciągu, którą można dostosować, jest nazwa pliku ACCESS, który ma zostać użyty. Zmodyfikujemy nasz program tak, aby przyjmował jako parametr nazwę tego pliku.
- Przestrzeń nazw, którą należy zaimportować, to teraz System.Data.OleDb.
Nasz program wygląda teraz następująco:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports Microsoft.VisualBasic
Imports System.Data.OleDb
Module db2
Public Sub Main(ByVal args() As String)
' konsola aplikacji
' wyświetla zawartość tabeli ARRTICLES z bazy DSN
', której nazwa została przekazana jako parametr
Const syntaxe As String = "syntaxe : pg base_access_articles"
Const tabArticles As String = "articles" ' la table des articles
' sprawdzanie parametrów
' czy mamy 1 parametr
If args.Length <> 1 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe)
' koniec
Environment.Exit(1)
End If
' pobieranie parametru
Dim dbArticles As String = args(0) ' la base de données
' przygotowanie połączenia z bazą danych
Dim articlesConn As OleDbConnection = Nothing ' la connexion
Dim myReader As OleDbDataReader = Nothing ' le lecteur de données
' próba uzyskania dostępu do bazy danych
Try
' ciąg połączenia z bazą danych
Dim connectString As String = "Provider=Microsoft.JET.OLEDB.4.0;Data Source=" + dbArticles + ";"
articlesConn = New OleDbConnection(connectString)
articlesConn.Open()
' wykonanie polecenia SQL
Dim sqlText As String = "select * from " + tabArticles
Dim myOleDbCommand As New OleDbCommand(sqlText)
myOleDbCommand.Connection = articlesConn
myReader = myOleDbCommand.ExecuteReader()
' Analiza pobranej tabeli
' wyświetlanie kolumn
Dim ligne As String = ""
Dim i As Integer
For i = 0 To (myReader.FieldCount - 1) - 1
ligne += myReader.GetName(i) + ","
Next i
ligne += myReader.GetName(i)
Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
' wyświetlanie danych
While myReader.Read()
' przetwarzanie bieżącego wiersza
ligne = ""
For i = 0 To myReader.FieldCount - 1
ligne += myReader(i).ToString + " "
Next i
Console.WriteLine(ligne)
End While
Catch ex As Exception
Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
Environment.Exit(2)
Finally
' zamknięcie czytnika
myReader.Close()
' zamknięcie połączenia
articlesConn.Close()
End Try
' koniec
Environment.Exit(0)
End Sub
End Module
Uzyskane wyniki:
dos>vbc liste.vb
E:\data\serge\MSNET\vb.net\adonet\6>dir
07/05/2002 15:09 2 325 liste.CS
07/05/2002 15:09 4 608 liste.exe
20/08/2001 11:54 86 016 ARTICLES.MDB
dos>liste articles.mdb
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
a300 vélo 2500 10 5
b300 pompe 56 62 45
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
e300 combinaison de plongée 2800 34 7
f300 bouteilles d'oxygène 800 10 5
7.3.4. Aktualizacja tabeli
W poprzednich przykładach ograniczaliśmy się do wyświetlania zawartości tabeli. Modyfikujemy nasz program do zarządzania bazą artykułów, aby umożliwić mu wprowadzanie zmian w tej bazie. Program nosi nazwę sql. Jako parametr przekazujemy mu nazwę DSN bazy artykułów, którą ma zarządzać. Użytkownik wpisuje bezpośrednio z klawiatury polecenia SQL, które program wykonuje, jak pokazują poniższe wyniki uzyskane na bazie artykułów MySQL:
dos>vbc /r:microsoft.data.odbc.dll sql.vb
dos>sql mysql-articles
Requête SQL (fin pour arrêter) : select * from articles
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
a300 vélo 2500 10 5
b300 pompe 56 62 45
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
e300 combinaison de plongée 2800 34 7
f300 bouteilles d'oxygène 800 10 5
Requête SQL (fin pour arrêter) : select * from articles where stock_actuel<stock_minimum
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
Requête SQL (fin pour arrêter) : insert into articles values ("1","1",1,1,1)
Il y a eu 1 ligne(s) modifiée(s)
Requête SQL (fin pour arrêter) : select * from articles
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
a300 vélo 2500 10 5
b300 pompe 56 62 45
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
e300 combinaison de plongée 2800 34 7
f300 bouteilles d'oxygène 800 10 5
1 1 1 1 1
Requête SQL (fin pour arrêter) : update articles set nom="2" where nom="1"
Il y a eu 1 ligne(s) modifiée(s)
Requête SQL (fin pour arrêter) : select * from articles
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
a300 vélo 2500 10 5
b300 pompe 56 62 45
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
e300 combinaison de plongée 2800 34 7
f300 bouteilles d'oxygène 800 10 5
1 2 1 1 1
Requête SQL (fin pour arrêter) : delete from articles where code="1"
Il y a eu 1 ligne(s) modifiée(s)
Requête SQL (fin pour arrêter) : select * from articles
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
a300 vélo 2500 10 5
b300 pompe 56 62 45
c300 arc 3500 10 20
d300 flèches - lot de 6 780 12 20
e300 combinaison de plongée 2800 34 7
f300 bouteilles d'oxygène 800 10 5
Requête SQL (fin pour arrêter) : select * from articles order by nom asc
----------------------------------------
code,nom,prix,stock_actuel,stock_minimum
----------------------------------------
c300 arc 3500 10 20
f300 bouteilles d'oxygène 800 10 5
e300 combinaison de plongée 2800 34 7
d300 flèches - lot de 6 780 12 20
b300 pompe 56 62 45
a300 vélo 2500 10 5
Requête SQL (fin pour arrêter) : fin
Program przedstawia się następująco:
' opcje
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Data.OleDb
Imports System.Text.RegularExpressions
Imports System.Collections
Imports Microsoft.VisualBasic
Module db3
Public Sub Main(ByVal args() As String)
' konsola aplikacji
' wykonuje zapytania SQL wpisane z klawiatury w
' tabeli ARTICLES w bazie DSN, której nazwa jest przekazywana jako parametr
Const syntaxe As String = "syntaxe : pg dsnArticles"
' weryfikacja parametrów
' czy mamy 2 parametry
If args.Length <> 1 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe)
' koniec
Environment.Exit(1)
End If 'if
' pobieramy parametr
Dim dsnArticles As String = args(0)
' ciąg połączenia z bazą danych
Dim connectString As String = "DSN=" + dsnArticles + ";"
' przygotowanie połączenia z bazą danych
Dim articlesConn As OdbcConnection = Nothing
Dim sqlCommand As OdbcCommand = Nothing
Try
' próba uzyskania dostępu do bazy danych
articlesConn = New OdbcConnection(connectString)
articlesConn.Open()
' tworzy się obiekt polecenia
sqlCommand = New OdbcCommand("", articlesConn)
'try
Catch ex As Exception
' komunikat o błędzie
Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
' zwolnienie zasobów
Try
articlesConn.Close()
Catch
End Try
Environment.Exit(2)
End Try 'catch
' tworzy się słownik akceptowanych poleceń SQL
Dim commandesSQL() As String = {"select", "insert", "update", "delete"}
Dim dicoCommandes As New Hashtable
Dim i As Integer
For i = 0 To commandesSQL.Length - 1
dicoCommandes.Add(commandesSQL(i), True)
Next i
' odczyt i wykonanie poleceń SQL wpisanych z klawiatury
Dim requête As String = Nothing ' texte de la requête SQL
Dim champs() As String ' les champs de la requête
Dim modèle As New Regex("\s+")
' pętla wprowadzania i wykonywania poleceń SQL wpisanych z klawiatury
While True
' na początku nie ma błędów
Dim erreur As Boolean = False
' wysłanie zapytania
Console.Out.Write(ControlChars.Lf + "Requête SQL (fin pour arrêter) : ")
requête = Console.In.ReadLine().Trim().ToLower()
' koniec?
If requête = "fin" Then
Exit While
End If
' rozbijamy zapytanie na pola
champs = modèle.Split(requête)
' czy zapytanie jest prawidłowe?
If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
' komunikat o błędzie
Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
' kolejne zapytanie
erreur = True
End If
If Not erreur Then
' przygotowywanie obiektu Command do wykonania zapytania
sqlCommand.CommandText = requête
' wykonanie zapytania
Try
If champs(0) = "select" Then
executeSelect(sqlCommand)
Else
executeUpdate(sqlCommand)
End If
Catch ex As Exception
' komunikat o błędzie
Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
End Try
End If
End While
' zwolnienie zasobów
Try
articlesConn.Close()
Catch
End Try
Environment.Exit(0)
End Sub
' wykonanie zapytania o aktualizację
Sub executeUpdate(ByVal sqlCommand As OdbcCommand)
' wykonuje sqlCommand, zapytanie o aktualizację
Dim nbLignes As Integer = sqlCommand.ExecuteNonQuery()
' wyświetlanie
Console.Out.WriteLine(("Il y a eu " & nbLignes & " ligne(s) modifiée(s)"))
End Sub
' wykonanie zapytania SELECT
Sub executeSelect(ByVal sqlCommand As OdbcCommand)
' wykonuje sqlCommand, zapytanie SELECT
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Przetwarzanie pobranej tabeli
' wyświetlanie kolumn
Dim ligne As String = ""
Dim i As Integer
For i = 0 To (myReader.FieldCount - 1) - 1
ligne += myReader.GetName(i) + ","
Next i
ligne += myReader.GetName(i)
Console.Out.WriteLine((ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf + ligne + ControlChars.Lf + "".PadLeft(ligne.Length, "-"c) + ControlChars.Lf))
' wyświetlanie danych
While myReader.Read()
' przetwarzanie bieżącego wiersza
ligne = ""
For i = 0 To myReader.FieldCount - 1
ligne += myReader(i).ToString + " "
Next i
' wyświetlanie
Console.WriteLine(ligne)
End While
' zwolnienie zasobów
myReader.Close()
End Sub
End Module
W tym miejscu omówimy jedynie zmiany w stosunku do poprzedniego programu:
- Tworzymy słownik akceptowanych poleceń SQL:
' tworzenie słownika akceptowanych poleceń SQL
Dim commandesSQL() As String = {"select", "insert", "update", "delete"}
Dim dicoCommandes As New Hashtable
Dim i As Integer
For i = 0 To commandesSQL.Length - 1
dicoCommandes.Add(commandesSQL(i), True)
Next i
co pozwala nam następnie w prosty sposób sprawdzić, czy pierwsze słowo (pole [0]) wpisanej zapytania jest jednym z czterech akceptowanych poleceń:
' czy zapytanie jest prawidłowe?
If champs.Length = 0 Or Not dicoCommandes.ContainsKey(champs(0)) Then
' komunikat o błędzie
Console.Error.WriteLine("Requête invalide. Utilisez select, insert, update, delete")
' kolejne zapytanie
erreur = True
End If 'if
- Wcześniej zapytanie zostało podzielone na pola za pomocą metody Split klasy RegEx:
Dim modèle As New Regex("\s+")
....
' rozbijanie zapytania na pola
champs = modèle.Split(requête)
Słowa składające się na zapytanie mogą być oddzielone dowolną liczbą spacji.
- Wykonanie zapytania select nie wykorzystuje tej samej metody, co zapytanie dotyczące aktualizacji (insert, update, delete). Należy zatem przeprowadzić test i wykonać inną funkcję dla każdego z tych dwóch przypadków:
' przygotowywanie obiektu Command do wykonania zapytania
sqlCommand.CommandText = requête
' wykonanie zapytania
Try
If champs(0) = "select" Then
executeSelect(sqlCommand)
Else
executeUpdate(sqlCommand)
End If 'try
Catch ex As Exception
' komunikat o błędzie
Console.Error.WriteLine(("Erreur d'exploitation de la base de données (" + ex.Message + ")"))
End Try
Wykonanie zapytania SQL może spowodować wyjątek, który jest tutaj obsługiwany.
- Funkcja executeSelect podsumowuje wszystko, co zostało omówione w poprzednich przykładach.
- Funkcja executeUpdate wykorzystuje metodę ExecuteNonQuery klasy OdbcCommand, która zwraca liczbę wierszy, na które ma wpływ polecenie.
7.3.5. IMPOTS
Wykorzystujemy obiekt impôt utworzony w poprzednim rozdziale:
' opcje
Option Strict On
Option Explicit On
' przestrzenie nazw
Imports System
Public Class impôt
' dane niezbędne do obliczenia podatku
' pochodzą ze źródła zewnętrznego
Private limites(), coeffR(), coeffN() As Decimal
' producent
Public Sub New(ByVal LIMITES() As Decimal, ByVal COEFFR() As Decimal, ByVal COEFFN() As Decimal)
' sprawdza się, czy wszystkie trzy tabele mają ten sam rozmiar
Dim OK As Boolean = LIMITES.Length = COEFFR.Length And LIMITES.Length = COEFFN.Length
If Not OK Then
Throw New Exception("Les 3 tableaux fournis n'ont pas la même taille(" & LIMITES.Length & "," & COEFFR.Length & "," & COEFFN.Length & ")")
End If
' w porządku
Me.limites = LIMITES
Me.coeffR = COEFFR
Me.coeffN = COEFFN
End Sub
' obliczenie podatku
Public Function calculer(ByVal marié As Boolean, ByVal nbEnfants As Integer, ByVal salaire As Integer) As Long
' obliczanie liczby udziałów
Dim nbParts As Decimal
If marié Then
nbParts = CDec(nbEnfants) / 2 + 2
Else
nbParts = CDec(nbEnfants) / 2 + 1
End If
If nbEnfants >= 3 Then
nbParts += 0.5D
End If
' obliczanie dochodu podlegającego opodatkowaniu i współczynnika rodzinnego
Dim revenu As Decimal = 0.72D * salaire
Dim QF As Decimal = revenu / nbParts
' obliczanie podatku
limites((limites.Length - 1)) = QF + 1
Dim i As Integer = 0
While QF > limites(i)
i += 1
End While
' zwrot wyniku
Return CLng(revenu * coeffR(i) - nbParts * coeffN(i))
End Function
End Class
Dodajemy do niego nową metodę konstruktora, umożliwiającą zainicjowanie tablic limites, coeffR, coeffN na podstawie bazy danych ODBC:
Imports System.Data
Imports Microsoft.Data.Odbc
Imports System.Collections
...
' konstruktor 2
Public Sub New(ByVal DSNimpots As String, ByVal Timpots As String, ByVal colLimites As String, ByVal colCoeffR As String, ByVal colCoeffN As String)
' inicjuje trzy tabele limitów: coeffR, coeffN na podstawie
' zawartości tabeli Timpots z bazy danych ODBC DSNimpots
' colLimites, colCoeffR, colCoeffN to trzy kolumny tej tabeli
' może wygenerować wyjątek
Dim connectString As String = "DSN=" + DSNimpots + ";" ' chaîne de connexion à la base
Dim impotsConn As OdbcConnection = Nothing ' la connexion
Dim sqlCommand As OdbcCommand = Nothing ' la commande SQL
' zapytanie SELECT
Dim selectCommand As String = "select " + colLimites + "," + colCoeffR + "," + colCoeffN + " from " + Timpots
' tabele służące do pobierania danych
Dim tLimites As New ArrayList
Dim tCoeffR As New ArrayList
Dim tCoeffN As New ArrayList
' próbuje się uzyskać dostęp do bazy danych
impotsConn = New OdbcConnection(connectString)
impotsConn.Open()
' tworzy się obiekt polecenia
sqlCommand = New OdbcCommand(selectCommand, impotsConn)
' wykonuje się zapytanie
Dim myReader As OdbcDataReader = sqlCommand.ExecuteReader()
' Analiza pobranej tabeli
While myReader.Read()
' dane z bieżącego wiersza są umieszczane w tablicach
tLimites.Add(myReader(colLimites))
tCoeffR.Add(myReader(colCoeffR))
tCoeffN.Add(myReader(colCoeffN))
End While
' zwolnienie zasobów
myReader.Close()
impotsConn.Close()
' tabele dynamiczne są umieszczane w tabelach statycznych
Me.limites = New Decimal(tLimites.Count) {}
Me.coeffR = New Decimal(tLimites.Count) {}
Me.coeffN = New Decimal(tLimites.Count) {}
Dim i As Integer
For i = 0 To tLimites.Count - 1
limites(i) = Decimal.Parse(tLimites(i).ToString())
coeffR(i) = Decimal.Parse(tCoeffR(i).ToString())
coeffN(i) = Decimal.Parse(tCoeffN(i).ToString())
Next i
End Sub
Program testowy działa w następujący sposób: jako argumenty otrzymuje parametry, które mają zostać przekazane do konstruktora klasy impôt. Po utworzeniu obiektu impôt dokonuje obliczeń podatku do zapłaty:
Option Explicit On
Option Strict On
' przestrzenie nazw
Imports System
Imports Microsoft.VisualBasic
' strona testowa
Module testimpots
Sub Main(ByVal arguments() As String)
' interaktywny program do obliczania podatku
' użytkownik wprowadza trzy dane za pomocą klawiatury: żonaty nbEnfants wynagrodzenie
' następnie program wyświetla podatek do zapłaty
Const syntaxe1 As String = "pg DSNimpots tabImpots colLimites colCoeffR colCoeffN"
Const syntaxe2 As String = "syntaxe : marié nbEnfants salaire" + ControlChars.Lf + "marié : o pour marié, n pour non marié" + ControlChars.Lf + "nbEnfants : nombre d'enfants" + ControlChars.Lf + "salaire : salaire annuel en F"
' sprawdzenie parametrów programu
If arguments.Length <> 5 Then
' komunikat o błędzie
Console.Error.WriteLine(syntaxe1)
' koniec
Environment.Exit(1)
End If 'if
' pobieranie argumentów
Dim DSNimpots As String = arguments(0)
Dim tabImpots As String = arguments(1)
Dim colLimites As String = arguments(2)
Dim colCoeffR As String = arguments(3)
Dim colCoeffN As String = arguments(4)
' tworzenie obiektu podatku
Dim objImpôt As impôt = Nothing
Try
objImpôt = New impôt(DSNimpots, tabImpots, colLimites, colCoeffR, colCoeffN)
Catch ex As Exception
Console.Error.WriteLine(("L'erreur suivante s'est produite : " + ex.Message))
Environment.Exit(2)
End Try
' pętla nieskończona
While True
' na początku brak błędów
Dim erreur As Boolean = False
' żądane są parametry obliczenia podatku
Console.Out.Write("Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :")
Dim paramètres As String = Console.In.ReadLine().Trim()
' co należy zrobić?
If paramètres Is Nothing Or paramètres = "" Then
Exit While
End If
' sprawdzanie liczby argumentów w wprowadzonej linii
Dim args As String() = paramètres.Split(Nothing)
Dim nbParamètres As Integer = args.Length
If nbParamètres <> 3 Then
Console.Error.WriteLine(syntaxe2)
erreur = True
End If
Dim marié As String
Dim nbEnfants As Integer
Dim salaire As Integer
If Not erreur Then
' sprawdzanie poprawności parametrów
' żonaty
marié = args(0).ToLower()
If marié <> "o" And marié <> "n" Then
Console.Error.WriteLine((syntaxe2 + ControlChars.Lf + "Argument marié incorrect : tapez o ou n"))
erreur = True
End If
' nbEnfants
nbEnfants = 0
Try
nbEnfants = Integer.Parse(args(1))
If nbEnfants < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument nbEnfants incorrect : tapez un entier positif ou nul")
erreur = True
End Try
' wynagrodzenie
salaire = 0
Try
salaire = Integer.Parse(args(2))
If salaire < 0 Then
Throw New Exception
End If
Catch
Console.Error.WriteLine(syntaxe2 + "\nArgument salaire incorrect : tapez un entier positif ou nul")
erreur = True
End Try
End If
If Not erreur Then
' parametry są poprawne – obliczamy podatek
Console.Out.WriteLine(("impôt=" & objImpôt.calculer(marié = "o", nbEnfants, salaire).ToString + " F"))
End If
End While
End Sub
End Module
Wykorzystywana baza to baza o nazwie MySQL, o nazwie DSN mysql-impots:
C:\mysql\bin>mysql --database=impots --user=admimpots --password=mdpimpots
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 5 to server version: 3.23.49-max-debug
Type 'help' for help.
mysql> show tables;
+------------------+
| Tables_in_impots |
+------------------+
| timpots |
+------------------+
mysql> select * from timpots;
+---------+--------+---------+
| limites | coeffR | coeffN |
+---------+--------+---------+
| 12620 | 0 | 0 |
| 13190 | 0.05 | 631 |
| 15640 | 0.1 | 1290.5 |
| 24740 | 0.15 | 2072.5 |
| 31810 | 0.2 | 3309.5 |
| 39970 | 0.25 | 4900 |
| 48360 | 0.3 | 6898 |
| 55790 | 0.35 | 9316.5 |
| 92970 | 0.4 | 12106 |
| 127860 | 0.45 | 16754 |
| 151250 | 0.5 | 23147.5 |
| 172040 | 0.55 | 30710 |
| 195000 | 0.6 | 39312 |
| 0 | 0.65 | 49062 |
+---------+--------+---------+
Uruchomienie programu testowego daje następujące wyniki:
dos>D:\data\devel\vbnet\poly\chap6\impots>vbc /r:system.data.dll /r:microsoft.data.odbc.dll /r:system.dll /t:library impots.vb
dos>vbc /r:impots.dll testimpots.vb
dos>test mysql-impots timpots limites coeffr coeffn
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 2 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 2 200000
impôt=33388 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :o 3 200000
impôt=16400 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 300000
impôt=50082 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :n 3 200000
impôt=22506 F
Paramètres du calcul de l'impôt au format marié nbEnfants salaire ou rien pour arrêter :
