allgemeine Zugriffs-Lösung für: MySql, Access, SqlCe, SqlServer, DatasetOnly

    • VB.NET

    Es gibt 15 Antworten in diesem Thema. Der letzte Beitrag () ist von DianonForce.

      allgemeine Zugriffs-Lösung für: MySql, Access, SqlCe, SqlServer, DatasetOnly

      Download Sources
      AllTogether2008.zip
      AllTogether2010.zip
      AllTogether2020.zip

      Zur Erläuterung von Konzept und Verwendung der Sources hab ich ein Tutorial verfasst, von dem aus ich hierrauf verlinke:
      DBExtensions - allgemeine Lösung der Daten-Persistierung via Datenbanken[/url]

      Der Download heißt sinnigerweise "AllTogether", denn ich habe 6 Sample-Projekte geschrieben, und alle in eine Solution gestopft. Um ein anderes Sample auszuprobieren muß man es also als Startprojekt festlegen.
      Das Projekt "SqlCeSamples" stellt sogar 2 Samples dar, je nachdem welches StartFormular gewählt wird: frmIncrementFill oder frmM_N_View. Näheres dazu im Tutorial.

      Innerhalb der einzelnen Sample-DateiOrdner sind auch spezifische Solution-Dateien, über die man jedes Sample einzeln öffnen kann. So sieht man deutlich, wie die datenbank-basierten Samples alle 3 Dlls einbinden, während "DatasetOnly" mit "nur" 2 Helper-Dlls auskommt.

      Ich veröffentliche die Samples zum Tut deshalb so etwas umständlich, weil ich hier bei jedem Update eine Post poste.
      Interessenten des Lösung können also diesen Thread abonnieren, und haben dann eine Update-Benachrichtigung, weil VBP ja an jeden Abonnenten eines Threads eine Email schickt, wenn ein neuer Post hinzugekommen ist.

      Bisherige History:
      • 1.10.11: first posted
      • 7.11.11:
        1. Sql-Generierung sehr optimiert
        2. Code-Design geändert, von DataTable.FillByParentrow(parentRow) auf parentRow.FillChildTables(tables())
        3. Unterstützung für SqlServer zugefügt

      • 8.11.11: Bugfix der DataTable.Fill(conditions, values()) - Extension-Methode
      • 12.11.11: Unterstützung auch von Sql-Funktionen, wie SUM(), BETWEEN, IN() und sowas
      • 25.11.2023: Endlich mal in CollectionX meine .ToDictionary()[/color]-Extension gelöscht, weil ab .Net FW 4 erzeugt die einen Namenskonflikt, weil MS hat nu eine ebensolche eingeführt.


      Wichtige Hinweise zu den Sample-Projekten:
      1. Projekt "SqlServerTest": Meine SqlServer-Instanz heißt "SqlServerExpress2008". Bei euch wird sie höchstwahrscheinlich anders heißen - dementsprechend müsst ihr den Connectionstring in den Anwendungseinstellungen anpassen.
      2. Projekt "MySqlTest": Ist nicht lauffähig. Habich entwickelt anhand einer DB im Internet (nichtmal meine - vielen Dank an Bernd!! :thumbsup: ), und daher das Passwort im Connectionstring entfernt.
      Dateien

      Dieser Beitrag wurde bereits 44 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Heuer habich was drangebastelt, damit die Persistierung selbständig erkennt, ob eine Dataset-DataTable in der DB ühaupt existiert oder nicht.
      Damit kann man im Dataset zusätzliche temporäre Tabellen anlegen, die keine Auswirkungen haben auf das, was abgespeichert wird.
      Im Northwind-Sample nutze ich das in frmRefillStore, um an die Kategorien eine CheckboxDatagridViewColumn dranzumachen.
      Der User kann diese Checkboxen checken oder unchecken, und damit eine Mehrfach-Auswahl treffen.
      Ich zeige mal den Mechanismus, wie eine Auswahl getroffen wird:

      VB.NET-Quellcode

      1. Private Sub KategorienDataGridView_CellContentClick(ByVal sender As Object, ByVal e As _
      2. DataGridViewCellEventArgs) Handles KategorienDataGridView.CellContentClick
      3. If e.ColumnIndex <> 0 Then Return
      4. Dim cat = KategorienBindingSource.At(Of KategorienRow)(e.RowIndex)
      5. If cat.Null Then Return
      6. ToggleCatChecked(cat)
      7. End Sub
      8. Private Sub ToggleCatChecked(ByVal cat As KategorienRow)
      9. Dim value = Not cat.Checked
      10. If value Then
      11. NorthWindDts.CheckedCategory.AddCheckedCategoryRow(cat)
      12. Else
      13. cat.GetCheckedCategoryRows.First.Delete()
      14. End If
      15. End Sub
      Die Checked-Property der umzuschaltenden KategorienRow wird nicht direkt gesetzt, sondern stattdessen wird ein untergeordneter CheckedCategoryRow-Datensatz angelegt bzw. gelöscht.
      Mehr ist nicht zu tun, denn die KategorienRow.Checked-Property ist als berechnete Spalte im Dataset angelegt, und als Berechnung habich reingeschrieben, dass nachgeguckt wird, ob ein untergeordneter Datensatz vorhanden ist oder nicht: Count(Child(FK_Kategorien_CheckedCategory).ID)>0
      Aber gugge selber Dataset-Designer - da ist das eingegeben:



      Dann habichnoch eine verschärfte Filterung drangebastelt, die alle Kategorien markiert, die Artikel enthalten, die nachzubestellen sind.
      Die Filter-Methode verwendet denselben Mechanismus, um Kategorie-Markierungen zu setzen.
      Anschließend werden 2 BindingSource-Filter gesetzt: Der eine filtert alle nachzubestellenden Artikel, der andere filtert die Kategorien, die gecheckt sind, sodaß eine Gesamt-Ansicht entsteht, die nur noch die nachzubestellenden Artikel präsentiert.

      Naja, man hätte auch ganz einfach eine normale Boolean-Spalte an die Kategorien-DataTable dranmachen können - die funktioniert ja von vornherein wie gewünscht.
      Aber dafür wäre auch die DB zu ändern. Und die Information, was grad gecheckt ist, gehört eiglich nicht in eine Datenbank.
      Hört sich vlt. übergenau an, aber zB das mit dem SpezialFilter - das kann durchaus nerven, wenn man beim Schließen immer gefragt wird: "Änderungen abspeichern?" - und dabei hat man garnix geändert - nur mal die gefilterte Ansicht aktiviert.

      Wie gesagt: DBExtensions erkennt selbsttätig, ob eine DataTable auch in der DB angelegt ist. Bei DatasetOnly ists mangels DB bisserl anners, dort muß man das codeseitig festlegen, falls eine DataTable ausgeschlossen wern soll:

      VB.NET-Quellcode

      1. NorthWindDts.CheckedCategory.Persist(False)
      (in frmDatasetOnly auffer Artikel-TabPage habe ich aber nur die Checkboxen implementiert, nicht den SpezialFilter der Northwind-Solution)

      Achso: Er kann jetzt auch SqLite :)
      SqLite ist eiglich besser als SqlServerCe:
      • kompakter
      • schneller
      • moderneres Handling beim Insert von Primärschlüsseln
      Nachteile sind leider:
      • Zum vernünftigen Anlegen einer DB, inklusive Primärschlüsseln und Beziehungen habich noch kein wirklich gutes Tool gefunden. Ich verwendete sqliteadmin.orbmu2k.de/ , aber um AutoIncrement und eine Beziehung mit LöschWeitergabe einzurichten muß man eine Abfrage in Sql komponieren und abfahren.
        Beispiel wie son Sql aussieht, ist inne Sample-s3db - Datenbank-Datei dabei - zum Angugge zB. genanntes Tool verwenden.
        (Andererseits: mit SqlServerCe ists fast ebenson Krampf :()
      • man muß SQLite auf dem Zielrechner installieren, oder aber Minimum die System.Data.SQLite.dll mitliefern. Die System.Data.SQLite.dll ist aber ganz einfach im Download-Zip von SQLite enthalten - kann man nehmen ohne jede Installation.

      Dieser Beitrag wurde bereits 8 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Hab bisserl was umgestellt, zB bei SqLite musste ich feststellen, das wird (zumindest in meiner Version von Fw4 nicht unterstützt, wohl aber von Fw3.5. Vlt. auch ein Installationsproblem bei mir - keine Ahnung.
      Dafür funktioniert SqlCe4.0 jetzt sehr schön - die Version 3.5 hatte ja bisserl rumgezickt.

      Worauf ich v.a. aufmerksam machen wollte ist DbGenerator. Das ist ein Tool, dem man ein typisiertes Dataset hinschmeisst, und es generiert die passende Datenbank dazu.
      Auch hier werden alle möglichen Db-Systeme unterstützt, die in .Net verfügbar sind.

      ErfinderDesRades schrieb:

      Hab bisserl was umgestellt, zB bei SqLite musste ich feststellen, das wird (zumindest in meiner Version von Fw4 nicht unterstützt, wohl aber von Fw3.5. Vlt. auch ein Installationsproblem bei mir - keine Ahnung.

      Ich hab mich jetzt auch mal ein bisschen mit SqLite auseinandergesetzt und kann dir dazu einige interessante Dinge erzählen:

      Es gibt in der Tat sehr viele verschiedene Versionen der System.Data.SQLite.dll, und zwar für jede Framework-Version (2.0, 3.5, 4.0, 4.5) und dann nochmal für jede Architektur (32-bit oder 64-bit) eine. Ich habe für meine Projekte (egal ob beruflich oder privat) nun entschieden, mich zunächst auf die fürs Framework 4.0 zu konzentrieren.

      Download der verschiedensten Versionen hier: system.data.sqlite.org/index.h…/trunk/www/downloads.wiki
      Ich bevorzuge die "Precompiled Statically-Linked Binaries", daraus kann man sich einfach die System.Data.SQLite.dll nehmen und den Rest aus dem Paket benötigte ich bisher nicht.

      Wichtig ist allerdings, dass es von der DLL keine "Any-CPU" Variante gibt, auch wenn "This binary package features the mixed-mode assembly..." mir suggerierte dass das so sei, aber vermutlich hab ich das falsch interpretiert. Man muss also in einem Programm oder einer Bibliothek, die SqLite verwenden will und mit "Any CPU" kompiliert wird, dafür sorgen, dass die richtige SQLite-Assembly benutzt wird.

      Hierzu habe ich folgende Lösung mit Hinweisen die ich im Netz gefunden habe realisiert:

      Lade sowohl die 32bit- als auch die 64bit-Variante der DLL herunter und stelle sie mit deinem Programm/deiner Bibliothek nicht im selben Pfad, sondern z.B. in Unterverzeichnissen zur Verfügung. Bei mir wäre das zzt. [AppPath]\SqLite\x86 und [AppPath]\SqLite\x64. Damit nun die richtige davon ausgewählt und verwendet wird, hängst du dich so früh wie möglich (z.B. im statischen Konstruktor deiner Datenbank-Hauptklasse) an das AssemblyResolve-Ereignis von AppDomain.CurrentDomain ran. Das Event wird immer (nur) dann aufgerufen, wenn das Framework versucht eine Assembly ausfindig zu machen, sie aber nicht finden kann (weil sie in diesem Fall nicht im Anwendungspfad sondern nur in Unterverzeichnissen davon liegt). Und dort kümmern wir uns dann um den Rest:

      C-Quellcode

      1. static Database()
      2. {
      3. AppDomain currentDomain = AppDomain.CurrentDomain;
      4. currentDomain.AssemblyResolve += currentDomain_AssemblyResolve;
      5. // ...
      6. }
      7. private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
      8. {
      9. if (args.Name.StartsWith("System.Data.SQLite,")) // ja, das Komma ist Absicht ;)
      10. {
      11. Assembly result = null;
      12. string subPath = "";
      13. if (IntPtr.Size == 8) { subPath = "x64"; }
      14. else { subPath = "x86"; }
      15. subPath = Path.Combine("SQLite", subPath, "System.Data.SQLite.dll");
      16. var path = Path.Combine(System.Windows.Forms.Application.StartupPath, subPath);
      17. try
      18. {
      19. result = Assembly.LoadFrom(path);
      20. }
      21. catch
      22. {
      23. path = Path.Combine(Assembly.GetExecutingAssembly().CodeBase, subPath);
      24. result = Assembly.LoadFrom(path);
      25. }
      26. return result;
      27. }
      28. return null;
      29. }

      Wie du vllt merkst prüft er sogar zwei Alternativen: Zunächst geht er davon aus dass die oben erwähnte Verzeichnisstruktur im Pfad der Anwendung zu finden ist und wenn das fehlschlägt weicht er auf den Pfad der Bibliothek aus.

      Ich weiß wohl dass du bisher "nur" auf einem 32bit-System zu Hause bist, darum hatte ich zunächst auch ein paar Probleme deine Datenbank-Beispiele auf meinem 64-Bit-Windows mit SqLite zum Laufen zu bekommen, weil du offenbar nur die 32-Bit-Version der SqLite-Assembly verwendest.
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.
      hab ich jetzt ausprobiert, habe für mein x86 die "sqlite-netFx40-static-binary-bundle-Win32-2010-1.0.89.0.zip" von dort geladen, entpackt und bla, und drauf verwiesen.
      Trotztdem in mein Sample-Projekt für SQLite failt das Abspeichern eines zugefügten Datensatzes mit: "Auf das verworfene Objekt kann nicht zugegriffen werden" :(
      Hingegen Laden von Datensätzen geht.

      Funktioniert das bei dir?
      Die Frage fällt mir schwer zu beantworten, weil ich dein Projekt mit diesem Mechanismus nicht mehr ausprobiert hatte. Ich habe mir ja - vielleicht erinnerst du dich an mein Posting dazu - eine Art eigenen ADO.NET-OR-Mapper programmiert, der komplett am DataSet vorbeigeht und nur in wenigen Ausnahmefällen (z.B. Schemas) mit DataTables arbeitet. Damit gibt es keinerlei Probleme bisher, d.h. egal ob ich die Anwendung, die meine Bib verwendet, mit x86 oder x64 (oder AnyCPU) kompiliere, kann ich sowohl lesend als auch schreibend auf SqLite-Datenbanken zugreifen.

      Nachtrag:
      Okay, hab jetzt nochmal dein Paket heruntergeladen, den Mechanismus eingebaut und das ganze ausprobiert. Irgendwie verhält es sich eh recht seltsam. Ich hab jetzt nicht genau gesehen was es mit den beiden Tabellen auf sich hat. Die linke scheint eine User-Tabelle zu sein. Die Rechte ist eine separate Tabelle die Verweise auf User beinhaltet?

      Habe also frisch angefangen, ohne Load oder Save, ein paar User eingetragen und dann rechts auch eine Zeile hinzugefügt. Hab dann "Save" gewählt, und dabei sind zwei - nein, drei Sachen passiert:
      - die negativen IDs wurden positiv (das wird wohl gewollt sein, die negativen sind nur temporär bis die DB sie beim Insert bestätigt)
      - Es tauchte plötzlich ein sechster User namens "eeeee" auf, den ich nicht eingetragen hatte
      - Die Zeile in der rechten Tabelle verschwand wieder.
      Ansonsten kein Fehler.
      Erst als ich versucht hatte, rechts wiederum eine Zeile hinzuzufügen und dann zu speichern, kam der Fehler den du genannt hast: "Auf das verworfene Objekt kann nicht zugegriffen werden". Offensichtlich ist das SQLiteCommand-Objekt, das mit dem Adapter verwendet wurde, in der Zwischenzeit - von dir? von der SQLite-Dll? - disposed worden.
      Weltherrschaft erlangen: 1%
      Ist dein Problem erledigt? -> Dann markiere das Thema bitte entsprechend.
      Waren Beiträge dieser Diskussion dabei hilfreich? -> Dann klick dort jeweils auf den Hilfreich-Button.
      Danke.

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

      die Funktionsweise hast du richtig verstanden, es handelt sich um den Parent-Child-View einer 1:n - Relation.
      Dass sich die IDs durchs Abspeichern ändern ist das Erfolgs-Zeichen: Das Dataset hat seine selbst-generierten provisorischen IDs ausgetauscht durch die von der DB generierten endgültigen IDs.
      Und dass beim Reload weitere Datensätze auftauchen liegt einfach daran, dass in der DB schon vorher was drinne war :D

      Nur dass bei dir dann auch der ObjectDisposed-Fehler kommt, bedeutet, dass SQLite in FW4 nicht mehr korrekt arbeitet (weil alle anneren DBS kommen damit klar, und selbst SQLite3.5 im FW3.5)

      Update mit frei zugänglicher MySql-Db

      @oShortyo: hat mir Zugangsdaten zu einer MySql-Db vermacht, und mir erlaubt, die auch zu veröffentlichen - vielen Dank! :thumbsup:

      Soll man ja eigentlich nicht machen, weil jedermann kann diese DB nun verwüsten, oder - worst case - gar illegale Inhalte dort hinterlegen und nutzen.

      Also ich hab nichts dagegen, wenn jmd die DB versuchsweise verändert, etwa mittm DbGenerator. Falls er sie nicht wiederherstellen kann, sodass mein Sample hier wieder läuft - ist auch nicht soo schlimm.
      Wäre halt nett, mich dann davon zu unterrichten.
      Und gebt mir bitte auch bescheid, wenn ihr die MySql-Db kaputt vorfindet - es sind nur ein paar Klickse, die wiederherzustellen.
      Ansonsten guck ich eh immer wieder mal rein, und wenn mir nicht gefällt, was ich vorfinde, bügel ich da einfach mal drüber.

      :D
      bingo!
      ich gehe mal mit bester Hoffnung an die Sache heran und denke dass hier 90% der User vernünftig denken und handeln...
      da ich tägliche Backups meiner DBs mache, wird -falls jemand Bubu baut- binnen weniger Stunden die sache gefixt und die DB ist wieder auf dem soll-stand.

      AdvancedPainting

      grade ein umfangreiches Update geuppt - in post#1 als "AllTogether2010" downzuloaden.

      Zwischenzeitlich existiert oShortyo's MySqlDb leider nicht mehr im INet, also das MySql-Sample geht nicht mehr.

      Jo, geändert hab ich jede Menge, aber kaum an den Db-Extensions, sondern viel mehr an den Winforms-Extensions. Die enthalten ja viel wiederverwendbaren Code, der die Funktionalität von BindingSources, Datasets, DataTables doch sehr erweitert.
      In kleinen Übungs-Projekten kommt das kaum zum Tragen, daher hab ich ein großes Übungs-Projekt gecodet und mit reingepackt: "AdvancedPainting".
      Darin auch ein Readme, was einiges erläutert, u.a. eine Empfehlung, in welcher Reihenfolge für Interessierte vlt. günstig ist, sich das Verständnis zu erschließen. Die Dateien haben immer einen File-Header, der Konzept und Aufgabe der Klassen erklärt.
      Der Code selbst ist dann fast garnet kommentiert, denn wenn man die Sprache versteht, erklärt er sich selbst - so jedenfalls der Plan.

      Jdfs. AdvancedPainting ist ein Zeichenprogramm mit OwnerDrawing, und die Zeichen-Objekte sind typisierte DataRows, die mittels partialer Klassen um Grafik-Funktionalität erweitert sind. So habe ich ein ganz normales Datenmodell im typisierten Dataset mit Laden und Speichern, dem ganzen CRUD-Kram, und könnte auch eine Db hinterlegen, blabla... alles gut.
      Und ausserdem können sich die DataRows auf ein Control zeichnen, und da kann man sie herum-draggen, und haben Zieh-Punkte ("Grip" habich das genannt), und auch ein Kontextmenu zum Zufügen und Löschen einzelner Grips.
      Beigeordnet sind normale DatagridViews, und das nette ist die Synchronizität, also wenn ich einen Grip verschiebe, dann sausen im DGV die X/Y - Werte auf und ab :)
      Also mal Datenmodell:


      Man sieht: Es gibt Layer mit Figuren, und Figuren mit Grips.
      Kommt am Ende so raus:

      (Ups - bischen abgeschnitten) Jdfs. man sieht den Layer1, darauf ist Figure2 selected, und von Figure2 der Grip mit Index 3.
      Also selectete Figuren werden mit dickerem Strich gezeichnet, und unselectete Grips sogar gar nicht.
      Auch sieht man unter der Maus eine "gehoverte" Figur, also die ist umrandet, um zu kennzeichnen, dass sie reagieren wird, wenn man jetzt klickst.

      Jo, das ist auch schon alles, ich finde grad genügend, um aufzuzeigen, dass mit dieser Vorgehensweise die Anforderungen einer Grafik-Anwendung handhabbar sind.

      Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „ErfinderDesRades“ ()

      Welche Dataset-Erweiterungs-Features demonstriert nun das "AdvancedPainting"-Projekt?

      Suspendierungs-System
      Hätte man keine Möglichkeit, Databinding zu suspendieren, so erhielte man bei Massen-Operationen eine sehr schlechte Performance oder gar falsche Ergebnisse. Etwa in der Partialen Figure-Klasse habe ich Business-Logik angelegt, die dem User beim Zufügen einer Figur immer gleich 3 Grips hinzu-generiert, denn ohne Grips wäre die neue Figur ja unsichtbar.
      Solch macht man im DataTable_RowChanged-Event:

      VB.NET-Quellcode

      1. Private Sub _Active_FigureRowChanged(sender As Object, e As FigureRowChangeEvent) Handles _Active.FigureRowChanged
      2. Select Case e.Action
      3. Case DataRowAction.Add ' 3 Standard-Grips rein-generieren
      4. With Dts.Grip
      5. .AddGripRow(0, 0, e.Row, 0)
      6. .AddGripRow(50, 0, e.Row, 1)
      7. .AddGripRow(25, 36, e.Row, 2)
      8. End With
      9. Case DataRowAction.Change
      10. Case Else : Return
      11. End Select
      12. e.Row.ApplyChanges() ' Grafik updaten
      13. End Sub
      Aber ist ja auch klar, dass während des Ladens dieser Eventhandler abgehängt sein muss, sonst würde ja bei jedem Ladevorgang jede Figur um 3 weitere Grips erweitert. Für solch ist mein Suspendierungs-System, und intern findet sich in meiner Dataset.Fill-Extension dann so ein Aufruf:

      VB.NET-Quellcode

      1. Protected Overrides Sub _Fill(ByVal tables As Collections.Generic.IEnumerable(Of DataTable))
      2. If Not tables.Any Then tables = _DataSet.Tables.Cast(Of DataTable)()
      3. Using _DatasetAttach.BeginSuspendAll
      4. AddFillJobs(From tb In tables Select New TableAdapter.FillJob(Adapters(tb)))
      5. End Using
      6. End Sub
      Wie man sieht, erfolgt die Suspendierung vorzugsweise im Using-Block, das ist das allerbeste Mittel, um sicherzustellen, dass die Suspendierung auch wieder aufgehoben wird (Resume).
      Aber Business-Logik muss sich auch gegenseitig suspendieren können - zB im Grip ist ist ja auch Logik:

      VB.NET-Quellcode

      1. Private Sub _Active_GripRowChanged(sender As Object, e As GripRowChangeEvent) Handles _Active.GripRowChanged
      2. If e.Action.AndChangeAdd <> 0 Then e.Row.FigureRow.ApplyChanges() ' Figure-Grafik updaten
      3. End Sub
      Hier wird ebenfalls die Figure-Grafik-Aktualisierung aufgerufen (FigureRow.ApplyChanges()), wenn der User einen Grip changed oder added. Unsuspendiert würde also beim Adden einer Figur die Grafik 4 mal geupdatet, und das ist jedesmal ein recht aufwändiger Prozess.
      Also nochmal die Figure-Logik, und nun richtig:

      VB.NET-Quellcode

      1. Private Sub _Active_FigureRowChanged(sender As Object, e As FigureRowChangeEvent) Handles _Active.FigureRowChanged
      2. Select Case e.Action
      3. Case DataRowAction.Add ' 3 Standard-Grips rein-generieren
      4. Using Dts.Grip.Attach.BeginSuspend(DataTableAttach.Suspend.NotifyOnly)
      5. With Dts.Grip
      6. .AddGripRow(0, 0, e.Row, 0)
      7. .AddGripRow(50, 0, e.Row, 1)
      8. .AddGripRow(25, 36, e.Row, 2)
      9. End With
      10. End Using
      11. Case DataRowAction.Change
      12. Case Else : Return
      13. End Select
      14. e.Row.ApplyChanges() ' Grafik updaten
      15. End Sub
      Man sieht: anders als beim .BeginSuspendAll zuvor im _Fill wird hier nur die einzelne Grip-Tabelle suspendiert, und unter Angabe der Suspend-Stärke NotifyOnly. Dieses belässt die BindingSources aktiv, denn eine BindingSource zu resumen bedeutet, all ihre Datensätze neu zu lesen, und das flackert im DGV :thumbdown: .

      Und noch ein Problem: Suspendierung muss schachtelbar sein.
      Angenommen nach einem Lade-Vorgang will man noch ein paar Standard-Datensätze hinzugenerieren. Dann suspendiert man also, lädt, generiert die Extras, und resumet die Logik.
      Problem ist, dass die Lade-Funktion ja bereits intern suspendiert, und wenn das innere Resume die Logik wirklich wieder einschaltete, dann würde das Extra-Generieren doch unsuspendiert ablaufen. Daher funktioniert das Suspendierungs-System schachtelbar, und bei innerer Suspendierung wird nur ein Zähler incrementiert für die Schachtel-Logik.
      Sodass man erfolgreich so coden kann (wennmanwill):

      VB.NET-Quellcode

      1. Using Dts.Attach.BeginSuspendAll
      2. Dts.Fill()
      3. Dts.GenerateStandardData()
      4. End Using
      und die Standard-Datensätze entstehen auch unter dem Schutz der Suspendierung :D .

      Und noch ein': SuspendOnDeleteCascade
      Löschweitergabe im typDataset bewirkt ja die automatische Mit-Löschung aller ChildRows - weil die würden ja ungültig werden ohne ihre ParentRow. Das kann zu erheblichen Lagging führen, wenn man in einem hierarchischem View eine oder mehrere ParentRows löscht.
      Weil dann die BindingSources der Child- und Child-Child-Tables gewaltig anfangen, rumzuklappern, und bei jeder einzelnen Löschung die angebundenen DGVs wieder neu aufbauen.
      Im DatasetAdapter eingebaut ist daher, dass die RowDelete-Events aller Tables verarbeitet werden, sodass während Löschung der Row einer ParentTable die BindingSources aller Child- und Child-Child-Tables suspendieren.

      CurrentTracking
      Ich hab nicht nur den Dataset-Typ durch eine Attach-Klasse erweitert, sondern auch für typisierte Datatable-Klassen eine Attach-Klasse gebastelt. So attache ich zB eine ReadWrite Current-Property, sodass man eine Row als die CurrentRow der DataTable festlegen kann.
      Dazu gibts auch ein CurrentChanged-Event, und eine BindingSource.RegisterCurrency()-Methode, mit der man BindingSources dazu bringen kann, sich immer mit dieser Current-Property synchron zu halten.
      Und im Canvas habe ich ein _GripSelect-Dingens, um das grafische Selektieren zu handeln, und dessen Item-Property binde ich auch ans Current der Grip-Tabelle. Dadurch hängen nun beide am selben Current, und synchronisieren automatisch immer ihre Selection.
      Also im Form registriere ich gleich 3 BindingSources auf einmal (es geht ja nicht nur um die Grips):

      VB.NET-Quellcode

      1. Dts.RegisterCurrency(bsLayer, bsLayerFigure, bsLayerFigureGrip)
      Und im Dataset sieht die Initialisierung so aus:

      VB.NET-Quellcode

      1. Private Sub Dts_Initialized(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Initialized
      2. '...
      3. _FigureSelect.Property("Item").Synchronisize(Figure.Attach, "Current")
      4. _GripSelect.Property("Item").Synchronisize(Grip.Attach, "Current")
      5. End Sub


      ParentCurrentTracking
      Man kann bei der Dataset-Initialisierung auch einrichten, dass wenn die Row einer DataTable als Current gesetzt wird, dass dann ihre ParentRow in der ParentTable auch auf Current gesetzt wird:

      VB.NET-Quellcode

      1. Grip.SetParentNotifications(Figure, Layer)
      Dieses bestimmt sogar 2-stufig, sodass bei Current-Setzung eines Grips, sowohl die Figure-Parent-Row als auch die Layer-Parent-Parent-Row current gesetzt werden.
      NutzEffekt ist, wenn der User von einer Figur im Canvas auf den Grip einer anderen Figur wechselt, dass dann logischerweise nicht nur der neue Grip selected ist, sondern auch die ganze Figur.

      Links sieht man (ohne ParentCurrentTracking) einen Grip in Figure1 selected, aber die selectete Figur ist eine andere - nämlich Figure3 8|
      Rechts dagegen die konsistente Selektion (Grip + Figure):

      . . . . .

      Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „ErfinderDesRades“ ()

      weitere Erweiterungen

      Auch fürs DatagridView habe ich eine Attach-Klasse programmiert, die kann bewirken, dass in sortierten Spalten gleiche Werte als gemeinsame Gruppe hervorgehoben werden. Kombiniert werden kann das mit einer BindingSource-Erweiterung, die bei Änderung der Sort-Property die vorherige Sortierung nicht verwirft, sondern als nachrangige Sortier-Spalte weiterverwendet. So erhält man 2-stufige Sortierungen, und die gruppierte Ansicht derlei aufbereiteter Daten kann ganz interessant sein.
      In AdvancedPainting etwa gibts eine 2.Tabpage mit einem DGV, was alle Grips insgesamt präsentiert, zunächstmal 2-stufig sortiert nach "Layers, nachrangig Figures" (beide descending).
      Die "Gruppen-Darstellung" sieht so aus:
      . . . . . .
      Wenn man nun einen Columnheader klickst, wird die geklickste Spalte zur neuen Haupt-Sortierung, und die vorherige wird nachrangig - so kann man etwa nach "X, Layers" sortieren, und sieht so zB, welche Layer Grips mit X=0 enthalten. Die Columns sind auch verschiebbar, sodass man die gruppierten Spalten nebeneinander anordnen kann.
      Die Sortierung ist im DGV oben rechts eingeblendet, zB im mittleren Bild ist sie: X, LayerId DESC. Und man sieht dort, dass die Werte 0 und 25 in allen Layern vorkommen, aber negative Werte finden sich nur in Layer1, und zwar in Figure2.
      Und tauscht man die Sortier-Spalten (rechtes Bildle), sodass LayerId vorrangig sortiert wird, dann erkennt man, dass Layer1 sehr viele und sehr unterschiedliche X-Werte aufweist, während Layer2 nur 3 Werte kennt, die jeweils doppelt oder 3-fach auftreten, nämlich die Werte: 0, 25, 50.

      Also wenn man Daten zu sichten hat, (etwa LogFiles, Export-Dateien), ist so ein Blick manchmal hilfreich, um Zusammenhänge zu verstehen.
      Der UserCode davon geht in eine Zeile:

      VB.NET-Quellcode

      1. dgvAllGrips.Attach.HierarchicalSort.DrawGroupLines()
      Attach ist die erwähnte DGV-Extension, HierarchicalSort bestimmt die 2-stufige Sortierung, und DrawGroupLines() verbirgt sich wiederholende Werte und malt die roten Trennlinien zwischen die Gruppen.
      Das Verbergen sich wiederholender Werte ist für die optische Wahrnehmung als Gruppe mindestens ebenso wichtig wie das Einzeichnen der Trennlinien.

      Eine praktische Anwendung des Groupline-Features findet sich im (ebenfalls enthaltenen) DbGenerator-Projekt, allerdings nur einstufig sortiert:

      Das linke DGV listet alle DataTables und DataColumns eines typisierten Datasets in Gesamtschau. Besonders wichtig der Datentyp - Spalte FwName. Es ist nämlich das Mapping zu bestimmen, also welchen Datenbank-Datentyp die Datenbank für welche DataColumn des Datasets nehmen soll. Wie man sieht, hält Access zB für den Framework-Datentyp System.String 3 verschiedene Optionen bereit.
      Also sowas kann man mittm Groupline-Features sehr anschaulich präsentieren - sowohl (links) alle DataTables mit allen DataColumns, als auch (rechts) alle Framework-Datentypen mit zugehörigen Mapping-Optionen.

      Zur Laufzeit hat diese Ansicht aber auch noch ein weiteres Feature, nämlich "RelatedBindingsourceBinding". Die Zeile:

      VB.NET-Quellcode

      1. srcColumnMapping.BindRelatedSource(UclProviderMappings.srcMappings)
      verbindet die beiden BindingSources dergestalt, dass die übergeordnete BS (rechtes DGV) immer auf den Datensatz hopft, auf den die untergeordnete Anwahl (links) verweist. Und in Gegenrichtung bewirkt diese Bindung, dass der Verweis des untergeordneten Datensatzes nachgeführt wird, wenn der User den übergeordneten Datensatz wechselt.
      Heißt konkret: Der User kann rechts direkt ein anderes Mapping für die links angewählte Person.Name-DataColumn auswählen.

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

      Hallo ErfinderDesRades,

      nachdem ich nun tagelang die ganzen Threads zum Thema Dataset gelesen und die Videos in FourViews gesehen habe, dachte ich, nun kann es losgehen und wolltew die DLL's generieren. Das Projekt "AllTogether2010" geladen und geöffnet, doch da lässt sich so einfach nichts generieren.

      GeneralHelpers.dll klappt, die DLL kopiere ich dann in das Verzeichnis von WinFormHelpers.
      Doch das Projekt "WinFormHelpers" kann ich nicht übersetzen:
      "Fehler BC30521 Fehler bei der Überladungsauflösung, da keine zugreifbare "ToDictionary" für diese Argumente am spezifischsten ist:
      Erweiterungsmethode "Public Function ToDictionary(Of DataTable)(keySelector As Func(Of BindingSource, DataTable)) As Dictionary(Of DataTable, BindingSource)" in "Enumerable" definiert: Nicht spezifisch genug.
      Erweiterungsmethode "Public Function ToDictionary(Of DataTable)(keySelector As Func(Of BindingSource, DataTable)) As Dictionary(Of DataTable, BindingSource)" in "CollectionX" definiert: Nicht spezifisch genug. WinFormHelpers o:\Visual Studio\AllTogether\WinFormHelpers\System.Windows.Forms\BindingSourceBinder.vb 45 Aktiv"

      in

      VB.NET-Quellcode

      1. Public Sub New(first As BindingSource, second As BindingSource)
      2. Dim dic = {first, second}.[b]ToDictionary[/b](Function(src) src.DataTable)
      3. Dim rl = dic.First.Key.DataSet.Relations.Cast(Of DataRelation).FirstOrDefault( _
      4. Function(r) dic.ContainsKey(r.ChildTable) AndAlso dic.ContainsKey(r.ParentTable))
      5. If rl.Null Then Throw Me.Exception("there is no DataRelation between ", first.DataTable.TableName, " and ", second.DataTable.TableName)
      6. Me.bsParent = dic(rl.ParentTable)
      7. Me.bsChild = dic(rl.ChildTable)
      8. Me.rl = rl
      9. AddHandler bsChild.CurrentChanged, AddressOf bsChild_CurrentChanged
      10. bsChild_CurrentChanged(second, Nothing)
      11. End Sub


      Lösung:
      Offenbar ist im aktuellen Framework da eine System.Enumerable.ToDictionary()-Extension implementiert mit derselben Signatur, wie die von EDRs System.Collections.Generic.ToDictionary()-Extension. Also müssen wir den Teil auskommentieren.
      Suche Solution-weit nach der Methode "Public Function ToDictionary(Of TItem, TKey)(items As IEnumerable(Of TItem), keySelector As Func(Of TItem, TKey)) As Dictionary(Of TKey, TItem)", wird gefunden in CollectionX.vb.

      Danke ErfinderDesRades für den Hinweis und die Hilfe!

      Viele Grüße,
      Oliver

      *Posting verschoben*

      Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „OliverSte“ ()

      OliverSte schrieb:


      Lösung:
      Offenbar ist im aktuellen Framework da eine System.Enumerable.ToDictionary()-Extension implementiert mit derselben Signatur, wie die von EDRs System.Collections.Generic.ToDictionary()-Extension. Also müssen wir den Teil auskommentieren.
      Suche Solution-weit nach der Methode "Public Function ToDictionary(", wird gefunden in CollectionX.vb und SetList.vb.

      Danke ErfinderDesRades für den Hinweis und die Hilfe!

      Viele Grüße,
      Oliver

      *Posting verschoben*


      Was meinst du mit Auskommentieren? Mir ist schon klar was es bedeutet, aber ich kommentier so ungerne Code aus ohne zu wissen warum und wiso. Ich mein, die FunKtionen in den beiden Dateien auskommentieren verhindert die Fehlermeldung, aber EDR hat den Code ja sicher nicht einfach nur reingeschrieben weil er dache "Da ist noch Platz, da kann noch was rein".

      Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „DianonForce“ ()

      @DianonForce
      Nun, den Tipp gab mir der ErfinderDesRades höchst selbst, denn er vermutete, was ich unter Lösung schrieb.
      Sicherlich hast du Recht und man müsste den Code im Framework 4.6.2 suchen und mit der Extension von EDR vergleichen, ob sie wirklich dasselbe machen. Mach doch mal ;)
      Andererseits, wenn es sich nur um leicht unterschiedliche Implementierungen handelt, ist doch das Ergebnis dasselbe. Und damit kann man eine löschen.