Laden...

Dataset-Adapter

Erstellt von ErfinderDesRades vor 15 Jahren Letzter Beitrag vor 15 Jahren 4.257 Views
ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 15 Jahren
Dataset-Adapter

Irgendwie ist ADO.Net konzeptionell ziemlich vermurkst, scheint mir.
Die Grundidee ist eigentlich wunderbar: Man liest einen Ausschnitt der Datenbank in ein Dataset ein, bearbeitet es unabhängig von der DB, und speichert irgendwann die Änderungen zurück.
So hat man minimalen Datenbank-Traffic und höchste Verbindungs-Sicherheit - die Connection wird ja nur für Sekundenbruchteile beansprucht, und selbst beim Fehlschlag bleiben die Daten immerhin im Speicher erhalten.
Auch die Designer-Unterstützung - man kann im VS eine Datenbank öffnen, und sich die Tabellen aussuchen, die man in seinem Dataset haben will, inklusive der DataRelations. Und das typisierte Dataset stellt die Datensätze als Datarows zur Verfügung, und jede Spalte ist in einer extra und korrekt typisierten Property gewrappert - ohne eine Zeile Code selbst zu schreiben, schon gar kein SQL!

Aber mit dem Abspeichern - da war wohl Weihnachten, alle in Urlaub, und hamse halt den Ochsen im Stall gefragt...

Jetzt generierense für jede Tabelle einen genau passenden TableAdapter, der hat eine Methode .Update(), damit kanner alle Änderungen dieser Tabelle zurückspeichern.
Nur, daß das gar keinen Sinn macht, eine Tabelle abzuspeichern, jedenfalls nicht, wenn Relationen bestehen. (Und Relationen sind halt der Witz anne Datenbänkerei, sonst kann man auch eine List<T> nehmen.)
Durch Relationen über- und unter-geordnete Tabellen müssen im Zusammenhang abgespeichert werden, da gilt es, ein paar Regeln zu beachten, sonst meckert die Datenbank.
Neu hinzugefügte Datensätze sind Top-Down abzuspeichern, damit neu hinzugefügte untergeordnete Datensätze in der Datenbank gleich ihren Parent-Datensatz vorfinden, sonst wirft die DB mit einer Exception.
Dagegen Löschungen müssen Bottom-Up durchgeführt werden - wenn man in der Datenbank einen übergeordneten Datensatz löscht, würde man seinen untergeordneten ja den Parent wegnehmen - bei sowas passt die DB auf wie Luchs!
Oder unter Ausnutzung der Löschweitergabe löschen, das ist noch performanter, geht aber nochmal anders.

Also welches Schlaule kam auf die Idee, Löschungen und Hinzufügungen in einem Befehl, DataAdapter.Update() zusammenzufassen? (Ja, war Weihnachten, ...)
Aber geht noch weiter, mit einem Workaround kann man ja die zu löschenden Datarows ausfiltern und gesondert behandeln.
Insgesamt wünscht man sich doch ein Objekt, welches die verschiedenen Adapter für die verschiedenen Tabellen zusammenfasst, und Laden und Speichern des gesamten Datasets autark erledigt. Die erforderlichen Informationen sind ja vorhanden, und die Vorgänge sind ja bei jedem Dataset prinzipiell dieselben: Anhand der Relationen kann man eine Top-Down-Reihenfolge erstellen, und die Löschungen kann man getrennt behandeln.
Theoretisch geht das sogar Datenbank-unabhängig, im Namespace System.Data.Common sind abstrakte DataAdapter, Commands, Connections definiert, die Basis aller Datenzugriffs-Klassen aller gängigen Datenbank-Systeme sind. Theoretisch kein Problem, einer Daten-Zugriffs-Schicht, die sauber mit diesen Klassen programmiert ist, quasi unterm Hintern einen anderen DB-Provider unterzujubeln - das nennt man auch den Strategy-Pattern, soweit ich weiß.

Aber dieses höhere Wesen im Stall... ... hatte was dagegen.

Die generierten TableAdapter beerben nämlich nicht die phänomenal praktischen Klassen aus System.Data.Common - nein, sie wrappern sie. Und zwar wasserdicht, mit normalen Mitteln ist an die enthaltenen DataAdapter nicht heranzukommen.
Die wesentlichen Member der generierten TableAdapter - Update() und Fill() werden jedesmal taufrisch generiert, es gibt keine gemeinsame Basis, kein Interface, nix, womit man mehrere TableAdapter in einer Schleife irgendwie gemeinsam nutzen könnte.
Obwohl alle generierten TableAdapter absolut dasselbe machen, sind die Methoden, mit denen sie's machen (bei gleicher Signatur und gleicher Benamung), aus Sicht des Compilers vollkommen verschieden.

Naja, wir haben ja noch Reflection...

Also habe ich hier mal einen Dataset-Adapter gebastelt, mit Reflection. Da tut man ein typisiertes Dataset hinein und die erforderlichen generierten TableAdapter, und der lädt und speichert das Dataset dann, wie mans ihm sagt. Man kann ihm sogar noch BindingSources mitteilen, dann überwacht er deren _List_Changed, und lädt nur die Datensätze, die angefordert werden (inkrementelle Befüllung).

Ist noch ein bischen mehr drin. Zum Beispiel störte es mich, daß jeder TableAdapter seine eigene Connection hält. Wenn nämlich 4 Tabellen zu updaten sind, wird 4 mal eine Verbindung zur DB aufgebaut und wieder geschlossen - das habich abgestellt. Auch ist nirgends zu finden, wo die Connections, DataAdapter, Commands etc, wo das wieder disposed wird, auch da habich dran gebastelt, aber nicht ganz vollständig (die generierten sind mir z.T. zu verbaut, und ich bin das ja auch nicht gewesen 😉).

Dann habich noch was gebaut, daß man "Rahmenhandlungen", wie Öffnen und Schließen, Datenbindung unterbrechen und wiederherstellen, daß man so Sachen mit einem Using-Block ganz sicher sicherstellen kann, auch bei verschachtelten Aufrufen, aber dazu mache ich mal einen Extra-Upload.

Hier mal, wie Laden und Speichern aussehen kann, prinzipiell für alle Datasets gleichartig, (Das Generieren der TableAdapter funktioniert aber nur mit Access und SQL-Server).


using System;
using System.Windows.Forms;
using DatasetAdapterDll;

namespace DatasetAdapterTest {

   public partial class frmDatasetAdapterTest : Form {

      private DatasetAdapter _DatasetAdapter;

      public frmDatasetAdapterTest() {
         InitializeComponent();
         _DatasetAdapter = new DatasetAdapter(bestellungDataSet);
         _DatasetAdapter.AddAdapter(kategorieTableAdapter);
         _DatasetAdapter.AddAdapter(artikelTableAdapter);
         _DatasetAdapter.AddBindingSource(artikelBindingSource);  // "Artikel" inkrementell befüllen
         reLoadToolStripMenuItem.Click += (sender, e) => Reload();
         saveToolStripMenuItem.Click += (sender, e) => Save();
         Reload();
      }
      private void Reload() {
         _DatasetAdapter.Load();
      }
      private void Save() {
         FormHelpers.EndAllEdits(this);
         _DatasetAdapter.Save();
         System.Media.SystemSounds.Asterisk.Play();
      }

      /// <summary>
      /// Findich schlecht, daß VS die Ressourcen-Bereinigung des Forms im DesignerCode versteckt. 
      /// Habich dort rausgeholt. Auch das _Disposed-Event ist keine Option,
      /// weil, hat man keine Kontrolle über die Reihenfolge der Freisetzungen
      /// </summary>
      protected override void Dispose(bool disposing) {
         Helpers.DisposeManaged(disposing, ref components, bestellungDataSet, _DatasetAdapter);
         base.Dispose(disposing);
      }

   }
}

Das Teil hat allerdings die Einschränkung, daß auf client-seitige Primärschlüssel-Generierung gebaut wird.
Prinzipiell ließe sich wohl eine Unterstützung für server-seitige Primärschlüssel-Generierung dranbasteln, aber ich weiß grad kein schlagendes Argument gegen client-seitige Primärschlüssel-Generierung, außer, dasses einfacher ist, und etwas weniger Traffic beim Speichern von INSERTs verursacht, da ja nicht für jeden neuen Datensatz der neue Schlüsselwert rückgemeldet werden muß.

Der Upload ist nur deshalb so fett, weil auch eine Access-Mdb beiliegt.

Schlagwörter: dataset, dataadapter, adapter, tableAdapter, command, update, fill, connection

Der frühe Apfel fängt den Wurm.

1.985 Beiträge seit 2004
vor 15 Jahren

Hallo ErfinderDesRades,

ich bin ehrlich, ich habe mir nicht alles durchgelesen. Ich glaube aber, dafür wurde im .NET Framework 3.5 der TableAdapterManager eingeführt, der diese Probleme adressiert. Die MSDN sagt dazu:

Der TableAdapterManager ist eine neue Komponente in Visual Studio 2008, die auf vorhandenen Datenfeatures aufbaut (typisierte DataSets und TableAdapter) und Funktionen zum Speichern von Daten in verknüpften Datentabellen bereitstellt. In TableAdapterManager werden Fremdschlüsselbeziehungen verwendet, die Datentabellen verknüpfen, um so die richtige Reihenfolge beim Senden von Einfüge-, Aktualisierungs- und Löschvorgängen vom DataSet zur Datenbank festzulegen, ohne dabei die Fremdschlüsseleinschränkungen in der Datenbank zu verletzen (referenzielle Integrität).

Link zum MSDN-Eintrag: http://msdn.microsoft.com/de-de/library/bb384426.aspx

Kennst Du diese Klasse oder bin ich jetzt auf dem völlig falschen Dampfer?

Gruß,
Fabian

"Eine wirklich gute Idee erkennt man daran, dass ihre Verwirklichung von vornherein ausgeschlossen erscheint." (Albert Einstein)

Gefangen im magischen Viereck zwischen studieren, schreiben, lehren und Ideen umsetzen…

Blog: www.fabiandeitelhoff.de

ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 15 Jahren

Doch, TableAdapterManager kennich schon ein bischen. Habich aber gleich wieder rausgeschmissen, nachm ersten Fehler.
jetzt habichs gründlicher probiert, und folgendes gefunden:*Bei 5 Tabellen isses schon so viel Code, wie mein Teil insgesamt - das ist aber andererseits nur ca. 1/10 dessen, was das Dataset sonst so an generiertem Code enthält *Befüllen kanner gar nicht, nur updaten *Beim updaten isser genau wie meiner auf clientseitige PrimKey-Generierung angewiesen.
Nur richtet er die clientseitige PrimKey-Generierung nicht automatisch ein, und händisch ist das bischen tricky, weil im Designer geht das auch nicht (so jedenfalls mein Stand von VS2005).

*Der update-Algo ist wesentlich umständlicher: alle zu updatenden Datarows werden selectiert (getrennt nach INSERT, UPDATE, DELETE), herumkopiert und einzeln accepted. DELETEs werden Bottom-Up deleted, also ohne Ausnutzung der Löschweitergabe der DB.
Bei meinem Algo werden nur die DELETEs extra behandelt, aber Top-Down, unter Ausnutzung der Löschweitergabe. Accepten geht eh automatisch.

*Befüllen nach dem Prinzip "Nimm alles" ist ja relativ einfach, das kann man noch verschmerzen. Aber inkrementelle Befüllung - da hälter sich leider auch vornehm zurück.

Edit:
Jetzt habich auch das mit dem Autowert überprüft - mein Stand von VS2005 ist nicht aktuell: Die INSERT-SQL-Generierung bei im Designer eingestellten AutoIncrement-Columns scheint geändert worden zu sein, sodaß die generierten TableAdapter keine Abstürze mehr produzieren.

Das macht den generierten TableAdapterManager insgesamt immerhin benutzbar (wenner auch weiterhin zu fett ist, und rel. unperformant updatet).

Also mussich meinen Vorsprung beim (inkrementellem) Befüllen und beim automatischen Einrichten des Datasets ausbauen 😉

Der frühe Apfel fängt den Wurm.