Laden...

DataSet/DataTable -> Verbindung zu GUI

Erstellt von Fracoon vor 16 Jahren Letzter Beitrag vor 16 Jahren 5.586 Views
F
Fracoon Themenstarter:in
85 Beiträge seit 2007
vor 16 Jahren
DataSet/DataTable -> Verbindung zu GUI

verwendetes Datenbanksystem: PostGRE

Hallo an alle Profis da draußen,

ich bin was die C# Programmierung angeht noch relativ jungfäulich. Erst mal kurz zu meinem bisherigen Kenntnissstand (mit 3 's'??? 🤔 )

Ich habe mich schon relativ gut mit den "Basics" von C# beschäftigt (Variablen, Klassen, Methoden etc.)
Ich bin mitlerweile so weit, dass ich eine Anwendung mit GUI erstellen kann die mehrere Controlls hat und diese auch Interagieren können.
Datenbankabfragen an meine Postgre Datenbank funktionieren über Npgsql. Ich kann also Datenbankabfragen in einerm Dataset oder einer Datatable speichern und diese auch in einem Datagridview anzeigen.

Jetzt ist es ja so, dass für die Visualisierung von Daten ein Datagridview nicht immer sinnvoll ist. Beispielsweise wenn ich aus einer Kundentabelle der Datenbank gezielt EINEN Kunden abfrage um ihn zu ändern.

Jetzt die Frage(n):

Wenn ich gezielt nur einen Datensatz aus einer Datenbank beziehe, muss ich trotzdem eine Datatable benutzen um das Ergebnis zu speichern?

Macht es Sinn eine Klasse (zum beispiel Kunde) zu erstellen die die Datenstruktur der Datenbank wiederspiegelt? Ich stelle mir das so vor, dass diese Klasse über eine Methode verfügt die einen Kunden mit einer bestimmten Kundennummer aus der Datenbank holt und somit die Datenstruktur der Klasse befüllt. Die Klasse könnte auch eine Methode haben um einen neuen Kunden in die Datenbank zu schreiben nach dem die Datenstruktur der Klasse durch den Benutzer befüllt wurde.
Ich hoffe mein Gedankengang wird klar und ich liege mit meiner Idee nicht völlig daneben 😄.

Angenommen ich würde diese Klasse verwirklichen müsste ich ein GUI erstellen welches ebenfalls die Datenstruktur (in Form von Textfeldern, Checkboxen, Dropdownlisten, etc.) wiederspiegelt, dann entweder durch den Anwender befüllt und abgespeichert(in die Klasse / das Objekt) und somit in die Datenbank übertragen wird, oder das aus der Datenbank befüllte Objekt zum bearbeiten anzeigt.

Ich hoffe jemand nimmt sich die Zeit, ließt meinen Beitrag und gibt mir einen Denkanstoß in die richtige Richtung. Ich möchte nicht einfach so "umher programmieren" um dann Festzustellen dass ich in die Komplett falsche Richtung gehe.

Gruß
Fracoon

2.187 Beiträge seit 2005
vor 16 Jahren

Hi,

Dein "Problem" ist ein ganz allgemeines und es gibt verschieden Lösungen:

  • Direkt über einen Reader für den SQL-Befehl
    (Lowlevel und unnötig Kompliziert, die anderen Möglichkeiten sind 100%ig besser)
  • Über DataTables
  • Über einen O/R Mapper

Über die letzten beiden Themen gibts bereist einige lange Threads hier im Forum, viel Spass beim lesen.
Objektorientierte Datenzugriffsschicht
typed oder untyped Dataset

Ich persönlich tendiere zu O/R Mappern, da sie
A) Klassen mit Zustand und Verhalten erlauben (das sieht nicht jeder als Voteil)
B) die Kapselung in den Klassen sauber möglich ist
C) Typensicherheit gegeben ist
D) alle standard Konstrukte der OOP angewendet werden können (z.B.: Patterns)

Was ich an DataTables und typisierten DataTables auszusetzen habe, kannst du in einem der besagten Threads nachlesen. g

Noch etwas Werbung in eigener Sache: Ich hätte da so einen O/R Mapper, den ich dir anbieten könnte. 😁 |Aisys| O/R Mapper

Gruß
Juy Juka

3.728 Beiträge seit 2005
vor 16 Jahren

Hallo Fracoon,

seit .NET 2.0 gibt es die Komponente BindingSource. Mit der kannst Du ganz einfach Deine Steuerelemente an typisierte DataSets binden (Ohne eine Zeile Code schreiben zu müssen). Es geht natürlich auch mit untypisierten DataSets. Aber mit typisierten DataSets geht vieles einfacher. Vor allem hast Du Intellisense im Code.
Du solltest auf jeden Fall eine BindingSource verwenden, statt Deine Steuerelemente direkt ans DataSet zu binden. Ansonsten musst Du Dich mit dem CurrencyManager rumärgern (Besonders wenn es darum geht, eine laufende Änderung programmatisch abzuschließen).

Ich möchte Dir allerdings von TableAdaptern in DataSets abraten. Damit hättest Du nämlich Datenstrukturen und Datenzugriffslogik miteinander verschmolzen und eine unnötige Abhängigkeit der Datenstrukturen von einer bestimmten Datenbank-Technologie. Es ist deshalb besser, das DataSet ganz herkömmlich mit einem DataAdapter zu füllen.

Auf dem folgenden Screenshot siehst Du, wie man Datenbindung mit der BindingSource einsetzt.

Wie JuyJuka bereits gesagt hat, gibt es verschiedene Möglichkeiten (z.B. OR-Mapper oder XML-Mapper) für den Datenzugriff. Alle haben ihre Vor- und Nachteile. Typisierten DataSets und ADO.NET sind allerdings die Standard-Werkzeuge des .NET Frameworks. Du wirst deshalb sehr viel Material, Beispiele und Dokumentation dazu finden. Außerdem hast Du damit großartige Designer-Unterstützung in Visual Studio. DataSets lassen sich im DataSet-Designer auch direkt mit XML-Namensräumen ausstatten. Das macht sie auch interessant für den Einsatz mit Webservices.
Schau Dir am besten die verschiedenen Techniken an und wähle eine, die am besten zu Deinem Projekt passt.

F
Fracoon Themenstarter:in
85 Beiträge seit 2007
vor 16 Jahren
Hi

Erstmal ein dickes danke an euch beide dass ihr euch die zeit nehmt mir zu helfen.

@JuyJuka :
Deine Antwort klingt zwar sehr interessant. Is mir aber als Anfänger etwas komplex =)

@Rainbird
Deine Antwort kann ich zumindest im Ansetz nachvollziehen.

Evtl hast du Zeit und Lust dass mit mir mal an einem konkreten Beispiel durchzugehen.
Ich habe ein Bild einer Beispieldatenbank angehängt die ich mir zu Testzwecken erstellt habe.

Wie gehe ich vor wenn ich dafür ein GUI programmieren will?

  • Anzeigen von Kunden
  • Suchen nach Kunden
  • neuen Kunden anlegen
  • bestehenden Kunden ändern

Wie würde ich vorgehen?

  • Eine "MainForm" mit einem DataGrid in dem die Kunden angezeigt werden und einer Suchmaske die diese dan Filtern kann (erstes Problem : bei hoher Kundenzahl könnte das befüllen dieses Grids sehr lange dauern. Wie kann ich dass lösen?)
  • Datenbank Verbidung über Npgsql (Postgre) die Mainform hätte dann selbst ein NpgsqlCommand, NpgsqlConnection, NpgsqlDataAdapter, ein Dataset, und ein Datagridview welches die Kunden anzeigt.
  • (Wie kann ich dann in den Daten suchen? Kann ein Datagridview selbstständig nach gewissen Kriterien filtern?

Da mir ein Datagridview zum anlegen von neuen Kunden etwas ungeeignet erscheint würde ich ein Zweites Form erstellen das über einen Button aufgerufen wird und Textfelder für Vorname, Nachname etc. enthält. Diesem Form würde ich wiederum NpgsqlCommand, NpgsqlConnection verpassen. Das SQL Command dann aus den Textboxen genrieren und ausführen. Beim schließen des Forms würde dann das Grid im MainForm aktualisiert.

Kunden Bearbeiten würde ich über einen Button im Mainform lösen der ein Weiteres Form mit den Daten des gerade im Datagridview Markierten Kunden zum bearbeiten läd. Wiederum mit NpgsqlCommand, NpgsqlConnection. Das SQL Command würde aus den Feldern generiert und beim klick auf den Save Button ausgeführt.

Ich habe diesen Ansatz bereits ausprobiert und kann es auch soweit umsetzen. Mir kommt dass Ganze aber irgendwie "unschön" vor. Das zusammenbasteln des SQL Commands aus den Textboxen zum Beispiel löse ich über eine verkettung von Strings was sehr aufwendig ist.

Auch wenn die Beispiel Datenbank sehr klein ist würde ich mich über eine Antwort freuen die mich in die Richtung einer "professionellen" Lösung schubst.

3.728 Beiträge seit 2005
vor 16 Jahren
  • Eine "MainForm" mit einem DataGrid in dem die Kunden angezeigt werden und einer Suchmaske die diese dan Filtern kann (:::

Wie Du bereits selbst erkannt hast, wird das mit zunehmender Größe des Kundenstamms immer langsamer. Also Murks!
Generell sollte man nicht alle Datensätze von der Datenbank abrufen und clientseitig filtern. Der Trick ist, den Benutzer zuerst einmal Suchriterien (z.B. Kunden-Nr, ein Teil des Firmennamens, PLZ, ...) eingeben zu lassen. Das können simple TextBoxen sein. Hat der Benutzer mindestens ein Suchkriterium eingegeben (oder vielleicht auch per AutoComplete ausgewählt; Das geht seit .NET 2.0 auch mit einfachen TextBoxen), klickt er auf einen Suchen-Button (oder auch eine F-Taste; Professionelle Lösungen lassen sich immer auch gut ohne Maus bedienen), um die Suche anzuwerfen. Die Treffer der Suche werden ihm dann z.B. unterhalb der Suchkriterien-Eingabefelder in einem DataGridView angezeigt. Um z.B. alle Firmen zu suchen, die zum SIEMENS-Konzern gehören, müsste der Benutzer in die Firmennamen-Such-TextBox folgendes eingeben: "SIEMENS%". Intern würde daraus folgende SQL-Anweisung für die Suche (ich verwende grundsätzlich Englische Bezeichner):


SELECT CompanyID,CompanyName,CountryCode,City,ZIP,EMail
FROM Companies
WHERE CompanyName LIKE @companyNameCriteria

Das Resultset dieser Abfrage würde ich an eine BindingSource binden. An diese wiederum das DatagridView. Über eine BindingSource hat man die Datenbindung besser im Griff (z.B. bei Navigation und Änderungen des Bearbeitungsstatus).

  • Datenbank Verbidung über Npgsql (Postgre) die Mainform hätte dann selbst ein NpgsqlCommand, NpgsqlConnection, NpgsqlDataAdapter, ein Dataset, und ein Datagridview welches die Kunden anzeigt.

Diese Vorgehensweise ergibt sogenannten Spaghetti-Code. Sowas ist jedem Entwickler prfessioneller Business-Software ein Gräul! Man kommt so zwar am schnellsten zu Ergebnissen, produziert am Ender aber eine schwer Wartbare Anwendung die Code nur sehr schwer bis gar nicht an mehreren Stellen wiederverwenden kann.
Du solltest Benutzeroberfläche und Geschäftslogik grundsätzlich immer trennen. Das bedeutet, dass die eigentliche Geschäftslogik (Verarbeiten von Eingaben, Transaktionen, Validierung der Daten, Berechnungen, ...) nicht in einer Klasse stehen darf, die von Form oder UserControl erbt. Von SQL-Anweisungen, Datenbankverbindungen und Buchungs- und Speicherfunktionen dürfen die Formulare also gar nichts wissen. Man kapselt das in separaten Geschäftsklassen. Du könntest das in Deinem Fall z.B. in eine Klasse mit dem Namen ContactManager packen. Formulare die mit Daten des Kundenstamms (oder eben der Kontaktverwaltung) zu tun haben, müssen sich also vertrauensvoll an diese Klasse wenden.
Im Gegenzug darf die Klasse ContactManager nicht von "Oberflächen-Sachen" wissen. Das bedeutet, dass man z.B. niemals einen Verweis auf ein Steuerelement an eine Methode einer solchen Geschäftsklasse als Parameter übergibt. Dadurch würde die Geschäftsklasse ja wieder von einer bestimmten Oberflächenklasse abhängig. Das Resultat wäre wieder Spaghetti.
Zum Austauschen der Daten (irgendwie muss ja z.B. das Resultset der Suche aus dem ContactManager-Objekt ins Formular kommen, damit es angeziegt werden kann) verwendet man deshalb neutrale Typen (bei Datenbank-Anwendungen sind das bevorzugt DataSets/DataTables oder ihre typisierten Varianten).
Für die Entwicklung im Team ist diese Vorgehensweise auch wesentlich praktischer. So kann z.B. einer die Geschäftsklassen schreiben und ein anderer (der vieleicht auch über etwas mehr künstlerische Begabung verfügt) die Benutzeroberfläche. Um z.B. die Kunden-Suche innerhalb eines Formulars aufzurufen, wäre nur folgender Code notwendig (Basierend auf der oben beschriebenen Suchmaske):


// Suche laufenlassen und Trefferliste an BindingSource übergeben
_companySearchBinding.DataSource=ContactManager.FindCompanies(_companyNameTextBox.Text);

... Diesem Form würde ich wiederum NpgsqlCommand, NpgsqlConnection verpassen. Das SQL Command dann aus den Textboxen genrieren und ausführen. ...

Genauso wie das Such-Formular, würde das Formular zum Bearbeiten die Geschäftsklasse verwenden. Auch da lässt sich eine BindingSource verwenden, um die TextBoxen an die einzelnen Felder der DataTable zu binden. Ein solches Formular kannst Du automatisch zum anzeigen, anlegen, ändern und löschen von Kunden verwenden, da eine DataTable diese Funktionen bereits beherrscht. Natürlich nur offline im Hauptspeicher. Um die Eingaben in die Datenbank zu speichern, muss wieder die Geschäftsklasse bemüht werden. Bei der Gelegenheit kann diese auch alle Eingaben prüfen und ggf. Geschäftsregeln darauf anwenden. So könnte ein Aufruf zum speichern eines neuen Kunden aus dem Bearbeitungs-Formular heraus aussehen:


// Datenquelle der BindingSource des Formulars abrufen
ContactDataSet.Companies table=(ContactDataSet.Companies)_companyBinding.DataSource;

// Eingaben validieren und in der Datenbank speichern
ContactManager.SaveCompanies(table);

Die SaveCompanies-Funktion könnte die Änderungen z.B. mit einer Kombination aus CommandBuilder und DataAdapter speichern.

Solche Geschäftsklassen sparen Dir letztendlich also jede Menge Arbeit, da Du sie von beliebigen Formularen aus benutzen kannst. Angenommen Du erweiterst Deine Kundenstammverwaltung morgen um ein schickes Angebotsmodul. Im Formular zur Angebotserfassung benötigst Du auch die Adresse des Kunden. Da Du aber alles feinsäuberlich in einer Geschäftsklasse gekapselt hast, brauchst Du Dich nur noch aus diesem Füllhorn zu bedienen:


// Standard-Adresse des Kunden abrufen
ContactDataSet.Addresses address=ContactManager.GetCompanyAddress(_companyID);

Die saubere Trennung von Geschäftslogik und Benutzeroberfläche lohnt sich!

...Das zusammenbasteln des SQL Commands aus den Textboxen zum Beispiel löse ich über eine verkettung von Strings was sehr aufwendig ist.

Nicht nur unschön, sondern auch sicherheitstechnisch sehr gefährlich. Ein böswiller Benutzer könnte z.B. durch folgende Einagbe in die Firmennamen-Such-TextBox einfach eine Tabelle komplett löschen:

';DROP TABLE Companies; -- Haha! Rainbird was here (8= 

Das sollte in einer professionellen Geschäftsanwendung nicht möglich sein. Deshalb grundsätzlich parametrisierte SQL-Befehle verwenden! Dabei werden der Datenbank Benutzereingaben als Parameter übergeben. Inhalt von Parametern wird nicht als SQL-Code ausgeführt. Das verhindert wirkungsvoll, dass böse Buben Schabernack mit dem Datenbankserver treiben.

Hoffentlich kannst Du mit mienen Tipps etwas anfangen.

F
Fracoon Themenstarter:in
85 Beiträge seit 2007
vor 16 Jahren

einfach nur wow.. und ein weiteres dankeschön an dich.

Bei mir ist nun der ein oder andere Groschen gefallen.

Muss das Ganze jetzt erstmal verdauen und mich ein wenig damit auseinandersetzen.

Ich denke ich komme nun ein ganzes stück weiter.

F
Fracoon Themenstarter:in
85 Beiträge seit 2007
vor 16 Jahren
  
SELECT CompanyID,CompanyName,CountryCode,City,ZIP,EMail  
FROM Companies  
WHERE CompanyName LIKE @companyNameCriteria  
  

Ok.. eine kleine frage noch.

deine beschreibung mit den parametern ist mir klar. Was mache ich aber wenn ich das sql-query zur Laufzeit genrieren muss und vorher noch nicht weiß wieviele parameter ich habe?
Dann bleibt wieder nur die Stringverkettung?!?!?

Beispiel

SELECT
e.buch||e.jnr||'/'||substr(e.ejahr,3,2) as journal,
e.eingdt,
e.stat,
e.art,

coalesce(CACO3.CACO3,'-------'),
coalesce(TRS.TRS,'-------')

FROM
lims_eingang e

LEFT JOIN

(
SELECT coalesce(to_char(w.vWert,'99999D999'),'!!!Fehlt!!!') as CACO3, w.jnr, w.buch, w.ejahr , p.short
FROM lims_werte w
LEFT JOIN lims_para p on (w.code = p.code and w.buch = p.buch)
where p.short = 'CACO3'
) as CACO3 on (e.jnr = CACO3.jnr and e.buch = CACO3.buch and e.ejahr = CACO3.ejahr)

LEFT JOIN

(
SELECT coalesce(to_char(w.vWert,'99999D999'),'!!!Fehlt!!!') as TRS, w.jnr, w.buch, w.ejahr , p.short
FROM lims_werte w
LEFT JOIN lims_para p on (w.code = p.code and w.buch = p.buch)
where p.short = 'TRS'
) as TRS on (e.jnr = TRS.jnr and e.buch = TRS.buch and e.ejahr = TRS.ejahr)

WHERE
e.ejahr = 2007 and e.buch = 'D'
( and (CACO3.CACO3 != '-------' or TRS.TRS != '-------')

ORDER BY
e.jnr

(habe auf CODE Tags verzichtet weil ich sonst nichts einfärben kann)

Die rot Markierten stellen müssen für jeden Parameter (die der Benutzer aus einer Liste auswählen kann) wiederholt werden.

In diesem Beispiel wäre z.B. CACO3 und TRS ausgewählt.

Dass wird dann ziemlich kompliziert oder?

Im moment mach ich diese abfrage über ein PHP Script mit reiner Stringverkettung. Funktioniert super.

T
108 Beiträge seit 2005
vor 16 Jahren

Hi!

Ich nutze für unsre Reports eine Stored Procedure, die über Parameter das Result einschränkt.


CREATE PROCEDURE [dbo].[rs_Komponenten]
				@param_1		nvarchar(50) = null,
 				@param_2 		nvarchar(50) = null, 
				@param_3		nvarchar(50) = null,
				@param_4		nvarchar(50) = null
				--@debug 		bit          = 1

AS
	SET NOCOUNT ON;
	-- Deklaration
	DECLARE @sql		nvarchar (4000) 
	DECLARE @paramlist	nvarchar (4000)

-- Anlegen einer temporären Tabelle
	CREATE TABLE [dbo].[#tmp](
		[Spalte1] [nvarchar](50), 
		[Spalte2] [nvarchar](50),
		[Spalte3] [nvarchar](50),
		[Spalte4] [nvarchar](50)	
	) ON [PRIMARY]
	

-- Aufbau des Statements

	SELECT @sql = 
		'INSERT INTO dbo.#tmp
		 SELECT * FROM Tabelle WHERE (1=1)' 
		 
	-- Aufbau der Where Bedingungen, werden über die Berichtsparameter gesteuert
	IF (@param_1 IS NOT NULL)
		SELECT @sql = @sql + ' AND [@param_1]= @xparam_1'

	IF (@param_2 IS NOT NULL)
		SELECT @sql = @sql + ' AND [@param_2]= @xparam_2'

	IF (@param_3 IS NOT NULL)
		SELECT @sql = @sql + ' AND [@param_3]= @xparam_3'

	IF (@param_4 IS NOT NULL) 
		SELECT @sql = @sql + ' AND [@param_4]= @xparam_4'

	--IF @debug = 1
	--	Print @sql


	SELECT @paramlist = '@xparam_1		nvarchar(50),
 					 @xparam_2		nvarchar(50),
					 @xparam_3		nvarchar(50),  
					 @xparam_4		nvarchar(50)'

	EXEC sp_executesql @sql, @paramlist, @param_1, @param_2, @param_3, @param_4

	set NOCOUNT off;
	select * from dbo.#tmp;
	set NOCOUNT on; 

	-- Cleanup                                  
	drop table #tmp;
	return 0;

Damit ist es möglich 0 bis 4 Parameter zu übergeben. Man kann das dann beliebig erweitern....

Falls es eine bessere Lösung gibt, wäre ich daran sehr interessiert 🙂

Was einmal war, wird nie wieder sein...

3.728 Beiträge seit 2005
vor 16 Jahren

Was mache ich aber wenn ich das sql-query zur Laufzeit genrieren muss und vorher noch nicht weiß wieviele parameter ich habe?
Dann bleibt wieder nur die Stringverkettung?!?!?
Du kannst die Parameter doch auch dynamisch generieren.

F
Fracoon Themenstarter:in
85 Beiträge seit 2007
vor 16 Jahren

Was mache ich aber wenn ich das sql-query zur Laufzeit genrieren muss und vorher noch nicht weiß wieviele parameter ich habe?
Dann bleibt wieder nur die Stringverkettung?!?!?
Du kannst die Parameter doch auch dynamisch generieren.

sorry wenn ich so blöd frag. aber hast mal en Beispiel?

3.728 Beiträge seit 2005
vor 16 Jahren
Dynamische Parameter

Bitteschön:


// SQL-Befehl erzeugen
SqlCommand command = new SqlCommand();
command.CommandType = CommandType.Text;
command.Connection = connection;

// Basis-SQL-Anweisung erzeugen 
StringBuilder sqlBuilder=new StringBuilder("SELECT * FROM Addresses");

// Wenn auf PLZ-Bereich eingeschränkt werden soll ...
if (!string.IsNullOrEmpty(zipCodeCriteria))
{
    // SQL-Anweisung erweitern
    sqlBuilder.Append(" WHERE ZIP LIKE @zipCriteria");

    // Paramater anfügen
    command.Parameters.Add("@zipCriteria", SqlDbType.NVarChar, 25).Value = zipCodeCriteria;
}
// Sortierung anfügen
sqlBuilder.Append(" ORDER BY LastName");

// Wenn absteigend sortiert werden soll ...
if (sortDescending)
{ 
    // DESC anhängen
    sqlBuilder.Append(" DESC");
}
// Fertige SQL-Anweisung dem Befehlsobjekt zuweisen
command.CommandText = sqlBuilder.ToString();

Je nach Inhalt der Variable zipCodeCriteria, wird ein Parameter eingefügt, oder nicht. Statt die Benutzereingaben direkt im String einzufügen, wird einfach ein Paramatername in den String eingefügt. Der eigentliche Wert wird über ein Parameter-Objekt an den Befehl übergeben. So werden SQL-Injection-Attacken trotz dynamischem Statement wirkungsvoll verhindert. Außerdem sind parametrisierte Abfragen auch noch schneller als unparametrisierte, da der Datenbankserver leichter Cache-Treffer erzielen kann (Das Statement sieht ja immer gleich aus, egal welche Daten als Paramater übergeben werden).

F
Fracoon Themenstarter:in
85 Beiträge seit 2007
vor 16 Jahren

sqlBuilder.Append

⚠Append ⚠

mir geht ein Licht auf =)

Edit:

SqlBuilder müsste ja auch für Postgres funktionieren oder?

3.728 Beiträge seit 2005
vor 16 Jahren
StringBuilder

Die Klasse heißt StringBuilder und nicht SqlBuilder und funktioniert überall. Ein StringBuilder verkettet Zeichenfolgen in einer ressourcensparenden Art und Weise. Ich habe meine Objektvariable sqlBuilder genannt, da ich in diesem Fall einen StringBuilder einsetze, um ein SQL-Statement zusammenzuketten.

Dieses Prinzip funktioniert für alle ADO.NET Datenprovider. Aber Vorsicht! Manche Provider (z.B. OLEDB) unterstützen keine benannten Paramater (z.B. @zipCriteria) im SQL-Code, sondern nur Fragezeichen (?). Bei diesen Providern musst Du besonders darauf achten, dass die Parameter dem Command-Objekt in der richtigen Reihenfolge zugefügt werden.