Datenmodell Anpassung MVVM

  • C#

Es gibt 14 Antworten in diesem Thema. Der letzte Beitrag () ist von MichaHo.

    Datenmodell Anpassung MVVM

    Hallo,
    Da ich bei meinem DojoProjekt hier und da mit kleineren Problemen zu kämpfen hatte, habe ich mein Datenmodell noch einmal überdacht und angepasst.
    Vorher habe ich mir eine komplette Struktur aus Context, BusinessLogic, InstanceHolder und Provider (Repository) analog zum Projekt: WpfNote2 von @Nofear23m erstellt.
    Bevor ich nun die Logic für die einzelnen Models baue, wollte ich kurz fragen ob mein Datenmodell so passt, oder ob es Optimierungsbedarf gibt.

    Kurz zum Hintergund:
    Die Anwendung soll eine Verwaltung von Trainingsmitgliedern darstellen.
    Verwaltet wird dies von einem Admin (kann der Trainer sein, kann aber auch eine andere Person sein).
    Daher gibt es die Klasse User, die unter anderem eine LoginData hat.
    Die Klasse Person representiert eine Person, die entweder ein Member, ein Admin, ein MemberAdmin, ein Sensei oder ein SenseiAdmin sein kann.
    Member können nur ihre Anwesenheit am Trainingstag eintragen, der Sensei ist quasi der Trainer (ein Trainer kann selbst entscheiden ob er seine Anwesenheit einträgt oder nicht), der Admin verwaltet die Anwednung und benötigt dazu Zugangsdaten
    MemberAdmin und SenseiAdmin können dann beides sein und benötigen dann auch Zugangsdaten.

    Jede Person hat eine Liste von Graduierungen (ist das richtig so?)
    Die Klasse PresentEntry soll speichern, wer sich wann eingetragen hat -> ist es hier besser der Person auch eine Liste von Einträgen zu geben?

    Die Klasse BujinkanGraduation representiert eine Graduierung nach Bujinkan, diese wird in der Seed Methode beim ersten Start angelegt und dann nur noch zur Auswahl benötigt. Also quasi eine Vorgabe Liste.

    Ich hänge mal das ModelProjekt an, Vielleicht hat jemand Lust/Zeit kurz drüber zu schauen und mir Tips zu geben, was verbessert werden kann.

    Danke Euch
    Dateien
    "Hier könnte Ihre Werbung stehen..."
    Hallo @ErfinderDesRades
    Ich hab nun ein dgml Datei erstellt, ich lad sie mal hoch, als Graph wird mir das aber leider nicht angezeigt, nur als Text

    EDIT: ich hänge auch noch das SQl Script dran
    Dateien
    • Dojo.zip

      (4,42 kB, 82 mal heruntergeladen, zuletzt: )
    • Dojo.txt

      (5,8 kB, 76 mal heruntergeladen, zuletzt: )
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    als Graph wird mir das aber leider nicht angezeigt
    echt nicht?
    Also ich kenns vom SqlServer, da gibts ein SqlServer-ManagmentStudio dazu, damit kann man angugge alle Datenbanken, die der SqlServer verwaltet - in einem Treeview.
    Und in einer Datenbank gibts einen Node "DatenbankDiagramme", den kann man erweitern, und ein Diagramm erstellen, das ist dann ein ER-Diagramm, ganz ähnlich einem typDataset, oder einer Ef-Edmx-Datei.

    naja, ma gugge.
    jo, also ich hab die ForeignKeys in ein typDataset übertragen, da stellt sich das so dar:

    Offensichtlich willst du alles wasses gibt protokollieren ;)

    Auffallen tut mir die 1:n relation People->Graduations - das würde bedeuten, dass ein People mehrere Graduations hat.
    Ist das beabsichtigt, oder ists realiter nicht vielmehr so, dass ein People nur eine Graduation haben kann?
    Dann wäre die Relation falsch rum.
    Oder du beabsichtigst eine Graduierungs-History zu basteln, das würde man IMO aber auch anders anfangen.
    Ich sehe auch keine Grade-Tabelle in der DB - bildest du das jetzt wieder als Enum ab?

    ups - sorry - eine Tabelle habich ganz übersehen: BujinkanGraduations - das scheint das wohl zu sein.
    Aber keine der anneren Tabellen verweist auf diese - das kann eiglich nicht sein.
    Ich glaube der Graduations-Tabelle fehlt ein ForeignKey auf BujinkanGraduations - so könnte man eine Graduierungs-Historie abbilden, und alles ergäbe Sinn (für mich).



    Was mich noch beunruhigt ist, dass die Datenbank scheinbar nur 2 Datentypen kennt: Text und Integer.
    Ist das die Wahrheit - wie ist da der Umgang mit Datum-Werten geregelt? Nach meim Wissensstand kann sowas eiglich nicht gut gehen.

    so weit - muss weg.

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

    Hi @ErfinderDesRades
    das protokollieren hab ich nur drin weil ich die MVVM Struktur von @Nofear23m nachgebaut habe, ist nicht zwingend erforderlich.
    es gibt tatsächlich 2 Tabellen, die Graduierunen abbilden. Die BujinkanGraduation ist eine Tabelle, die beim Starten (erstmalig) angelegt wird. Weil sonst müsste man die Graduierungen, die die Teilnehmer haben können ja selbst irgendwie in die Anwendung kloppen.
    die Graduations soll eine Historie aller Graduierungen sein, die eine Person erhalten hat
    Wobei die Zuordnung zur BujinkanGraduation dadurch besteht, das die GraduationId auf die Tabelle zeigt, bzw. zeigen soll, ich hatte es nur vergessen umzubenennen. Müsste dann eigentlich BujinkanGraduationId heißen.

    jo und Text und Integer scheint ein ominösem von EFCore und SQLite zu sein. Funktioniert aber super, wenn ich ein Datum in die DB schreiben will, weise ich dem Model.Property das zu und EF kümmert sich drum das in die DB zu schreiben und auch wieder auszulesen.

    wie würdest Du das Datenmodell denn aufbauen?
    Vorraussetzung ist halt, das es 1 oder mehrere Personen geben kann, die Zugangsdaten haben können, alle anderen haben keine.
    Jede Person kann mehrere PresentEntry haben und mehrere Graduation als Historie.

    ich glaub was da wirklich verwirrt ist die Tabelle BujinkanGraduation, das ist wie gesagt nur eine Reading Tabelle, da darf nix mehr rein geschrieben werden.

    die ganzen Properties aus der Tabelle brauche ich aber, weil damit die Patches(Aufnäher auf dem Anzug) abgebildet werden. Jede Graduierung hat seine eigenen hintergrundfarben, Schriftfarbe usw.
    Hätte Ich diese Tabelle nicht, müsste der Anwender ja die Graduierungen alle selbst eintragen mit den ganzen Farben, Sternen usw.
    "Hier könnte Ihre Werbung stehen..."
    So, ich glaub jetzt hab ich es soweit richtig:

    SQL-Abfrage

    1. CREATE TABLE "PersonGraduations" (
    2. "Id" INTEGER NOT NULL CONSTRAINT "PK_PersonGraduations" PRIMARY KEY AUTOINCREMENT,
    3. "CreatedBy" TEXT NULL,
    4. "CreatedOn" TEXT NULL,
    5. "LastUpdatedBy" TEXT NULL,
    6. "LastUpdatedOn" TEXT NULL,
    7. "BujinkanGraduationId" INTEGER NULL,
    8. "ValidFrom" TEXT NULL,
    9. "ValidUntil" TEXT NULL,
    10. "PersonId" INTEGER NOT NULL,
    11. "LastUpdateTimestamp" TEXT NULL,
    12. "CreationTimestamp" TEXT NULL,
    13. "DeletedFlag" INTEGER NOT NULL,
    14. "DeletedTimestamp" TEXT NULL,
    15. CONSTRAINT "FK_PersonGraduations_BujinkanGraduations_BujinkanGraduationId" FOREIGN KEY ("BujinkanGraduationId") REFERENCES "BujinkanGraduations" ("Id") ON DELETE RESTRICT,
    16. CONSTRAINT "FK_PersonGraduations_People_PersonId" FOREIGN KEY ("PersonId") REFERENCES "People" ("Id") ON DELETE CASCADE
    17. );

    Ich hab die Tabelle Graduation umbenannt in PersonGraduation denn es ist ja eine Graduierung einer Person und die Person selbst kann davon dann mehrere haben (Historie).
    Die Tabelle Person Graduation hat nun eine BujinkanGraduatinID und eine BujinkanGraduation, damit ist dann die Zuordnung gegeben.
    Aber, wenn eine Person gelöscht markiert wird, sollen alle PersonGraduation auch als gelöscht markiert werden, aber nicht die Bujinkan Graduation, ist somit auch richtig, denn der FK wird als Restrict erstellt.

    Die Tabelle Person hat sich somit nicht geändert, aber die Tabelle PersonGraduation sieht nun so aus:

    C#-Quellcode

    1. public class PersonGraduation : ModelBase, ILogicalTimestamp, IProtocolable, ILogicalDelete
    2. {
    3. [Key]
    4. public virtual int Id { get; set; }
    5. public virtual int? BujinkanGraduationId { get; set; }
    6. public virtual BujinkanGraduation BujinkanGraduation { get; set; }
    7. public virtual DateTime? ValidFrom { get; set; }
    8. public virtual DateTime? ValidUntil { get; set; }
    9. public virtual int PersonId { get; set; }
    10. public virtual ICollection<Protocol> Protocols { get; set; }
    11. public DateTime? LastUpdateTimestamp { get; set; }
    12. public DateTime? CreationTimestamp { get; set; } = DateTime.Now;
    13. public bool DeletedFlag { get ; set; }
    14. public DateTime? DeletedTimestamp { get; set; }
    15. }


    Was mir jetzt aufgefallen ist, ist das wenn ich eine ID als nullable markiere, dann ist das Löschverhalten restrict und wenn ich es nicht als nullable markiere, ist das Löschverhalten Cascade...
    Dann schauen wir jetzt mal, wie ich das im Repository (Provider) umgesetzt bekomme...
    "Hier könnte Ihre Werbung stehen..."
    natürlich ist eine ID nicht nullable. Ein Datensatz ohne Id darf nicht vorkommen.
    Und natürlich muss das Löschverhalten Cascade sein. Wenn ein Datensatz rausfliegt, dann müssen auch alle Datensätze fort, die auf ihn verweisen.
    Wenn du also eine Person löschst, dann ist selbstverständlich ihre Graduations-Historie ebenfalls weg.
    So isses inne Realität, und inne DB nicht anders.
    Da würde es doch recht bald zu Exceptions kommen, wenn da Historien-Einträge rumfahren, deren Person-Verweise ins Leere verweisen.

    (Mag Ausnahmen geben, aber dies ist glaub ein ganz typischer Fall.)

    Soweit ich EF-Codefirst verstanden habe, brauchste da auch keinen Extra-Konfigurations-Code schreiben.
    Sondern die Graduation-Klasse kriegt eine Liste von History-Entries, und jeder History-Entry einen Verweis auf "seine" Graduation.
    Das ist die objektorientierte Entsprechung einer 1:n - Relation, und EF kümmert sich ums weitere.

    (So hab ich das jdfs verstanden, und fände es nicht gut, wenns anders wäre.)

    Mag verwunderlich erscheinen, der Graduation-Klasse eine List(Of History) zu geben, aber ist vlt. am Ende ganz schnucklig, wenn du eine Graduation auswählen kannst, und siehst alle VereinsMitglieder dieses Ranges.

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

    OK, Verwirrung perfekt.... :D

    Nur nochmal damit ich jetzt richtig mitkomme.

    Es gibt grundsätzlich erstmal 2 Tabellen die Graduierungen abbilden (oder doch nur eine????)
    Die BujinkanGraduiation bildet 1 Graduierung nach Bujinkan Vorgabe ab
    Die PersonGraduation soll 1 Graduierung (die aus der Auflistung aller BujinkanGraduierungen ausgewählt wird) darstellen.
    Dazu hat die Person selbst eine Auflistung aller bisher vergebenen PersonGraduation.
    Die BujinkanGraduation ist eigentlich NUR eine Auswahlliste und darf weder geändert, noch angepasst werden. Ich könnte die Tabelle auch komplett weg lassen und jedesmal beim Starten der Anwendung diese Auswahlliste erstellen, aber dann hätte ich ja keine Zuordnung. Es wäre auch durchaus möglich, das man die BujinkanGraduation garnicht vorausfüllt, sondern das der User die selber alle rein kloppen soll, das wollte ich dem User aber ersparen, denn die Graduierung nach Bujinkan ist ja fest vorgegeben, da gibts auch nix dran zu ändern. Wenn es jetzt eine KarateGraduation wäre, dann müsste es eine andere Auflistung sein.

    OK, weiter... Also wenn jetzt einer Person mehrere Graduierungen im Laufe der Zeit zugewiesen wurden, dann ist es natürlich richtig, das wenn ich die Person lösche, das dann auch alle historischen Graduierungen gelöscht werden, das wird es ja auch bei meinem Model, denn PersonGraduation ist ja als CascadeDelete gekennzeichnet.
    Was aber nicht sein darf ist, das wenn die Person gelöscht wird und alle dazugehörigen PersonGraduation, das dann die BujinkanGraduation gelöscht wird, denn die muss ja weiterhin bestehen. Wird im Model oben ja auch so gemacht, denn die BujinkanGraduation ist als RestrictDelete gekennzeichnet.

    Ich kriegs im Kopp einfach nicht zusammen...

    Vielleicht anhand eines anderen Beispiels...
    Sagen wir, wir haben ein intelligentes Auto. Das Auto kann, wenn es fährt, alle gefahrenen Strecken aufzeichnen.
    Also gäbe es eine Tabelle Auto, eine Tabelle Strecke.
    Die Tabelle Auto hätte eine Liste von Strecke
    Somit kann jedes Auto seine gefahrenen Strecken auflisten

    Um jetzt den Bogen zu meiner Situation zu spannen, würde jetzt eine weitere Tabelle hinzukommen StreckenAuswahl weil das Auto nur die Strecken abfahren darf, welche in der Auswahl stehen.
    Somit brauch die Tabelle Strecke eine Zuordnung zur StreckenAuswahl.

    Oder?
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    Es gibt grundsätzlich erstmal 2 Tabellen die Graduierungen abbilden
    nein - nur eine, nämlich BujinkanGraduierung.
    PersonGraduation stellt keine Graduirung dar, sondern ist eine Zuordnungs-Tabelle.
    Welche - so ist das bei Zuordnungs-Tabellen - beiden anderen Tabellen untergeordnet ist.
    Und deshalb - weil sie beiden anderen untergeordnet ist - wird auch keine Graduierung gelöscht, wenn eine Person gelöscht wird.
    Weil Löschweitergabe löscht nur die untergeordneten Datensätze - keinesfalls übergeordnete.
    Eine besondere Kennzeichnung (von wegen RestrictDelete) ist nicht erforderlich, und macht sicherlich was anneres als was du denkst.



    Jo, dein Auto-Beispiel passt. Fast.
    Weil in meiner Welt wäre egal, ob das Auto intelligent ist und Strecken aufzeichnen kann.
    Es gäbe einfah - wie du sagst:
    1) Strecken, gegeben in der Datenbank, und 2) Autos, gegeben in der Datenbank.
    Und dann kann man inne Datenbank über eine 3) Zuordnungs-Tabelle zuordnen, welche Strecken ein Auto fahren kann (oder gefahren ist, oder was auch immer).
    Wenn so ein Auto dann gelöscht wird, sind seine Fahr-Strecken (ich würde es nennen: "Auto_Strecke", oder "Erlaubte_Strecke") auch futsch, aber natürlich nicht die Strecken - weil die gibts ja weiterhin - inne Realität wie inne Datenbank.

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

    OK, hab ich ja soweit verstanden auch mein Autobeispiel (deswegen hab ich es angebracht).
    Also hab ich jetzt eine Zuordnungstabelle gemacht:

    C#-Quellcode

    1. public class PersonGraduation
    2. {
    3. public virtual int? BujinkanGraduationId { get; set; }
    4. public virtual BujinkanGraduation BujinkanGraduation { get; set; }
    5. public virtual int? PersonId { get; set; }
    6. public virtual Person Person { get; set; }
    7. public virtual DateTime? ValidFrom { get; set; }
    8. public virtual DateTime? ValidUntil { get; set; }
    9. }


    weis noch nicht ganz wie ich die beiden Datumse einbauen soll, die brauch ich aber damit man später sieht, wer wann welche Graduierungen bekommen hat. EDIT: bzw. wann die Person die Graduierung erhalten hat und wie lange er die vorherige Graduierung gehhalten hat.

    So, nun hat die Person also eine ICollection<PersonGraduation> also eine Auflistung der Zuordnungstabelle.

    Brauch die BujinkanGraduation nun auch eine Auflistung der Zuordnungstabelle?

    EDIT2:
    im OnModelCreating muss ich ja die Zuordnung setzen:

    C#-Quellcode

    1. protected override void OnModelCreating(ModelBuilder modelBuilder)
    2. {
    3. modelBuilder.Entity<PersonGraduation>().HasKey(k => new {k.PersonId, k.BujinkanGraduationId);
    4. modelBuilder.Entity<PersonGraduation>().HasOne(p => p.Person).WithMany(g => g.PersonGraduations).HasForeignKey(k => k.PersonId);
    5. modelBuilder.Entity<PersonGraduation>().HasOne(g => g.BujinkanGraduation).WithMany(p => p.People).HasForeignKey(k => k.BujinkanGraduationId);
    6. }


    die Klasse BujinkanGraduation hat dazu dann eine ICollection<PersonGraduation> People

    EDIT3: Tja, was soll ich sagen..... das klappt.... ich könnte schwören das ich die Constellation schon hatte....
    Hier der SQL Statement von der Zuordnungstabelle:

    SQL-Abfrage

    1. CREATE TABLE "PersonGraduations" (
    2. "BujinkanGraduationId" INTEGER NOT NULL,
    3. "PersonId" INTEGER NOT NULL,
    4. "ValidFrom" TEXT NULL,
    5. "ValidUntil" TEXT NULL,
    6. CONSTRAINT "PK_PersonGraduations" PRIMARY KEY ("PersonId", "BujinkanGraduationId"),
    7. CONSTRAINT "FK_PersonGraduations_BujinkanGraduations_BujinkanGraduationId" FOREIGN KEY ("BujinkanGraduationId") REFERENCES "BujinkanGraduations" ("Id") ON DELETE CASCADE,
    8. CONSTRAINT "FK_PersonGraduations_People_PersonId" FOREIGN KEY ("PersonId") REFERENCES "People" ("Id") ON DELETE CASCADE
    9. )


    "Hier könnte Ihre Werbung stehen..."

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