Access Datenbank bearbeiten

  • VB.NET

Es gibt 34 Antworten in diesem Thema. Der letzte Beitrag () ist von drschef.

    Problem beim Speichern

    Ich habe mich jetzt längere Zeit um das Speichern der aktuellen DataTables bemüht. Dabei hat die kompakte Methode von KSE (Me.TableAdapterManager.UpdateAll(Me.Dataset)) Fehler gebracht. Zum Fehler kam es auch beim Speichern nach der schrittweise Methode, die der ErfinderDesRades empfohlen hat und die ich noch mit einem MS-Beispiel abgeglichen habe:

    Quellcode

    1. Private Sub SaveDS()
    2. Dim deletedChildRecords As FVDataSet.VerknüpfungDataTable = _
    3. CType(FVDataSet.Verknüpfung.GetChanges(Data.DataRowState.Deleted), FVDataSet.VerknüpfungDataTable)
    4. Dim newChildRecords As FVDataSet.VerknüpfungDataTable = _
    5. CType(FVDataSet.Verknüpfung.GetChanges(Data.DataRowState.Added), FVDataSet.VerknüpfungDataTable)
    6. Dim modifiedChildRecords As FVDataSet.VerknüpfungDataTable = _
    7. CType(FVDataSet.Verknüpfung.GetChanges(Data.DataRowState.Modified), FVDataSet.VerknüpfungDataTable)
    8. Try
    9. 'Update gelöschter Sätze in untergeordeten Tabellen
    10. If deletedChildRecords IsNot Nothing Then VerknüpfungTableAdapter.Update(deletedChildRecords)
    11. 'Update übergeordneter Tabellen
    12. FotoverzeichnisTableAdapter.Update(FVDataSet.Fotoverzeichnis)
    13. SchlüssellisteTableAdapter.Update(FVDataSet.Schlüsselliste)
    14. 'Update neuer und geänderter Sätze in untergeordneten Tabellen
    15. If newChildRecords IsNot Nothing Then VerknüpfungTableAdapter.Update(newChildRecords)
    16. ' => Fehler:
    17. 'Der Datensatz kann nicht hinzugefügt oder geändert werden,
    18. 'da ein Datensatz in der Tabelle 'Fotoverzeichnis' mit diesem Datensatz in Beziehung stehen muss.
    19. If modifiedChildRecords IsNot Nothing Then VerknüpfungTableAdapter.Update(modifiedChildRecords)
    20. 'Änderungsstatus löschen
    21. FVDataSet.AcceptChanges()
    22. Catch err As Exception
    23. MessageBox.Show("Fehler beim Update der Datenbank")
    24. Finally
    25. If deletedChildRecords IsNot Nothing Then deletedChildRecords.Dispose()
    26. If newChildRecords IsNot Nothing Then newChildRecords.Dispose()
    27. If modifiedChildRecords IsNot Nothing Then modifiedChildRecords.Dispose()
    28. End Try
    29. End Sub


    Eine extra programmierte Prüffunktion der Relationen bei allen Sätzen brachte auch keine Erklärung. Auch die Beschränkung der Tests auf nur einen Kopfsatz führte zum Fehler. Damit sich das nachvollziehen lässt, ist hier die Oberfläche mit dem Kommentar:



    Zum besseren Verständnis noch die Designerdarstellung:



    und die Beschreibung der kritischen Relation:



    Manchmal ist es nur eine Kleinigkeit! ;(

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „drschef“ ()

    Beim Abspeichern mehrerer verknüpfter Tabellen ist eine bestimmte Reihenfolge einzuhalten: Bei Inserts müssen die übergeordneten Datensätze zuerst abgespeichert werden, denn wenn du die untergeordneten Datensätze einspeicherst, dann verweisen deren ForeignKeys auf Datensätze, die (noch) nicht da sind - Peng!

    Bei Deletes kannst du auch die übergeordneten Datensätze zuerst speichern, und die Löschweitergabe haut dann die untergeordneten weg - das ist sehr performant.
    Aber die untergeordneten Delete-Anforderungen darfst du dann nicht mehr an die DB senden, sonst will die da was löschen, was bereits weg ist - Peng!

    Und so kommt meine Speicher-Methode zustande: Die Deletes speichern mit AcceptRejectRule.Cascade, und alles andere normal abspeichern, und dabei immer die übergeordneten Tables zuerst.
    Vorraussetzung sind dabei Beziehungen mit Löschweitergabe, sowohl inne DB als auch im Dataset.
    Und ich guck deine DataRelation an, die hat keine Löschweitergabe :P

    gugge nochma mein Benamungs-Schema für DB-Entitäten, da äusser ich mich auch dazu, wie Relationen einzurichten sind.

    (also das ist meine Strategie, die zuverlässig funzt, es gibt auch annere Strategien, ebenso zuverlässig, und wenn man die Thematik beherrscht, kann man auch variieren, und sich sonstwas für Escapaden ausdenken...)

    Löschweitergabe

    Danke für die schnelle Stellungnahme von ErfinderDesRades.

    Die Reihenfolge glaube ich in meinem Quelltext eingehalten und habe sie auch mehrfach kritisch überprüft. Es werden keine Sätze gelöscht, sondern nur ein Satz in der Fotoliste aufgebaut. Es gibt also nur hinzugefügte Sätze zu speichern. Und zuerst erfolgt, wie die Quelle zeigt, das Update des übergeordneten Fotoverzeichnisses. Danach das Update neuer Sätze in der Tabelle Verknüpfung. Wo sollte denn nach Deiner Meinung die Reihenfolge der Kommandos beim Speichern anders sein? Alle Verweise sind in dem Screenshot nachvollziehbar und so glaube ich, richtig. Kann es evtl. daran liegen, dass die Tabelle 'Verknüpfung' in zwei Relationen eingebunden ist?
    kann an allem möglichen liegen. Und mit meiner Speicherstrategie hat dein Code auch kaum noch was zu tun.
    meine Strategie besteht aus folgenden Elementen:
    1. alle Relations mit Beziehungs-Einschränkung, Fremdschlüsseleinschränkung, Löschweitergabe, Aktualisierungsweitergabe.
      Da ist deine DataRelation schon untauglich für
    2. auch in der DB muß Löschweitergabe eingerichtet sein (wie siehts da bei dir aus? weil deine Relation ist ja schon nicht so)
    3. alle Updates in Top-Down-Reihenfolge, (sowohl die Delete-Updates als auch den Rest)
    4. bei allen Datarelations AcceptRejectRule.Cascade einstellen
    5. bei allen DataTables die DeletedRows in ein Array sammeln
      Du sammelst sie in ein neues Dataset - das ist vmtl. ein Grund des Fails bei dir
    6. diese DeletedRows updaten (wie gesagt: in TopDown-Order)
    7. bei allen Datarelations wieder auf AcceptRejectRule.None zurückstellen
    8. alle anneren DataTables updaten (topDown)

    Empfehlungen ErfinderDesRades

    Zunächst vielen Dank für die Hinweise.

    ... bei allen DataTables die DeletedRows in ein Array sammeln
    Du sammelst sie in ein neues Dataset - das ist vmtl. ein Grund des Fails bei dir

    Dabei bin ich dieser Empfehlung von MS gefolgt.

    Inzwischen sind Deine Hinweise nun verwirklicht. Zum Speichern ist die Befehlsfolge aus der 'Sample-Solution mit OleDb12-Treiber' zugrunde gelegt und so gut ich es verstanden habe an die DB-Struktur angepasst. Alle Relationen wurden an die 'Sample Solution' angeglichen. Ich habe eine Prüffunktion geschrieben, die allen Verweisen nachgeht und keine Fehler gebracht hat. Das lässt sich auch optisch an dem vorangegangenen Screenshot in den Daten nachvollziehen.

    Hier die Funktion zum Speichern:

    VB.NET-Quellcode

    1. Private Sub SaveDS()
    2. Try
    3. 'Evtl in Bearbeitung stehende Zellwerte als Eingabe übernehmen
    4. If Not Me.Validate Then Media.SystemSounds.Hand.Play() : Return
    5. With FVDataSet.Fotoverzeichnis '1. Übergeordnete Table
    6. 'FotoverzeichnisTableAdapter.Update() für gelöschte DataRows
    7. .ChildRelations(0).ChildKeyConstraint.AcceptRejectRule = AcceptRejectRule.Cascade
    8. Dim DeletedRows As DataRow() = .Select("", "", DataViewRowState.Deleted)
    9. Me.FotoverzeichnisTableAdapter.Update(DeletedRows)
    10. .ChildRelations(0).ChildKeyConstraint.AcceptRejectRule = AcceptRejectRule.None
    11. End With
    12. With FVDataSet.Schlüsselliste '2. Übergeordnete Table
    13. 'SchlüssellisteTableAdapter.Update() für gelöschte DataRows
    14. .ChildRelations(0).ChildKeyConstraint.AcceptRejectRule = AcceptRejectRule.Cascade
    15. Dim DeletedRows As DataRow() = .Select("", "", DataViewRowState.Deleted)
    16. Me.SchlüssellisteTableAdapter.Update(DeletedRows)
    17. .ChildRelations(0).ChildKeyConstraint.AcceptRejectRule = AcceptRejectRule.None
    18. End With
    19. Me.FotoverzeichnisTableAdapter.Update(FVDataSet.Fotoverzeichnis)
    20. Me.SchlüssellisteTableAdapter.Update(FVDataSet.Schlüsselliste)
    21. Me.FotoschlüsselTableAdapter.Update(FVDataSet.Fotoschlüssel) 'Nach zwei Richtungen untergeordnete Table => Fehler
    22. Media.SystemSounds.Asterisk.Play()
    23. Catch err As Exception
    24. MessageBox.Show("Fehler beim Update der Datenbank" & vbCrLf & err.Message)
    25. End Try
    26. End Sub

    Beim Speichern erscheint trotzdem wie gehabt der Kommentar:
    Der Datensatz kann nicht angefügt oder geändert werden, da ein Datensatz in der Tabelle Fotoverzeichnis mit diesem Datensatz in Beziehung stehen muss.

    Was ist da noch zu tun? Ich könnte eine auf das Wesentliche geschrumpfte Quelle zur Verfügung stellen.

    drschef schrieb:

    Was ist da noch zu tun?

    ganz prinzipiell: Den TryCatch wegmachen. Dann siehst du die Fehlerzeile. Ich glaub zwar nicht, dass das noch eine überraschung bringt, aber wie gesagt, aus prinzip.

    Fehlerzeile ist?

    TryCatch ist ein heißes Eisen

    jo, und dann häng doch mal son Teil gezippt an.
    Hallo ErfinderDesRades,

    die Fehlerzeile ist im übermittelten Quelltext mit einem Hinweis auf den Fehler versehen. Die Zeile enthält das nachfolgende Update der untergeordneten Tabelle 'Fotoschlüssel'.

    Ich danke für die Bereitschaft, in die Quelle zu schauen. Ich hänge sie an und bin gespannt, was dabei herauskommt. Das Problem tritt beim 'Speichern' nach 'Satz lesen' auf. Natürlich auch, wenn mit 'Datei lesen' 12 Sätze eingearbeitet werden.

    Und nicht immer bis in die Nacht hinein arbeiten!
    Dateien
    • FVK.zip

      (591,06 kB, 142 mal heruntergeladen, zuletzt: )
    ach jeh - ich hab ja nach "Datenbank in 10 Minuten" auf Movie-Tuts meine Strategie geändert. Dort erzeugt ja noch nur der Client die Primkeys, während ich inzwischen umgeschwenkt bin auf eine Strategie, wo sowohl Client als auch DB Primkeys erzeugen. Dabei gelten die Primkeys des Clients nur vorläufig, und wenn geupdated wird, werden die von der DB vergebenen endgültigen Primkeys schnell noch abgefragt und ins Dataset eingepflegt.
    gugge Autowerte inserten

    habich jetzt bei dir auch eingebaut und geht.
    Dateien
    • FVK00.zip

      (76,88 kB, 144 mal heruntergeladen, zuletzt: )

    Hilfe von ErfinderdesRades

    Ich habe die Sache erfolgreich getestet. Vielen, vielen Dank für die Nachtarbeit! Die Quelle zu meiner Hauptform war ja etwa richtig. Und durch Deine Anpassung hat alles funktioniert.

    Damit mein eigentliches Projekt auch zum Laufen kam, musste ich die ergänzende <DataSet>.vb und ihren Aufruf in <Projekt>.vbproj übernehmen. Aber wie soll ein Datenbankeinsteiger ohne diese Hilfestellung jemals eine relationale Datenbank unter Mitwirkung von Framework, VB, Access, Betriebssystem und mehr oder weniger veralteten MSDN (die sich alle ständig ändern) zum Laufen bringen. Ich habe in einer Menge von Forumsbeiträgen und Empfehlungen von MS bisher nirgends einen Hinweis auf diese Ergänzung gefunden, ohne die aber nichts läuft. Es kann doch nicht sein, dass sich die grundsätzliche Lauffähigkeit fast aktueller Werkzeuge (s.o.) auf so spezielle Anpassungen gründet.

    Mir macht auch immer noch zu schaffen, warum die (theoretisch) einfache Lösung mit 'Me.TableAdapterManager.UpdateAll(DataSet)' nicht geht. Die wird doch angepriesen, wie warme Semmeln und sie entbehrt nicht einer hohen Sinnfälligkeit. Mit Hilfe der Beschreibung der Datasets und Relationen ist doch alles bekannt, was ein sinngerechtes Rückschreiben der DB ermöglichen kann. Der Implizierung des aufwendigen und kritischen Codes, der dem Schreiben dient, einschließlich Deinem Geheimtipp steht doch nichts im Wege. Oder sehe ich das falsch? Das hieße, man könnte eine Klasse schreiben, die die ganze Logik umsetzt und ein funktionierendes 'UpdateAll' bereitstellt.

    Wichtig waren für mich auch viele Tipps am Rande, wie z.B. Deine Sicht auf die Try-Strukturen. Und ich habe bestimmt bald wieder eine Frage, denn jetzt geht's ja erst richtig zur Sache.
    ich bin fasziniert - du denkst genau meine Gedanken: "Das kann doch nicht wahr sein, dass so ein Wald-und-Wiesen-Task wie das Abspeichern von Änderungen so irrsinnig kompliziert sein muß! Alle notwendigen Informationen sind da, das muß man doch ein-für-allemal lösen können, statt da in jedem neuen Einzelfall das Rad zum millionenundersten Mal neu zu erfinden"

    Tatsächlich gibts annere DB-Provider, wo man auf dieses Gemurkse mittm Identity-Select nachschießen verzichten kann - etwa SqlServer unterstützt in-out-Parameter, da bekommt der Insert-Command den neuen PrimKey iwie gleich als Rück-Antwort, und dementsprechend sind dann auch die Inserts der TableAdapter anners generiert.
    Also bei SqlServer funzt TableAdapterManager.UpdateAll() so wie mans erwarten würde, aber Access ist leider zu doof dafür.

    Aber ich hab mal selbst eine generische Lösung aufgesetzt, und die haut bisher zu meine vollen Zufriedenheit hin, und damit kann man den ganzen Zusatz-Kram, also alle TableAdapter, und auch den TableAdapterManager gleich wieder runterschmeißen vom DatasetDesigner.
    Wenn du mal in den generierten DesignerCode guckst, stellste fest, dass ca. 50% des Codes sich mit diesen bekloppten Komponenten beschäftigte, also das sind ganz schnell mal 5000 Zeilen generierter Code (den du nunmehr weghauen kannst).
    Der Dialog mit Dir ist erfreulich und hat mich neugierig auf Deine Lösung gemacht. Aber heute muss ich erst mal zur Geburtstagsfeier. Andererseits hast Du den SQL-Server ins Spiel gebracht. So was in der Richtung hatte ich auch mal erwogen. Aber da ich vor einigen Jahren in der Industrie viele Access-Lösungen mit vielen Relationen gemacht habe, hoffte ich auf ein leichtes Spiel, was ein totaler Trugschluss war.

    SQL ist mir auch weitgehend vertraut und da wäre auch SQL-Server denkbar. Aber da müsste ich, nachdem ich extra ein Access 2010 gekauft habe, wohl gleich wieder neue (materielle) Voraussetzungen schaffen. Und ohne Lehrgeld geht es sicher auch nicht ab. Noch wäre es Zeit zum Umstieg.
    Ob nu Access oder SqlServer - das geht doch immer mit Sql.
    Und wenn man mit typDataset arbeitet braucht man so gut wie garkein Sql.

    ich pers. mag SqlServer nicht besonders - weil das ist ein sowas von oversized Monstrum. Und Ms' Geschäfts-Politik nervt mich, dass die nur ihr neuestes Hausprodukt ordentlich unterstützen, dabei gibts gleichwertige oder bessere DB-Systeme, v.a. weniger aufgeblasen, für lau.
    Und die grafische Oberfläche vom SqlServer findich sowas von schlecht - aber das scheint bei Datenbänkern beliebt zu sein: schlechte Oberflächen. Also MySql oder SqLite (beide als DBProvider haben wesentliche Vorzüge gegenüber SqlServer) - aber was als Design-Oberfläche bereitgestellt wird - kaum zu glauben.
    Echt: Access hat das beste FrontEnd.

    Wobei ich insgesamt garnet für Datenbanken bin, weil so "kleine Sachen" kann man locker auch mit Dataset alleine abhandeln - DB-Programmierung ohne Datenbank.
    Dein Foto-Teil etwa: Wie viele Fotos haste? 10000? passt locker in ein typDataset, wird sich in < 3MB abspeichern lassen (während SqlServer unter 4MB ja garkeine Datenbank erstellen kann).

    Welche Datenbank?

    Das waren sehr nützliche Hinweise. Sie reichen aus, um vom SQLServer Abstand zu nehmen.

    Die aktuelle Fotoverwaltung arbeitet jetzt noch mit einer ASCII-Datei als Eingabe. Diese muss natürlich beim Start verarbeitet werden. Im Moment sind da 15.000 Fotos eingespeichert und es sollen, wie mir der Anwender angekündigt hat, um die 40.000 werden. Dann könnte die Initialisierung des ungebundenen DataGrid 1-2 Minuten dauern. Das stört mich mehr als den Anwender und war der Anlass, auf DB umzusteigen. Es hat mich auch selbst gereizt, wieder einmal eine DB praktisch einzusetzen.

    Deine Anregung zur 'DB-Programmierung ohne Datenbank' werde ich mir auf jeden Fall ansehen. XML taugt auch für kompliziertere Strukturen. Im Moment ist die Basis eine sequentielle Textdatei mit einer einzigen durchgängigen Satzstruktur, nämlich die, welche man im Haupt-Grid sieht. Alles andere wird abgeleitet. Möglicherweise dauert der Verarbeitungsprozess bei beiden Verfahren auf Datei-Basis ähnlich lange. Ich vermute, deswegen hattest Du die Zahl 10.000 ins Spiel gebracht.

    Was Access anbelangt, da hat mir die Oberfläche auch immer sehr gefallen. Da bleibt nur noch eine neugierige Frage. Würde das Handycap bei der Speicherung der Daten (früher sagte man wohl Bug dazu) beim Einsatz von mdb auch auftreten oder beschränkt sich das auf accdb und/oder OLEDB.12.0. Dann wäre ja der Umstieg auf mdb noch eine Überlegung wert. Möglicherweise hat da auch der Anwender, der teilweise noch mit XP arbeitet auch bessere Karten. Für den musste ich nämlich bei dem Excel-Export des Schlüsselverzeichnisses auch schon gewisse Korrekturen machen.
    "Bug" würde ich das nicht nennen, "Handycap" passt ziemlich gut. Es ist halt ein Entwicklungsstand, den einige DBProvider erreicht haben, annere noch nicht. In meine DBExtensions ist die Unterscheidung gewissermaßen in Code gegossen, indem ich ein klein Dictionary für die smarten Provider anlege:

    VB.NET-Quellcode

    1. ' find out LastRowIdSqlFunction, if the provider provides a specific function to re-query
    2. '°autogenerated primKeys
    3. Dim _SmartProviders = NewDictionary( _
    4. NewKeyValue("System.Data.SqlClient", "SCOPE_IDENTITY"), _
    5. NewKeyValue("MySql.Data.MySqlClient", "last_insert_id"), _
    6. NewKeyValue("System.Data.SQLite", "last_insert_rowid"))
    7. _SmartProviders.TryGetValue(Provider, LastRowIdSqlFunction)
    So riesen-Geniestreich ist das auch nicht, ein SmartProvider unterstützt es nur, dem CommandText eines INSERTs das nachschießende SELECT gleich anzuhängen, was die LastRowID abfragt ( &= ... ):

    VB.NET-Quellcode

    1. Adapter.InsertCommand.CommandText &= ";".And( _
    2. "SELECT ", .ColName, " FROM ", .TableName, _
    3. " WHERE ", .ColName, " = ", _DtsAttach.LastRowIdSqlFunction, "()")
    (.ColName ist der Name der ID-column)