Laden...

Kleine Datenverarbeitung DatasetOnly

Erstellt von ErfinderDesRades vor 8 Jahren Letzter Beitrag vor 8 Jahren 9.922 Views
ErfinderDesRades Themenstarter:in
5.299 Beiträge seit 2008
vor 8 Jahren
Kleine Datenverarbeitung DatasetOnly

Anbei der Anfang einer "Quiz-Anwendung"

Das Konzept ist entstanden aus dieser Diskussion: Wie designe ich die Datenbank für ein Quiz mit drei verschiedenen Fragearten? und setzt sie quasi etwas fort.
Ihr könnt gerne - wenn ihr wollt - Antworten posten, Erweiterungen, Verbesserungen, Alternativen - am besten wäre natürlich mit angehängtem, lauffähigen Zips, auf der Vorlage basierend oder auch from Scratch.

Datenmodell
Bildle gugge: QuizFrage ist Zuordnungstabelle einer m:n - Relation, deren Datensätze jeweils eine Frage einem Quiz zuordnet.

Problem1: Polymorphie
Das komplizierte ist jetzt, dass es 3 verschiedene Arten von Fragen gibt:1.SpellFrage: verlangt eine buchstaben-getreue Antwort 1.BildFrage: Ein Bild wird gezeigt, dazu eine Frage, ebenfalls buchstaben-getreu zu beantworten 1.McFrage - **M**ultiple **C**hoice: Mehrere Antwortmöglichkeiten, von denen eine richtig ist

Es handelt sich also um ein Polymorphie-Problem, und OOP-mäßig würde man dazu neigen, etwas mit geerbten Basisklassen oder Interfaces zu versuchen.
Richtige Polymorphie wird aber vom Dataset nicht unterstützt, und würfe auch Präsentations-Probleme auf, die imo erst in Wpf eine wirkliche Antwort finden (DataTemplates).

Hier ists so gelöst, dass ein QuizFrage-(Zuordnungs-)Datensatz gleich 3 Verweise hat, auf jede Frage-Art einen.
Business-Logik muss nun sicherstellen, dass immer mindestens 2 der Verweise genullt sind - anders gesagt: Dass ein Zuordnungs-Datensatz nur eine Frage zuordnet, nicht 3 verschiedene auf einmal.
Diese "Umgehung der Polymorphie" erweist sich bei der Präsentation als überraschend praktisch: Dem User werden die verschiedenen Frage-Arten ja als Spalten präsentiert, und indem er in eine Spalte eingibt hat er bereits die Art der zugeordneten Frage bestimmt.
😁

Problem2: Multiple Choice
Ein anderer Trick ist die wechselseitige Relation zwischn McFrage und McAntwort:
Nämlich eine McFrage hat logisch viele Antwort(-Optionen), ist also übergeordnet.
Aber gleichzeitig hat sie auch einen Verweis (also einen Fremdschlüssel, untergeordnet) auf die richtige Antwort.
Gui-Logik sorgt nun dafür, dass wenn die DGV-Checkbox-Zelle der IsTrue-Spalte angeklickt wird, dass dann die Parent-Frage ihren Verweis auf diese Antwort setzt.
IsTrue ist übrigens eine berechnete Spalte, darin gar kein Wert eingebbar ist, sondern sie berechnet sich selbst, mit folgender Expression (eingetragen im Dataset-Designer):
count(child.ID)=1
also wenn es genau eine ChildRow gibt, ist dieses die richtige Antwort.
Da die ChildRow einer McAntwortRow aber gleichzeitig immer auch die ParentRow (McFrageRow) ist, kann es zu einer Antwort auch nur maximal 1 ChildRow geben.
Und wenn es sie gibt ist damit IsTrue true, und diese Antwort also als die einzig richtige gekennzeichnet 😁
(ups - mir fällt grad auf: das ist ja gar kein Multiple-Choice, da ist ja nur Choice 8o)

Der frühe Apfel fängt den Wurm.

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

Noch Bildle der Quiz-Zuordnungs-Ansicht:
Man sieht den ersten Quiz, und der hat 5 Fragen, und jede Frage verweist auf nur einen Frage-Typ.
Unten in der Zufügezeile kann man weitere Fragen zufügen.
In der vorletzten Zeile bin ich grad im Begriff, eine andere Frage anderen Typs zuzuordnen. Wenn ich das bestätige, wird dann die McFrage derselben Zeile genullt.

Der frühe Apfel fängt den Wurm.

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

Hier noch der Zip-Anhang, und Code:

using System;
using System.IO;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Windows.Forms;

namespace QuizApp {
   public partial class frmQuizApp : Form {
      private FileInfo _DataFile = new FileInfo(@"..\..\Data.xml");
      public frmQuizApp() {
         InitializeComponent();
         btLoad.Click += btLoad_Click;
         btSave.Click += btSave_Click;
         LoadData();
      }

      void btLoad_Click(object sender, EventArgs e) { LoadData(); }

      void btSave_Click(object sender, EventArgs e) {
         if (!this.Validate()) return;
         quizDts.WriteXml(_DataFile.FullName);
         quizDts.AcceptChanges();  // Doku lesen! AcceptChanges() bei hinterlegter Datenbank **nicht** verwenden! Unbedingt vorher Doku lesen und **verstehen**
         System.Media.SystemSounds.Asterisk.Play();
      }

      void LoadData() {
         quizDts.QuizFrage.ColumnChanged -= QuizFrage_ColumnChanged; // Event vor Massen-Operationen deaktivieren
         var bss = components.Components.OfType<BindingSource>().ToArray();
         foreach (var bs in bss) bs.RaiseListChangedEvents = false;
         quizDts.Clear();
         quizDts.ReadXml(_DataFile.FullName);
         quizDts.AcceptChanges();  // Doku lesen! AcceptChanges() bei hinterlegter Datenbank **nicht** verwenden! Unbedingt vorher Doku lesen und **verstehen**
         foreach (var bs in bss) {
            bs.RaiseListChangedEvents = true;
            bs.ResetBindings(false);
         }
         quizDts.QuizFrage.ColumnChanged += QuizFrage_ColumnChanged;
      }

      void QuizFrage_ColumnChanged(object sender, DataColumnChangeEventArgs e) {
         if (e.ProposedValue == null) return;
         quizDts.QuizFrage.ColumnChanged -= QuizFrage_ColumnChanged; // Event deaktivieren, sonst Selbst-Auslösung
         var tb = quizDts.QuizFrage;
         foreach (var col in new DataColumn[] { tb.SpellFrageIDColumn, tb.MCFrageIDColumn, tb.BildFrageIDColumn }) {
            if (col != e.Column && !e.Row.IsNull(col)) e.Row[col] = Convert.DBNull;
         }
         quizDts.QuizFrage.ColumnChanged += QuizFrage_ColumnChanged;
      }

      protected override void OnClosing(CancelEventArgs e) {
         if (quizDts.HasChanges()) {
            switch (MessageBox.Show(this, "Save Changes?", "Save Changes?", MessageBoxButtons.YesNoCancel)) {
            case DialogResult.Yes: btSave.PerformClick();
               break;
            case DialogResult.No:
               break;
            case DialogResult.Cancel: e.Cancel = true;
               break;
            }
         }
         base.OnClosing(e);
      }
      protected override void OnClosed(EventArgs e) {
         tabControl1.Dispose();
         base.OnClosed(e);
      }

      private void mCAntwortDataGridView_CellContentClick(object sender, DataGridViewCellEventArgs e) {
         var grd = (DataGridView)sender;
         if (grd.Columns[e.ColumnIndex].DataPropertyName != "IsTrue") return;
         var rwAntw = (QuizDts.MCAntwortRow)((DataRowView)bsMcFrageMcAntwort.Current).Row;
         var rwFrage = rwAntw.MCFrageRow;
         rwFrage.MCAntwortRow = rwFrage.MCAntwortRow == rwAntw ? null : rwAntw;
      }
   }
}

Also es ist noch "Klumpen-Architektur" 😉 , das würde man beim Weiter-Entwickeln diversifizieren.
Es ist auch nicht absturz-sicher, kann noch keine Bilder anzeigen, und wie man das Quiz nun spielt, hab ich mir noch nichtmal was zu ausgedacht.
Also falls sich jemand für interessiert, oder gar Lust hat, auch bischen was dran zu basteln, würde ich auch noch bischen weiter machen ansonsten lass ich das auf diesem "Proor-of-concept"-Stand.

geproofte Konzepte wären*streng typisiertes, relationales Datenmodell *Abspeichern und Laden ohne Db - die Xml ist sogar einfach in den Sources anzugucken 🙂

*databinding-getriebene Oberfläche *crud-komplett (lesen, schreiben, ändern löschen aller Datensätze) *Save-Changes-Abfrage beim Schließen *Lösung des Problems der verschiedenen Frage-Typen *Lösung des Problems der Multiple-Choice-Antworten

Der frühe Apfel fängt den Wurm.