relationale Datenhaltung und OOP - wie zusammenführen? Aber ohne Typprüfung und ohne DataSet [provisorisch gelöst]

  • VB.NET
  • .NET (FX) 4.5–4.8

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

    relationale Datenhaltung und OOP - wie zusammenführen? Aber ohne Typprüfung und ohne DataSet [provisorisch gelöst]

    Hallo zusammen,

    ich habe ein paar hier vereinfacht dargestellte Klassen.

    VB.NET-Quellcode

    1. Class SellableItem
    2. Property ID As Integer
    3. Property Name As String
    4. End Class
    5. Class Delivery
    6. Property ID As Integer
    7. Property SellableItemID As Integer
    8. Property SupplierID As Integer
    9. End Class
    10. Class Sell
    11. Property ID As Integer
    12. Property SellableItemID As Integer
    13. Property SellDate As Date
    14. End Class
    15. Class Supplier
    16. Property ID As Integer
    17. Property Name As String
    18. End Class
    Dies ist als Vorstufe für eine Datenbankverwendung so aufgebaut, dass z.B. die Sell-Klasse nur die ID der Ware kennt, aber nicht die Warenklasseninstanz an sich, um Datenredundanz zu verhindern, wenn der Artikel nochmal verkauft wird (und zwar im Sinne von: der gleiche Artikel, nicht derselbe)
    Desweiteren habe ich in der MainForm-Klasseninstanz je eine List(Of) der o.g. Klassen.
    Ich habe weitere Klassen mit weiteren Details, aber ich habe es auf die genannten Daten zur Veranschaulichung zusammengeschmolzen.

    Wenn ich mir jetzt einen Vorgangsinfotext geben lassen will, hätte ich gerne Rückgabewerte wie:
    • Artikel X wurde am 12.12.2022 von Lieferant A geliefert.«
    • Artikel Y wurde am 13.12.2022 verkauft.«
    Ich möchte nun eine chronologische Vorgangsliste erstellen lassen, die dann natürlich gemischt sein wird. Vielleicht erst die Lieferung von zig Artikeln, dann Verkauf einzelner Artikel, Lieferung weiterer Ware, Verkauf weiterer Artikel, usw.
    Ich stehe nun vor dem Problem, dass ich zwar ein Interface IBookable erstellen und von Delivery und Sell implementieren lassen könnte, welches die Function GetProcessInfoText deklariert. Aber dann stehe ich vor dem Problem, dass ich die ID der Ware erhalten kann, jedoch nicht die Artikelbezeichnung, und eine Delivery-Instanz mir zwar eine SupplierID geben kann, nicht aber den Namen des Lieferanten. Den kennt nur die entsprechende Supplier-Klasseninstanz, und nur die alles umfassende MainForm-Klasseninstanz kann den Lieferantennamen und die Artikelbezeichung ermitteln und dann einen sinnvollen Text zusammenbauen. Allerdings müsste ich dazu wiederum bei jeden Vorgang prüfen, ob der Vorgang vom Typ Delivery ist, da ein Verkauf keine SupplierID hat, und dann mir die sehr vorgangsspezifischen Daten zusammensammeln. Und Typprüfung soll ja angeblich bei sauberer OOP eben nicht notwendig sein, soweit ich mir als ungelernter Hobbyprogrammierer die mir zur Verfügung stehende Literatur in Erinnerung rufe.

    Jetzt die Frage: Mach ich nur was falsch oder kann man einfach nur sagen: »Isso, Pech gehabt«?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ich an deiner Stelle würde eben sogar den Lieferantennamen, Artikelnamen in die Delivery und Sell Klasse packen. Auch den aktuellen Preis (falls, der mal relevant sein sollte). Einfach mit dem Hintergrund, dass man sieht, mit welchen Daten zum Zeitpunkt X gearbeitet wurde.
    Wenn du am Tag 1 Artikel 1 verkaufst, siehst du dann wie der Artikel geheißen hat und welchen Preis der hatte.

    Ist mMn. sogar zwingend notwendig, da du eventuell mal einen Verkauf "stornieren" musst - welcher an einem anderen Tag getätigt wurde.

    Wir programmieren an einer Warenwirtschaft mit Kassensystem - da ist das auch so. Die Daten sind dann auch nicht doppelt vorhanden - sondern waren zum Zeitpunkt X dann so.

    LG
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten
    Hallo,

    in meinen Anwendungen arbeite ich mit Listen von Objekten, die selbst wieder in Klassen dargestellt werden und deren Eigenschaften durchaus auch wieder Listen sein können (Leider in C#):
    Spoiler anzeigen

    C#-Quellcode

    1. public class Good
    2. {
    3. public int ID { get; set; }
    4. public string ArticleNumber { get; set; }
    5. public string ArticleName { get; set; }
    6. public string StorageLocation { get; set; }
    7. public decimal StockValue { get; set; }
    8. public decimal PurchasePrice { get; set; }
    9. public decimal RetailPrice { get; set;}
    10. public decimal AverageRetailPrice { get; set;}
    11. public Supplier MainSupplier { get; set; }
    12. public List<Delivery> Deliveries { get; set; }
    13. public List<Sale> Sales { get; set; }
    14. }
    15. public class Supplier
    16. {
    17. public int ID { get; set; }
    18. public string Name { get; set; }
    19. public string Description { get; set; }
    20. }
    21. public class Delivery
    22. {
    23. public DateTime DeliveryDate { get; set; }
    24. public Supplier Supplier { get; set; }
    25. public decimal Amount { get; set; }
    26. public decimal Discount { get; set; } = 0;
    27. public decimal Price { get; set; } //purchase
    28. }
    29. public class Sale
    30. {
    31. public DateTime SellingDate { get; set; }
    32. public decimal Amount { get; set; }
    33. public decimal Price { get; set; } //retail
    34. }

    Hab es mir grade so zusammengestümpert, hoffe es hilft.
    @fichz: Wenn jetzt aber ein Lieferant seinen Namen ändert, wie machst Du das mit Auswertungen, um zu sehen, was vor und nach der Namensänderung von dem kam? Im krassesten Fall könnte ich mir auch vorstellen: Was passiert, wenn eine Sache umbenannt wird und 3 Jahre später eine andere Sache aus der gleichen Klasse den Namen der ersten Sache bekommt? Unwahrscheinlich, aber nicht unmöglich. Wenn z.B. Hans Schubert seine Vorhängeschlösser Lockfix nennt, dann aber Großfabrik Schneider daherkommt und sagt: »neenee, den Namen haben wir uns schon patentiert«, dann benennt Hans Schubert seine Schlösser in Schließfest um und irgendwann werden auch die Lockfix von Großfabrik Schneider ins Sortiment aufgenommen. Da gäbe es ja Verkäufe von Großfabrik-Schneider-Lockfixe, die in Wirklichkeit Verkäufe von Hans-Schubert-Schließfest-Produkten sind. Und die Hans-Schubert-Schließfest-Verkäufe (ehem. Lockfix) sind gar nicht mehr ermittelbar.
    @Dksksm: Würde das nicht bedeuten, dass Du all die Lieferantendaten zigfach repliziert in den Good-Instanzen hast, über Deliveries -> Supplier?
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

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

    Nö, das Entspricht einer Relation im typisierten Dataset
    Opps, ich muss zugeben, das weiß ich nicht. Tatsächlich sind es bei mir immer Subeinträge wie Adressen oder Kontaktdaten (Telefon, Email, sonstiges). Ob es die ggf. mehrfach gibt interessiert mich nicht.
    Du könntest Recht haben.

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

    Man kann ja aufgrund der ID (SupplierID, SellableItemID) immer den aktuellsten Namen für diverse Auswertungen finden, falls dieser es halt erfordert. Aber z.B. auf Rechnungen darf sich der Name (und auch Anschrift) nicht ändern, da die Rechnung so bleiben muss wie diese ausgestellt wurde - z.B. für einen Nachdruck der Rechnung.

    In deinem Beispiel: Um zu sehen, was vor und nach der Nameänderung kam, wurden die einzelnen Datensätze, vor der Änderung, eh mit "Lockfix" (da der Lieferantenname ja mitgespeichert wurde) und nach der Änderung mit "Schließfix" gespeichert.
    Da ich aber eh immer die SupplierID des Lieferanten mitverjoinen muss finde ich dann eh nur die relevanten Daten des gewählten Lieferanten.

    Im Prinzip ist es eine Mischung:
    Du kannst immer auf den aktuellen Lieferanten aufgrund der SupplierID zugreifen und Daten lesen wie du diese benötigt. Teilweise möchte ich den aktuell gespeicherten Namen in der Tabelle Supplier lesen, teilweise wie der Namen zum Zeitpunkt des Bons/Rechnung war.

    Um beides machen zu können ist halt notwendig, dass der Lieferantenname mitgespeichert wird.

    LG
    ScheduleLib 0.0.1.0
    Kleine Lib zum Anlaufen von Code zu bestimmten Zeiten

    VaporiZed schrieb:

    Wenn ich mir jetzt einen Vorgangsinfotext geben lassen will, hätte ich gerne Rückgabewerte wie:
    Artikel X wurde am 12.12.2022 von Lieferant A geliefert.«
    Artikel Y wurde am 13.12.2022 verkauft.«
    na, dann sollte die Lieferung-Klasse eine Methode GetInfotext() haben, und die Verkauf-Klasse auch so eine Methode.
    Wie bereits in Post#1 erwähnt: Da diese Klassen nicht die Daten haben, um mir diese Informationen zu liefern, kann ich das nicht bewerkstelligen.
    Ich könnte das Problem vielleicht umgehen, indem ich allen Vorgangsklassen solch eine Funktion spendiere und pauschal alle Daten gebe (Warenliste, Lieferantenliste, später Kundenliste usw.) und sich die einzelnen Klassen dann für den Infotext die Daten raussuchen, die sie brauchen. Aber das könnte irgendwann eine sehr große Parameterliste werden. Klassenspezifisch kann ich die Funktionen aber nicht parametrisieren, da sie ja aufgrund des Interfaces polymorph aufgerufen werden sollen, also eben nicht geschaut werden soll, welche Vorgangsklasse da im Logbuch steht und ihren Infotext gerade wiedergibt.
    Mir geht es vor allem darum, ob ich bei der Verknüpfung von RDM und OOP was Grundlegendes übersehen habe.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

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

    VaporiZed schrieb:

    Da diese Klassen nicht die Daten haben, um mir diese Informationen zu liefern, kann ich das nicht bewerkstelligen.
    Willst du etwa auf "NavigationProperties" hinaus?
    Die gibts nur in generierten EF-Klassen, und ... - achnee, das ja nicht.
    Dann hab ich mir vor Zeiten mal ein Klassen-System aufgebaut, mit dem man relational modellieren konnte. hab sogar ein t4-script erstellt, was einen EF-Designer analysiert hat, und die Klassen entsprechend hingeneriert.
    Aber das kann bislang nur direkt auf Platte speichern, DB-verknüpfbar isses nicht (und dann kann man ja auch EF nehmen).
    Ist auch schon länger her, und liegt auf meim alten Rechner.
    Da konnte ich jetzt bei dem Vorpost nix an Infos für mich mitnehmen. Ich blick's leider nicht.

    Ich habe mich jetzt dazu erstmal entschieden, das folgendermaßen zu basteln - obwohl ich eigentlich auch auf Enums verzichten wollte.
    Ich nehme das "If-durch-Feldindexauswahl-ersetzen"-Vorgehen, so wie ich es bereits bei meinem Konsolenmenü genutzt habe.

    VB.NET-Quellcode

    1. '…
    2. Dim Actions As IEnumerable(Of Action(Of IBookable)) = {AddressOf ProcessDelivery, AddressOf ProcessSell}
    3. For Each Process In Processes
    4. Actions(Process.ProcessType)(Process)
    5. Next
    6. End Sub
    7. Private Sub ProcessDelivery(GeneralProcess As IBookable)
    8. Dim IDs = GeneralProcess.GetIDs
    9. ListBox1.Items.Add($"Artikel {Items.Single(Function(x) x.ID = IDs(0)).Name} wurde am {GeneralProcess.ProcessDate.ToShortDateString} von Lieferant {Suppliers.Single(Function(x) x.ID = IDs(1)).Name} geliefert.")
    10. End Sub
    11. Private Sub ProcessSell(GeneralProcess As IBookable)
    12. Dim IDs = GeneralProcess.GetIDs
    13. ListBox1.Items.Add($"Artikel {Items.Single(Function(x) x.ID = IDs(0)).Name} wurde am {GeneralProcess.ProcessDate.ToShortDateString} verkauft.")
    14. End Sub

    Die einzelnen Vorgangsklassen bekommen je einen Enum-Wert aus einem ProcessType-Enum. Dieser Wert wird mit über das IBookable-Interface zugänglich gemacht. Die Infotext-Schleife in Zeile#5 holt ihn sich für jeden Vorgang und so wird die positionsabhängige Methode aus dem Actions-Feld aufgerufen. Eine ebenfalls im IBookable-Interface deklarierte Funktion sammelt klassenspezifisch in einem Integer-Array die prozessspezifischen IDs, die MainForm-Funktionen basteln daraus den Text, da eben nur das MainForm all die Infos besorgen kann, die es braucht.

    Sollten sich also weitere anzuzeigende Vorgänge ergeben, brauch ich nur das Enum zu erweitern, das Actions-Feld zu ergänzen und eine passende MainForm-Methode zu schreiben. Die bestehenden Vorgangsklassen blieben unbehelligt.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.

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

    VaporiZed schrieb:

    Ich blick's leider nicht.
    Was blickst du nicht?
    Ists der Begriff 'NavigationProperty' ?
    Das sollteste recherchieren, das ist ein Grundkonzept von EntityFramework, und im typDataset gibts das ebenso.
    Also es ist ein Grundkonzept beim Objektorientierten Umgang mit relationalen Datenmodellen.

    Kurz gesagt: NavigationProperties kapseln das Suchen in Schlüsselspalten weg, und optimieren dabei die Performance.
    Also solche Ausdrücke:

    VB.NET-Quellcode

    1. Dim IDs = GeneralProcess.GetIDs
    2. Dim itemName = Items.Single(Function(x) x.ID = IDs(0)).Name
    wären als NavigationProperty vielleicht so ausgedrückt: Dim itemName = GeneralProcess.Item.Name, wobei Item die NavigationProperty ist.

    Aber vlt hilft dir das garnix, weil du willst da ja noch iein Interface einarbeiten, und ein Enum - sieht mir danach aus, als würde das die Fähigkeiten einer klassischen NaviProp überschreiten.

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

    Ich hab ja selber längere Zeit DataSets verwendet und mir ist klar, dass jede Table ihre verknüpften, ihr untergeordneten Tabellenzeilen kennt, daher hatte ich ja im Titel auch den tDS-Hinweis gegeben.
    Würde also eine Lieferung auf eine Ware verweisen, würde die Ware wissen, dass sie eine weitere Lieferung "bekommen hat". Das ließe sich ja auch einfach einbauen: Die Warenklasse bekommt ein IEnumerable von Lieferungen und die alleswissende Containerklasse weist dann jeder Warenklasseninstanz die ihr zugeordneten Lieferungen zu:

    VB.NET-Quellcode

    1. For Each Item In Items
    2. Item.Deliveries = Deliveries.Where(Function(x) x.ItemID = Item.ID)
    3. Next

    Würde eine weitere Lieferung für diesen Artikel hinzukommen, wüsste das damit der Artikel automatisch, da dank IEnumerable und Where die Daten immer bei Bedarf neu evaluiert werden.

    Mir ging es um den Rest des Textes. Das klang eher wie ein Erinnerungspost an Dich selbst. Aus dem Teil konnte ich (daher) keine Infos für mich rausziehen. Ja, auch T4 sagt mir was. Damit T4 hatte ich mich mal eine kurze Zeit vor ein paar Jahren beschäftigt. Aber mir fehlte die Fantasie für eine Nutzung.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.