ADO.NET, Frage zu verknüpften Tabellen [solved]



  • Hi,

    irgendwie habe ich hier gerade keinen Plan, wie das funnktionieren soll:

    Ich habe eine SQL Server Datenbank, diese verfügt über zwei Tabellen. Kunden und Aufträge. Dabei kann jeder Kunde mehrere Aufträge besitzen, es besteht also eine 1:n Relation, der Primärschlüssel "ID" der Tabelle "Kunden" taucht als Fremdschlüssel in der Tabelle "Aufträge" auf, um so die Relation zu erstellen.
    Auf dem SQL Server wurde auch eine solche Verknüpfung erstellt.

    Meine Anwendung erstellt ein sog. DataSet, was die Tabllen und ihre Beziehungen des SQL Server im Cache des Clients nachbildet.

    Nun importiere ich Adressen aus einer (von der DB völlig unabhängigen) TXT File. Anhand dieser Datei sollen Aufträge erzeugt werden.

    Nur weiss ich nicht, wie ich hier die Bindung zwischen den beiden Datensätzen gewährleisten kann. Bisher versuche ich, die ID, welche der neuen Kunden Tabellenreihe zugewiesen wurde auch an den Auftrag zuzuweisen. Leider bekomme ich dann beim Versuch, das in die Datenbank zu schreiben eine Exception, weil ich angeblich statt der ID NULL übergeben würde.

    Ich versteh es nicht, was mache ich falsch und wie macht man das "eigentlich" wenn man zwei verknüpfte Datensätze hat?

    In der MSDN steht (sinngemäß) man solle eben erst die Übergeordnete Tabelle updaten, danach die untergeordnete und um die Relationen würde das DataSet (zunächst) und danach der SQL Server sich selber kümmern, da diese die Schlüsselspalten automatisch verwalten würden. Das scheint aber nicht so zu sein, denn wenn ich der Fremdschlüsselspalte in "Auftrag" nichts zuweise, dann bleibt sie leer (DBNull). Wie kann ich diese Vernüpfungen also erstellen udn die Datenintegrität gewährleisten?

    for (int i = 1; i < thelist.Count; i++)
                {
                    MyDataSet.KundenRow newOwner = MyDataSet.Kunden.NewKundenRow();
                    MyDataSet.AuftragRow newAuftrag = MyDataSet.Auftrag.NewAuftragRow();
    
                    customer = thelist[i]; // customer ist eine struct mit den daten aus der txt datei
                    Kunden.name = customer.name;
                    Kunden.phonee = customer.phone;
    
                    MyDataSet.Kunden.Rows.Add(newKunde);
    
                    newAuftrag.reason = "Bestellung";
    // nutze die ID von newKunde
                    newAuftrag.customer = newKunde.ID;
    
                    MyDataSet.Auftrag.Rows.Add(newAuftrag);
                    }               
    
    // prüfe auf hinzugefügte Einträge    
    MyDataSet.Kunden rowToadd = (MyDataSet.KundenDataTable)MyDataSet.Kunden.GetChanges(DataRowState.Added);
    
    MyDataSet.Auftrag addrow = (MyDataSet.AuftragDataTable)MyDataSet.Auftrag.GetChanges(DataRowState.Added);
    
    // schreibe diese in den SQL Server         
    KundenTableAdapter.Update(rowToadd);
    AuftragTableAdapter.Update(addrow); // Exception, ID = NULL
    


  • So das schreibt schonmal die MSDN dazu:

    Datenadapter und verknüpfte Tabellen
    Das Vorhandensein von separaten Tabellen im Dataset hat unter anderem zur Folge, dass ein Datenadapter in der Regel nicht auf SQL-Befehle oder gespeicherte Prozeduren verweist, die Tabellen verknüpfen. Stattdessen werden die Informationen aus verknüpften Tabellen von verschiedenen Adaptern separat in das Dataset eingelesen. Danach wird ein DataRelation-Objekt verwendet, um die Einschränkungen zwischen den Dataset-Tabellen (z. B. sich überlappende Aktualisierungen) zu verwalten und Ihnen die Navigation zwischen verknüpften Hauptdatensätzen und untergeordneten Datensätzen zu ermöglichen.

    Angenommen, Sie arbeiten mit zwei verknüpften Tabellen in der Datenbank Northwind, Customers und Orders. Anstatt eine Verknüpfung anzugeben, die beide Tabellen zu einem einzigen Ergebnissatz kombiniert, definieren Sie in der Regel zwei Adapter, einen zum Füllen der Customers-Tabelle im Dataset und einen zweiten zum Einlesen der Order-Datensätze in eine andere Dataset-Tabelle. Die einzelnen Adapter enthalten wahrscheinlich Auswahlkriterien, um die Anzahl der Datensätze in den Datentabellen zu beschränken.

    Sie würden im Dataset auch ein DataRelation-Objekt definieren, das angibt, dass Order-Datensätze über das CustomerID-Feld mit Customer-Datensätzen verknüpft sind. Sie können die Tabellen immer noch einzeln verwalten. Dies wäre nicht möglich, wenn Sie vor dem Abrufen von Datensätzen aus der Datenquelle Tabellen verknüpft hätten. In Situationen, in denen Sie mit verknüpften Datensätzen arbeiten möchten, können Sie Eigenschaften und Methoden des DataRelation-Objekts aufrufen.

    Siehe direkter Link zur MSDN

    Lösung wäre demnach eine Insertion-Methode die man im Formular als private macht ordnungshalber. Über das DataRelation-Objekt so wie die es beschreiben
    habe ich noch nicht angetestet.

    Dann mal naschauen welche Tabellen involviert sind.
    Pro Tabelle ein DataSet worauf Du deine Kundenauftragsdaten aus der TXT aufspaltest!

    N DataSet N Commands (mit INSERT-SQL-Statments)

    oder

    N Commands (mit INSERT-SQL-Statments) daraus könnte man auch N OleDbAdapter machen und über den Assistenten das INSERT genereieren lassen.
    Im übrigen so haben es die zertifizierten Microsoft-Dozenten auf der Firmenschulung wo ich mal war auch immer gemacht.



  • Du musst also die Funktionalitäten des Quellcodes in dieser Hinsicht anpassen
    das DataSet hat leider nicht solche Inteligenz bzw. die DatenbankProvider spalten solche INSERTS nicht auf.



  • Pro Tabelle ein DataSet? Aber dann ist die Verknüpfung im DataSet doch wieder dahin?



  • Wiso Du kannst z.B ein SELECT auf nur eine Tabelle der DB machen. Vom Frontend aus betrachtet über die Datenleitung zum SQl-Server.
    Anhand dieses SELECT bildet das DataSet die Beziehung, als Kopie, selbstverständlich ab. Da wird dann nichts drin stehen ist noch mit der Tabelle verknüpft etc.



  • Aber mom ich werde es mal antesten ich kann Dir dann mal Bescheid geben wenn es funzt:

    Datenadapter und verknüpfte Tabellen
    Das Vorhandensein von separaten Tabellen im Dataset hat unter anderem zur Folge, dass ein Datenadapter in der Regel nicht auf SQL-Befehle oder gespeicherte Prozeduren verweist, die Tabellen verknüpfen. Stattdessen werden die Informationen aus verknüpften Tabellen von verschiedenen Adaptern separat in das Dataset eingelesen. Danach wird ein DataRelation-Objekt verwendet, um die Einschränkungen zwischen den Dataset-Tabellen (z. B. sich überlappende Aktualisierungen) zu verwalten und Ihnen die Navigation zwischen verknüpften Hauptdatensätzen und untergeordneten Datensätzen zu ermöglichen.
    *

    D.h. Ich vollziehe es mal an einem Beispiel mit dem DataRelation-Objekt nach
    kann etwas dauern, ich hoffe Du hast etwas Zeit?



  • Variante 2 OleDbAdapter als Klasssenmember des Formular, mit
    2 lokalen DataSet in einer INSERTION-Methode (btnInsert_Click)

    private void btnInsert_Click(object sender, System.EventArgs e)
    		{
    			DataRow drNewThemenGebiet;
    			DataRow drNewVokabel;
    
    			DataSet dsThemengebiet = new DataSet();
    			DataSet dsVokabeln     = new DataSet();
    
    			/* Zwischen der Tabelle "Themengebiete" zur der Tabelle "Vokabeln"
    			 * besteht bei mir über "Themengebiete.Themengebiet" eine 1:n Beziehung nach "Vokabeln.Themengebiet"
                 * "Themengebiet" ist PS in Tabelle "Themengebiete" und FS in Tabelle "Vokabeln"
    			 */
    
    			oleDbDataAdapter1.Fill(dsThemengebiet);
    			oleDbDataAdapter2.Fill(dsVokabeln);
    
    			try
    			{
    
    				drNewThemenGebiet                 = dsThemengebiet.Tables[0].NewRow();
    				drNewThemenGebiet["Themengebiet"] = txtThemengebiet.Text;
    
    				drNewVokabel                      = dsVokabeln.Tables[0].NewRow();
    				drNewVokabel["Fehler"]            = txtFehler.Text; 
    				drNewVokabel["Themengebiet"]      = txtThemengebiet.Text;
    				drNewVokabel["WortDeutsch"]       = txtWortDeutsch.Text;
    				drNewVokabel["WortEnglisch"]      = txtWortEnglisch.Text;
    
    				// So jetzt reorganisieren wir in der DB die Tabelle "Themengebiet" mit neuen Daten
    				try
    				{
    					dsThemengebiet.Tables[0].Rows.Add(drNewThemenGebiet);
    					oleDbDataAdapter1.Update(dsThemengebiet);
    				}
    				catch(System.Exception ex)
    				{
    					// Du bekommst wenn ich bei mir versuch ein Themegebite was schon existiert hier eine MessagBox
    					// weil es ist was existent Aber es wird über die DB-Verbindung auch nichts eingefügt
    					// Datenintegrität ist also safe
    					MessageBox.Show(this,ex.Message);
    
    					// Und deshalb könnte ich eine MessageBox an der Stelle auch weglassen und das ignorieren.
    				}
    
    				// So jetzt reorganisieren wir in der DB die Tabelle "Vokabeln" mit neuen Daten an der Stelle sage ich es gibt mehrere Übersetzunge für ein Wort entsprechend ist meine DB auch ausgelegt
    				try
    				{
    					dsVokabeln.Tables[0].Rows.Add(drNewVokabel);
    					oleDbDataAdapter2.Update(dsVokabeln);
    				}
    				catch(System.Exception ex)
    				{
    					MessageBox.Show(this,ex.Message);
    				}
    
    				/* Zum prüfen ob reduntandte Datensätze entstehen oder ob Sie
    				 * durch eine enstprechend konfigurierte DB abgefangen werden
    				 * 
    				 * Ergebniss: Bei entsprechend konfigurierter DB wird die DB auf Datenintegrität
    				 * Testobjekt bei mir ACCESS
    				 * 
    				 * Relationen bleiben gewahrt!!! So wie die DB eben aufgesetzt ist
    				 */
    
    				dsThemengebiet.Clear();
    				oleDbDataAdapter1.Fill(dsThemengebiet); 
    				dataGrid1.DataSource = dsThemengebiet.Tables[0];
    
    				dsVokabeln.Clear();
    				oleDbDataAdapter2.Fill(dsVokabeln); 
    				dataGrid2.DataSource = dsVokabeln.Tables[0];
    			}
    			catch(System.Exception ex)
    			{
    				MessageBox.Show(this, ex.Message);
    			}
    		}
    

    Läuft über je ein Adapter pro Tabelle zum Insert nehme ich dann innerhalb der Methode pro Tabelle ein "lokales" DataSet. Die sind dann nur temporär solange vorhanden wie eben Lebensdauer der aufgerufene Methode ist.

    So im Projekt habe ich für jede tabelle die Daten bekommt 1 extra Adapter.
    Diese sind immer "für eine Tabelle konfiguriert" und haben auch nur ein INSERT und SELECT-Command für jeweilige Tabelle.

    Meine 1 Tabelle enthält für meinen Vokabeltrainer alle Themengebiete über eine Spalte "Themengebiet" dort habe ich beim erstellen in ACCESS das CONSTRAINT
    "UNIQE" draufgedrückt.

    So Du hast ja nun Angst das die Relationen kaputt gehen? Ich habs getestet
    also bei mir wird alles wenn die DB entsprechend konfiguriert ist eingehalten!
    Wenn der User was falsches versucht wird es um die Integrität zu wahren nicht von der DB gemacht.

    Ich bekomme über die DB-Connectiviy lediglich eine Exception in mein Frontend rein. "User Du wolltest etwas machen was laut DB nicht erlaubt ist, ich habe Dein Vorhaben mal ignoriert" ums mal umgangsdeutsch im Volksmud zu erklären. Das kann ich ignorieren oder ich verunsicher den User durch ein Meldungsfenster....



  • Ich weiss das Beispiel ist nicht mit dem DataRelation-Objekt damit mich jetzt niemand drauf festnagelt. Aber es tut bei mir seine Arbeit so wie erwartet.

    Das DataRelation-Objekt dient auch lediglich um Relationen festzustellen! Beispielsweise
    wenn ich ein SELECT auf Tabellen mache werde ich wenn Tabellen über PS und FS verknüpft sind entsprechendes in dem Objekt wiederfinden.

    Aber bekanntlich kann ich auch DB's bauen mit CREATE und verzichte auf PS und FSsses sollte man nicht machen. Dabei kann ich die Integrität nurnoch durch sinnvolle DML-SQL-Statments sicherstellen. Es gibt bekanntlich keine Einschränkungen.



  • DeinDataSetMitDerRelationAus2Tabellen.Clear();
    			DeinAdapterMitDerRelationAus2Tabelle.Fill(DeinDataSetMitDerRelationAus2Tabellen);
    			MessageBox.Show(DeinDataSetMitDerRelationAus2Tabellen.Tables.Count.ToString());
    

    Ich verwende ja ACCESS habe eine OledbAdapter der auf 2 verknüpfte Tabellen, ausgestattet mit FS und PS 1:n verbunden sind, konfiguriert ist!
    Trotzdem sagt mir mein DataSet es hat nur 1 Tabelle nach dem Fill

    An der Stelle kann ich Dein Problem bei mir schon nichtmehr nachsimulieren!
    Aber das Codebeispiel wie man ein INSERT auf solche Tabellen macht funktioniert
    bei mir. Es macht keine Redundanzen in die DB alle relationen bleiben gewahrt.

    Müsstes den Code nur auf deine struct ummünzen, dort wo ich über die TextBoxen zuweise muss Du deine Daten aus Deiner struct rausholen und
    in entsprechende Items der neuen Datenzeile quetschen! Und Du brauchst noch entsprechend deiner Aufgabe Dklaratione zu DataTable/DataRow-Objekten sollte klar sein...

    Gruss sclearscreen



  • Da hat man Dich wohl verdonnert ein Kundenauftragsystem vom SQL-Server
    in ein Frontend zu implementieren?

    Oder baust Dir wohl eine Erleichterung? Weil Du selbst aussendienstlich
    Auftragsaquise machst, um spät am Feierabend noch Neuaufträge auf den SQL-Server zu spielen!

    So ich geh erstmal und tütele an meinem Kram rum.



  • danke, werde ich morgen mal probieren, heute hatte ich keine Zeit. Ist im Übrigen ein Teil aus einem CRM System (soll es jedenfalls mal werden).

    Vielen Dank und schönen Gruß!



  • Okay, das Problem ist wohl, dass ADO.NET hinsichtlich Auto Werten bei mehr als einer Tabelle gern ins schwimmen kommt. Nach allem was ich ausprobiert habe, gibt es im Prinzip zwei Workarounds für dieses Problem.

    1. Im DataSet den betreffenden Tabellen mitteilen, dass die als ID einen Negativen Wert nehmen, und diesen abwärts inkrementieren sollen.
    So erzeuge ich einen neuen Mastertabellen Eintrag (ID -1) und muss diesen dann per

    TableAdapter.Update(Tablename);
    

    in den SQL Server schreiben.
    Nun ein SELECT Statement absetzen um den vom SQL Server an dieser Stelle wirklich zugewiesenen Key zu erfragen. Diesen dann in die Fremdschlüsselspalte der untergeordneten Tabelle einfügen. Die untergeordnete Tabelle, sofern sie denn den richtigen ID aus dem SQL Server bekommen hat, kann auch am Ende der for Schleife erst in den Server geschrieben werden.

    Der Vorteil hierbei ist, dass auch wenn mehrere User in der DB arbeiten, sie sich so nicht in die Quere kommen.

    2. Statt einer INT für den Primärschlüssel einfach einen GUID einsetzen, diesen, beim Einlesen des Datensatzes in die DataTable erzeugen und der untergeordneten Tabelle zuweisen. Zum Schluss die Tabellen in den Server schreiben. Funktioniert auch

    Hat beides Vor- und NAchteile und sollte beides auch in Umgebungen mit mehrerern Benutzern funktionieren.



  • Auch wenn Du es gelöst hast nur falls es von Interesse ist folgender Link
    sollte auch zur Thematik passen.

    MSDN über automatische Primärschlüssel

    Dieses PDF habe ich jetzt auch schon mal meinem Datenmoloch gegeben.


Anmelden zum Antworten