Autor Beitrag
lilalaunebaer
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Sa 27.09.08 11:47 
verwendetes Datenbanksystem: <MySQL>

Hallo,

ich habe ein Designproblem bezüglich den Autoincrement (also effektiv die Key-) spalten einer datenbank.

wie man ja inzwischen weiß ist das konzept der stark typisierten datenbankrepräsentation im speicher per DataSet eine tolle sache.
man lädt beim programmstart alle tabellen voll, die irgendwie zusammenhängen, und dann arbeitet man darauf und updatet wieder in die wie-auch-immer geartete datenbank per adapter. So.

nur es kommt zum konflikt wenn man sowohl im dataset als auch in der mysql-tabelle auto_increment-spalten benutzt:
wenn ich in der quelldatenbank autoincrement-spalten habe, aber gleichzeitig effektiv gesehen nur im programm selber zeilenhinzufüge, wird das dataset (weil es ja jedesmal von null anfängt) irgendwann lokal verschiedene (immer KLEINERE) ID's vergeben, z.B. für einen neuen Kunden nach dem x-ten programmstart ID 5, und dann in irgend einer child-tabelle natürlich 5 als kunden-ID eintragen. wenn man das ganze jetzt in die quelldatenbank schreibt, wird mysql seine eigene neue ID für den kunden anlegen, und zwar nach alter manier den aktuellen auto_increment wert, der natürlich inzwischen bei 150 oder so liegt weil mysql ja nicht jedesmal bei null anfängt. also bekommt der kunde id 150, und in der childtabelle wird (weils ohne autoinc ist) schön die ID 5 eingetragen. Klar: beim nächsten start meckert das .NET-Dataset über fehlende referenzielle integrität weil murksdaten in der DB stehen.
eine lösung wäre natürlich sich zum programmstart die aktuellen autoinc-werte für jede tabelle zu holen.

aber das kann's ja nicht sein, das ich mir bei jedem programmstart alle autoincrement-werte aus der quelldatenbank holen muss und diese dann den autoincrement-seed werten der jeweiligen datasettabellen zuweise!

meine lösung ist im moment, einfach alle spalten der mysql-DB ohne auto-increment zu führen und die gesamte referentielle integrität vom programm durchführen/warten zu lassen; also alle tabellen im DataSet.xsd haben die jeweilige autoincremen-property gesetzt mit default seed 0. dies ist zwar okay solange das programm das einzige ist, das auf der DB arbeitet. wenn man nun aber von anderen quellen aus an der DB arbeiten will (z.B. weil die aufm webserver liegt und man per html/php daten einfügen möchte) hat man das problem sich mit den fehlenden autoincrement-spalten herumschlagen zu müssen.

also, gibt's da ne ordentliche lösung, die natürlich idealerweise beinhaltet das die ensprechenden spalten in der MySQL-DB MIT auto_increment eigenschaft sind?

danke!
lilalaunebaer
Ehemaliges Mitglied
Erhaltene Danke: 1



BeitragVerfasst: Mi 22.10.08 12:58 
Titel: gelöst!
hallo leute!

also hier ist ja nicht so viel los ..

inzwischen habe ich das problem für meinen fall gelöst:

da der MySQL Connector/.NET im zusammenhang mit dem VisualStudio-Plugin von MySQL leider gerade kein automatisches rücklesen und aktualisieren der effektiv zugewiesenen PrimaryKeys unterstützt, habe ich selber hand angelegt und mir folgende lösung gebastelt (natürlich ohne garantie, aber bei korrekter einbindung klappts ganz gut)

ausblenden volle Höhe C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
53:
54:
55:
56:
57:
/* der array adapters enthält alle MySqlDataAdapter-Instanzen der betroffenen typisierten Adapter, also der über <meinadapter>.Adapter verfügbare gewrappte mysqldataadapter */
        void irgend_ein_init() {
        foreach (MySqlDataAdapter adpt in adapters) {
                adpt.RowUpdated += new MySqlRowUpdatedEventHandler(adpt_RowUpdated);
            }
        }

        void adpt_RowUpdated(object sender, MySqlRowUpdatedEventArgs e) {
            if (e.StatementType == StatementType.Insert) {
                updateNewIDs(dsStammdaten, sender as MySqlDataAdapter, e.Row);
            }
        }

        /// <summary>
        /// Lädt für eine neu eingefügte Zeile die effektive ID aus der Datenbank und
        /// setzt sie in der entsprechenden Zeile in der Datenbank sowie allen
        /// Childtabellen an der ensprechenden Stelle. So wird die Referentielle Integrität
        /// gewahrt und das Autoincrement kann von der Datenbank erledigt werden.
        /// <remarks>
        /// ACHTUNG - Setzt voraus:
        /// - Alle PrimaryKeys sind eindimensional - gegeben bei AutoIncrement-Feldern
        /// - DataSet wurde mit dem DataSet-Designer von VisualStudio 2008 erstellt 
        /// (andere versionen - keine Garantie. z.B. erstellt der generator TableMappings, die
        /// essentiell fürs herausfinden der aktuell betroffenen Tabelle ist; sonst müsste
        /// man den ganzen code kopieren für jeden einzelnen adapter. schade das der generator
        /// keine oberklasse für die typisierten adapter generiert!
        /// - Für jede Relation muss entsprechend im Designer eine DataRelation erstellt worden sein,
        /// sonst funktioniert das child-table-updaten nicht.
        /// - Das wichtigste:KEINE GARANTIE FÜR FUNKTIONALITÄT! Ich habs noch nicht ausführlich getestet,
        /// lief aber bei ersten tests sehr gut.
        /// </remarks>
        /// </summary>
        /// <param name="ds">Synchronisiertes DataSet</param>
        /// <param name="adapter">Der DataAdapter zur Tabelle</param>
        /// <param name="insertedRow">Neu eingefügte Zeile</param>
        private void updateNewIDs(DataSet ds, MySqlDataAdapter adapter, DataRow insertedRow) {
            // Dem Adapter zugeordnete Tabelle identifizieren
            DataTable tab = ds.Tables[adapter.TableMappings[0].DataSetTable];
            // Es muss nur ein Update durchgeführt werden, falls Childtabellen existieren
            if (tab.PrimaryKey.Length > 0 && tab.ChildRelations.Count > 0) {
                // Primary Key holen
                String key = tab.PrimaryKey[0].ColumnName;
                long newID = adapter.InsertCommand.LastInsertedId;
                int oldID = (int)insertedRow[key];
                // Neue ID in der DataRow setzen
                tab.Rows.Find(insertedRow[key])[key] = newID;
                // Alle Childtabellen durchgehen und ID's aktualisieren
                foreach (DataRelation rel in tab.ChildRelations) {
                    // Nur updaten, falls die Relation auch dem Primary Key assoziiert ist.
                    if (rel.ChildColumns[0].ColumnName.Equals(key)) {
                        foreach (DataRow row in rel.ChildTable.Select(key + "=" + oldID)) {
                            row[key] = newID;
                        }
                    }
                }
            }
        }


Das Backverfahren ist jetzt also:
MySQL-Datenbank: Alle Basistabellen haben eine AutoInc-Spalte als Primärschlüssel.

Designer: Import der Datenbanktabellen über des DataSource-Assistent.
- alle primärkeys haben automatisch unique, key und autoinc gesetzt, mit seed -1 und step -1.

Designer: Setzt "Hierarisches Update" auf True, um einen AdapterManager generiert zu bekommen, der das updaten gemäß der relationen übernimmt.
dies garantiert das der obige code funktioniert da die tabellen mit den primärkeys zuerst geupdatet werden, und danach alle referenztabellen.

Editor: beim laden der form / datamodule / etc (wo auch immer die adaptoren initialisiert werden) dann obigen Code zu den RowUpdated-Events der inneren MySqlDataAdaptern hinzufügen.

Runtime: arbeiten mit den programm. methode UpdateAll() vom adaptermanager benutzen, die rowupdated-events werden dann an der richtigen stelle ausgeführt.

Forms: falls ihr datagridview-komponenten benutzt, noch ein this.Validate() hinzufügen damit die evtl. angezeigten nummern aktualsiert werden.

BAM. fertig.