Verständnisfrage zu DataSets und DB-Befüllung

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 7 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Verständnisfrage zu DataSets und DB-Befüllung

    Hallo zusammen!

    Erst einmal ganz großen Dank and "ErfinderDesRades" für sein zwar altes aber tolles Tutorial DataSetOnly. Ich habe damit schon ein paar Projekte umsetzen können und war in der Lage relationale Datenanwenungen zu schreiben, die auch ohne größere Probleme sauber funktioniert haben.
    Nun möchte ich allerdings eine neue Anwendung schreiben und die Daten diesmal aber in einer SQLite Datenbank ablegen. Die Sache mit den DataTableAdaptern etc. habe ich bereits verstanden und kann auch Daten abrufen, in einem DataGrid darstellen und bearbeiten.

    Ich habe aber ein Verständnisproblem mit den folgenden 2 Punkten und würde mich freuen, wenn mir da jemand auf die Sprünge helfen könnte:

    1. Muss ich tatsächlich für übergeordnete Tabellen die komplette SQLite Tabelle ins DataSet laden?
    Sprich zb. "SELECT * FROM Client"? Verfehlt das nicht den Zweck von einer DB-Engine? Also das ich nur relevante Datensätze in den Arbeitsspeicher lade?
    Ich habe zb. eine "Entry" Entität und diese kann einen "Client" als Parent haben (1:n). Aber nun könnte ich ja 100.000 "Clients" haben und nur 100 "Entries". Dann sind die 100.000 Clients im Arbeitsspeicher bisschen drüber, wenn ich ja maximal 100 als Parent zugeordnet haben könnte. Nicht, dass ich das um jeden Preis vermeiden will. Wenn das kein Problem ist, lass ichs gerne so. Frage mich nur, ob das im Sinne des Erfinders ist. Dann kann ich ja gleich mit DataSetOnly weiter machen und einfach als XML laden und speichern!?

    2. Ein globales DataSet für die Anwendung oder ein DataSet pro "View"?
    Ich habe bei meinen DataSetOnly Anwendungen immer mit einem DataSet für die komplette Anwendung gearbeitet (sprich in einem applikationsweiten Singleton). Hat super funktioniert. Macht es aber jetzt Sinn, wenn man mit einzelnen Queries zu einer Datenbank arbeitet, mit einem DataSets pro "View" zu arbeiten? Also wenn ich zb. eine Entität bearbeiten will zb. nur den Primary Key von einem DataGrid an ein Fenster zu übergeben und dann in diesem Fenster einen Query nur für diese ID durchzuführen(also auch alle Abhängigkeiten etc.) mit eigenem DataSet?


    Ich danke euch für eure Anregungen und Gedanken! :)
    Hmm, ich hab noch mehr Tuts gemacht, ich hoffe, vier Views-Videos kennste?

    naja, du musst natürlich nur die Daten laden, die du brauchst.
    Also von den Clients nur diejenigen, auf die deine Entries per FK verweisen.
    Das ist allerdings in SQL nur umständich zu formulieren, insbes. wenn noch weitere Bedingungen einfliessen sollen.
    Und wenn du gar deinen Entries zur Auswahl stellen willst, welcher Client ihr Parent sein soll ("Joining-View"), dann brauchste doch wieder alle Clients.

    Und inne Praxis ist das extrem unwahrscheinlich, dass eine übergeordnete Tabelle umfangreicher ist als die untergeordnete.

    Braucht halt Augenmass. Wo's möglich ist, ist eine komplette Tabelle zu laden meist einfacher und performanter.
    Also zB die Mitarbeiter eines mittelständischen Betriebs kann man wohl komplett einlesen.
    Mit den Kunden wird das schon fraglich. Bestellungen noch schwieriger, und Bestellposten... - naja.
    Vielleicht lieber nur die offenen Bestellungen. Oder die der letzten 3 Monate.

    Ich bin für globales Dataset.
    Fast immer hängen alle Daten einer Anwendung iwie miteinander zusammen - da wärs keine gute Idee, das in mehrere Datenmodelle zu zerrupfen.

    Man muss aber auch iwie gucken, dass man Daten auch wieder los wird. Sonst wenn eine Anwendung lange läuft, und man überall mal so rumgeguckt hat, dann hat die iwann auch mal den gesamten Datenbestand geladen.
    Aber ich hab deswegen nicht mit mehreren Datasetsen anfangen müssen. Eine DataTable hat ja auch den .Clear()-Befehl.

    Bei EF reden die Leuts gerne von "Unit of Work", also dass ein Datacontext für eine begrenzte Aufgabe gehalten wird, und dann wieder verworfen.

    Jo, wenn das toll ist, einem Fenster eine ID zu übergeben, und es holt sich dann Daten selber, hält sie selber, speichert sie selber ab - ich bin da pauschal nicht von begeistert, aber kann sein, dasses nützlich ist.
    Auf Arbeit habich halt mit einer Anwendung zu tun, da werden auch immer nur IDs rumgereicht, und derselbe Datensatz wird zig oder hundertmal abgerufen, - also kann ich nicht empfehlen.
    Vielen Dank für Deine ausführliche Antwort!

    Dann ist Punkt 2 für mich schon mal abgehakt: es bleibt beim globalen DataSet. Fühlt sich auch angenehmer und übersichtlicher an.

    Zu Punkt 1.:
    Wie sieht denn das dann aus?

    Child-Tabelle:

    C-Quellcode

    1. "SELECT * FROM AccountEntry WHERE fkBusiness = @fkBusiness AND dateBooked >= @startDate AND dateBooked <= @endDate";

    Parent-Tabelle:

    C-Quellcode

    1. "SELECT * FROM Client WHERE id IN (SELECT fkClient FROM AccountEntry WHERE fkBusiness = @fkBusiness AND dateBooked >= @startDate AND dateBooked <= @endDate AND fkClient NOT NULL)";


    So? Irgendwie umständlich und schwieriger in den Programmablauf zu bringen (mit Blick auf verallgemeinernerde (Wrapper)Helfer-Klassen zb.). Vorallem wenn ich auf die ClientTabelle auch anderweitig zugreifen will und eine andere Ergebnismenge benötige, oder?


    Folgendes konkretes Szenario:

    Das System ist AccountEntry-Zentrisch. Dh. der Dreh- und Angelpunkt ist die Tabelle "AccountEntry".
    Wenn die Applikation startet, werden die "AccountEntries" folgendermaßen gefilter von SQLite abgefragt und vom Hauptfenster in einem DataGrid dargestellt:

    C-Quellcode

    1. "SELECT * FROM AccountEntry WHERE fkBusiness = @fkBusiness AND dateBooked >= @startDate AND dateBooked <= @endDate";


    Das heißt: die größte Tabelle wird schon vorgefiltert. Funktioniert prächtig. Ich lade zu diesem Zeitpunkt keine Clients in den DataTable. Scheint ein Problem zu sein, weil der Debugger hier schon meckert zwecks Constraint-Verletzungen. Klar, wenn ich Clients zugeordnet hab, existiert dieser ja nicht in der übergeordneten Tabelle. Programm läuft aber.

    Wenn ich jetzt einen AccountEntry bearbeiten will, wird ein neues Fenster geöffnet und dieses soll mir tatsächlich die Möglichkeit bieten, einen Client für diesen AccountEntry auszuwählen. Das geht logischerweise nicht mehr in einer ComboBox, weil die Kundenliste zu umfangreich ist.
    Ich hatte mir überlegt, hier einfach ein Auswahlfenster mit Suchmaske zu machen, bei dem ich dann einen Query zur Datenbank schicke, der die Kunden nach verschiedenen Kriterien filtert, sprich "Firma", "Name", "Kundennummer" etc. und dann erst in das DataSet läd. Dann kann ich einen Client von dieser vorgefilterten Liste auswählen. Diese ClientRow wird dann dem AccountEntryRow.ClientRow = ClientRow zugeordnet. Dann ist, wenn ich das globale DataSet verwende, der DataTable der Clients aber auf dem Suchstand der Auswahl! Schlecht für das Hauptfenster, weil dieses ja eine anderen SELECT für die übergeordnete Tabelle bräuchte!?

    Die einfachste Möglichkeit wäre tatsächlich einfach einen "SELECT * FROM Client" zum Programmstart bzw. an kritischen Stellen auszuführen und dann einfach auf dem DataTable bzw. DataView einen "Select" zu machen um den DataTable/DataView zu filtern. Hab ich in den DataSetOnly Projekten auch so gemacht. Damit wären alle Probleme gelöst... wenn die Clientliste überschaubar bleibt und eben nicht irgendwann 500.000 Rows hat.

    Die Daten werden erstmal nicht so riesig sein (da werden erstmal keine 500.000 Kunden rein kommen...); ich will nur die Zukunft im Blick behalten und auch für andere Bereiche und Projekte sinnvoll und zukunftssicher arbeiten.

    Oder mach ich mir da einfach viel zu viele Gedanken?

    Danke!

    Dieser Beitrag wurde bereits 6 mal editiert, zuletzt von „asuryan“ ()

    asuryan schrieb:

    Oder mach ich mir da einfach viel zu viele Gedanken?
    Nö, das sind genau die Gedanken, die ich mir auch machen täte.

    Jo, und mittm Sql scheinste ja so leidlich klarzukommen.
    Beachte, dass das ein Pattern ist, den du da gefunden hast: Die Child-Table-Query ist komplett Bestandteil der ParentTable-Query. Wenn du viel sowas am Hut hast, könnte man einen Query-Builder basteln, der quasi sone 'Include'-Funktionalität beinhaltet.

    Ah - das ist ein klein Pluspunkt für EF: Da gibts bereits diese Include-Funktionalität.

    In meine DbExtensions ist auch eine Art QueryBuilder enthalten (heisst glaub 'Joiner'). Der kann aber 'nur' komplexe InnerJoin-Sql-Segmente.
    Damit habich auch das ParentTable-Query-Problem gelöst, nämlich durch Voranstellen von Distinct (ich hoffe, du verstehst ungefähr wassich meine).

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

    Vielen Dank!
    Ich steige jetzt schon etwa mehr durch, wie man mit DataSets und eine direkten Datenbank Anbindung denken muss. :) Habe vorher nur mit Queries in PHP/HTML und MySQL gearbeitet und da gibt es das DataSet Konzept ja nicht.
    Habe mir jetzt eine kleine Helper-Funktion geschrieben, die mir den Query für eine Parent-Tabelle mit ausgibt. Ich fahre dann diesen Query einfach vor der eigentlichen Abfrage.

    C#-Quellcode

    1. public static String getParentQuery(String childWhereQuery, String childTable, String parentTableName, String parentPrimaryKey, String childForeignKey)
    2. {
    3. String query = "SELECT * FROM " + parentTableName + " WHERE " + parentPrimaryKey + " IN (SELECT " + childForeignKey + " FROM " + childTable + " WHERE " + childWhereQuery + ")";
    4. return query;
    5. }


    Danke nochmal fürs Licht ins Dunkel bringen!
    hmm, hmm - höchst anfällig, eine Methode, der man 5 Strings übergeben muss.
    Anfällig für Schreibfehler, und wenn das Datenmodell geändert wird, und auch für SqlInjection.

    Meine Include-Funktion arbeitet mit Objekten des Datasets.
    Also um das Sql zu generieren, welches 2 Tabellen verknüpft, musste eiglich nur die DataRelation des Datasets angeben - die enthält (mittelbar) alle nötigen Informationen:
    ParentColumn, und damit auch ParentTableName und ParentPrimKey, ChildColumn, und damit auch ChildTableName und FK.
    Also so eine SimpelSignatur käme bei raus:

    C#-Quellcode

    1. public static string ParentQuery(DataRelation rel, string childWhereQuery){... }
    Ich würd sogar eine DataRelation-Extension-Methode davon machen, dann wäre die Aufrufe-Syntax so:

    C#-Quellcode

    1. var parentQuery = myDataset.Relations["FK_Table1Table2"].ParentQuery("table2.ConditionColumn=5");
    oder sowas

    Man kanns noch doller treiben, wenn man einen DataRelation-Such-Algorythmus implementiert hat.
    Dann kann man typisierte DataColunms (Pk einer Table, FK der anderen) angeben, und er sucht die Relation selbst - sodass man da keinen String-Smell implementiert:

    C#-Quellcode

    1. public static string ParentQuery(DataColumn pk, DataColumn fk, string childWhereQuery){... }

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

    Danke Dir für die Anregungen!

    Ich hab das jetzt etwas umgeschrieben:

    C#-Quellcode

    1. public static String getParentQuery(DataRelation relation, String childWhereQuery)
    2. {
    3. String query = "SELECT * FROM " + relation.ParentTable.TableName + " WHERE " + relation.ParentKeyConstraint.Columns[0].ColumnName + " IN (SELECT " + relation.ChildKeyConstraint.Columns[0].ColumnName + " FROM " + relation.ChildTable.TableName + " WHERE " + childWhereQuery + ")";
    4. return query;
    5. }


    Und aufgerufen wird so:

    C#-Quellcode

    1. getParentQuery(dataSet.Relations["FK_Customer_Invoice"], whereQuery)


    Hab zwar den "String-Smell" drin... aber von 5 Strings auf 2 runter ist doch schon mal was.

    Danke!

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

    sieht gut aus.
    Nu wäre noch sinnvoll, DatenbankObjekte zu quoten.
    Es kommt immer wieder vor, dass jmd. Namen verwendet, die mit geschützten Worten im SQL kollidieren.
    Auch sollte man Spaltenbezeichner vollqualifiziert verwenden, also

    SQL-Abfrage

    1. select * from [Table1].[Column1] ...
    sollte bei rauskommen.
    Am besten wäre sogar, du könntes die childWhereQuery daraufhin untersuchen, und ggfs korrigieren (das wird aber recht kompliziert).