Laden...

Tree(Node) Collection als Zwischenschicht bauen [und grundsätzliches zur 3-Schichten-Architektur]

Letzter Beitrag vor 14 Jahren 106 Posts 72.817 Views
was passiert wenn....

hallo zusammen,

was kann denn eigentlich passieren, wenn ich die Abfragen und alles im GUI habe

viele Grüsse

Ronny

Hallo romu2000,

aller Voraussicht nach wird das GUI blockieren. Solange im GUI-Thread eine Aktion ausgeführt wird, ist das GUI blockiert.

herbivore

danke

jetzt habe ichs verstanden 🙂

grüssle

ronny

Hallo,
ich habe mir mal das Beispiel von Rainbird angeschaut.
Gibt es immer nur ein Model für sämtliche Funktionen eines Programms?
Wird das ganze dann nicht schnell etwas unübersichtlich?

Models

Es ist nur ein Beispiel. Mehrere Models hätten den Rahmen etwas gesprengt.

Das kommt auf die Anwendung an. Wenn eine Anwendung umfangreich ist, wird auch das Model umfangreich. Bei großen Anwendungen hängt man aber nicht alles an einem Model auf. Man unterteilt die Anwendung in mehrere große Blöcke.

Angenommen ich will ein CRM-System bauen, welches Kunden, Angebote und Projekte verwalten kann. Dazu würde ich zunächst drei Model-Klassen in drei unterschiedlichen Assemblies (DLLs) erstellen. Die könnten z.B. so heißen:
*CustomerManagement *OfferManagement *ProjectManagement

Für jedes Model gäbe es natürlich einen eigenen Controler (ein Hauptformular sozusagen) und einige Views. Als Ergbnis habe ich nun drei autonome saubere Module. Aber ich habe noch keine vollständige Anwendung.

Also würde ich noch eine Assembly erstellen (Diesmal eine EXE). Diese würde auch ein Model bereitstellen, welches z.B. CrmApplication heißen könnte. Außerdem natürlich einen Controler (das Anwendendungshauptfenster). In diesem Fenster würde ich die jeweiligen Hauptfenster der drei Anwendungsmodule als Views laufen lassen. Über das Model CrmApplication können die Module kommunizieren. So kann z.B. ein Doppelklick in der Angebotsliste eines Kunden im Kundenformular, das Angebot öffnen, ohne dass das Kundenformular das Angebotsformular kennen muss.

Man muss sich eben ein paar Gedanken über die Architektur der GUI machen, dann wirds auch bei MVC nicht unübersichtlicht.

Hallo,
danke für die Antwort.
Die BusinessComponents (in deinem Bsp OrderManager und SupplierManager) bleiben aber trotzdem alle in einer Assembly oder die dann auch in die Module auslagern und den Datenzugriff wieder in ein getrenntes Assembly (DBHelper ist das glaube ich in deinem Bsp) ?

Wie kann das Kundenformular das Angebotsformular öffnen ohne es zu kennen? Muss da dann noch ein getrenntest Interfaceassembly her oder wie funktioniert das dann?!

Assemblies

In einem realen Projekt würde man allgemeine Tools wie DBHelper auf jeden Fall in eine separate Assembly stecken. Ob die Geschäftslogik (OrderManager etc.) in verschiedene Assemblies gepackt wird, hängt von der Größe des Projekts ab. Wenn die komplette Geschäftslogik eh nur auf einem Applikationsserver (Remoting Host) liegt und am Stück deployed wird, macht es keinen Sinn, alles aufzusplitten. Das gibt nur Verweis-Hölle. Wenn man die einzelnen Komponenten auf verschiedenen Applikationsservern betreiben will bzw. Komponenten einzelt deployed werden sollen, ohne dass die anderen angefasst werden (Vielleicht soagr im laufenden Betrieb, wie bei ASP.NET), sollte man die Komponenten in jeweils eigene Assemblies aufteilen.
Grundsätzlich sollte man bei großen Projekten mit vielen Komponenten, diese in größere Bereiche (Kann man gut mit Namespaces abgrenzen) einteilen. Komponenten aus Bereich A sollten nie direkt auf Komponenten in Bereich B verweisen, sondern über die öffentliche Remoting-Schnittstelle gehen. Alles andere führt in den meisten Fällen, zu Nudelsalat. Grundsätzlich sollte man die Geschäftslogik-Assemblies niemals den Clients geben, sondern nur Schnittstellen (In meinem Beispiel habe ich das glaube ich aus Zeitgründen aber nicht gemacht; Es ist ja auch nur ein Beispiel). Für jede Geschäftslogik-Assembly muss es dann logischerweise eine Schnittstellen-Assembly geben. Auch Komponenten aus verschiedenen Bereichen sollten nur über die Schnittstellen miteinander reden.

Zu der Sache mit dem Kunden- und dem Angebotsformular:

Kunden- und Angebotsformular ist im Sinne des Model CrmApplication eine View. Das Anwendungshauptformular ist der Controler für CrmApplication und hat die beiden mit dem Model CrmApplication bekannt gemacht. Wenn das Kundenformular nun ein Angebot öffnen will, muss es sich nur vertrauensvoll an sein CrmApplication-Model wenden:


// Angebot öffnen
_myCrmApplication.OpenOffer(_offerList.SelectedOfferID);

Das CrmApplication-Model feuert aufgrund dieses Aufrufes ein LoadOffer-Ereignis, welches das Anwendungshauptformular fängt:


private void _myCrmApplication_LoadOffer(object sender,LoadOfferEventArgs e)
{
    // Neue Instanz des Angebotsformular erzeugen
    OfferFrom offerView=new OfferForm();

    // Instanz mit Model bekannt machen
    offerView.Model=_myCrmApplication;

    // Instanz zur Auflistung der offenen Angebotsviews zufügen
    _offerViews.Add(e.OfferID,offerView);

    // Angebot laden und anzeigen
    offerView.Open(e.OfferID);
    offerView.Show();
}

Auf diese Weise entkoppekt ein Controler bei MVC die Views voneinander.

Hi!

@Rainbird: Vielen Dank für das klasse Beispiel. Hat mir wirklich geholfen.

Was müsste ich bei deinem Beispiel beachten, wenn

3 Clients auf die BL (und darüber auf die DAL) zugreifen, die auf einem Extra Server liegt? Gibt es probleme bei parallelen Zugriff??

Gruß
Tokka

Was einmal war, wird nie wieder sein...

Parallelzugriffe

Meinst Du mit Extra Server den Datenbank Server (SQL Server, Oracle etc.) oder einen separaten Applikationsserver für die Datenzugriffslogik?

Geschäftslogik und Datenzugriffslogik auf zwei Applikationsserver aufzuteilen ist nicht unbedingt sinnvoll. Die Server müssen ja über TCP/IP miteinander reden. Natürlich greifen auch die Clients über TCP/IP zu. Bei zwei Applikationsservern (Einer für BL und einer für DAL) sind die Antwortzeiten beim Client um einiges länger, als bei einem zentralen Applikationsserver (DAL und BL auf diesem einen Server).

Bei zwei Applikationsservern läuft die Kommunikation so ab:*Der Client ruft via TCP einen Dienst auf dem BL-App-Server auf *Der BL-App-Server besorgt sich via TCP die Daten vom DAL-App-Server *Der DAL-App-Server kommuniziert mit dem Datenbankserver

Die Daten müssen also zwei Mal übers Netzwerk geschoben werden. Das sollte man bedenken. Bei einem zentralen Applikationsserver fällt ein "Netzwerk-Hop" weg.

Trotzdem muss es nicht schlecht sein, die Schichten wirklich auf verschiedene Server aufzuteilen. Ich würde allerdings eher einzelne Dienste komplett (Jeweils mit BL und DAL) auf verschiedene Server verteilen. Das kommt auf den Anwendungsfall an. Allerdings sind zwei Applikationsserver für nur 3 Clients ziemlich überdimensioniert. Selbst bei Installationen mit 50 oder mehr Benutzern ist ein einzelner Applikationsserver meistens völlig ausreichend (Ausgehend von Standard-Business-Anwendungen, die mit Daten wie Kundeninfos, Angeboten etc. hantieren).

Was Parallelzugriffe betrifft, sollte es bei SingleCall-Aktivierung keine Probleme geben. Die Objekte leben nur für einen Methodenaufruf. Jeder Methodenaufruf von einem Client wird in einem separaten Thread ausgeführt. Bei fünf gleichzeitigen Client-Zugriffen sind das fünf Threads. Auch bei Singleton-Aktivierung läuft jeder Client-Aufruf in einem eigenen Thread. Allerdings verwenden alle Clients das selbe Objekt (und damit auch die selben Variableninstanzen). Bei Singleton-Aktivierung muss man also IMMER threadsicher programmieren.

Damit auf der Datenbank alles konsistent bleibt, sollte man alle schreibenden Operationen in Transaktionen ausführen (Namensraum System.Transactions).

Grundsätzlich ist es egal, auf wie viele Server Du welche Logik aufteilst. Das Prinzip ist immer das selbe. Man muss Vor- und Nachteile der möglichen Szenarien gegeneinander abwägen.

Vielen Dank für deine Ausführliche Antwort.

Die BL und DAL werde zusammen auf dem SQL-Cluster liegen.
Das ganze auf 2 Separate Maschinen aufzuteilen ist nur ein Gedanke, den ich im Hinterkopf habe... (Bei Java habe ich ja den AS und den DB-Server).

Natürlich werden nicht nur 3 Clients eingesetzt, es ging mir ja ehr nur um die Parallälität. So wie es aussieht, wird die GUI auf ca 900 Clients laufen.

Nochmals vielen Dank. Nun können wir uns mehr darunter vorstellen und das Konzept verfeinern!

Gruß
Tokka

Was einmal war, wird nie wieder sein...

Gibt es einen Grund warum Databinding nicht verwendet wurde?

"Das Problem kennen ist wichtiger, als die Lösung zu finden, denn die genaue Darstellung des Problems führt automatisch zur richtigen Lösung." Albert Einstein

MVC-Beispiel

Es ist ein MVC-Beispiel. Ich wollte nicht unnötig viele verschiedene Technologieen mit reinbringen, sondern zeigen, wie MVC funktioniert.

In der Praxis spricht aber nichts dagegen, auch Data Binding einzusetzen.

Gratulation

Hallo.

Noch einmal danke für dieses erstklassige Beispiel.
Ich kann es jedem auch in Hinblick auf verteilte Anwendungen empfehlen.

Ich versuche gerade eine bestehende Anwendung, die eine DataGridView verwendet, nach dem MVC-Muster umzubauen.

In der Praxis spricht aber nichts dagegen, auch Data Binding einzusetzen.

In deinem Beispiel liefert das Model für die Bestellungen ein DataSet inkl. Relation zurück. d.h. ich kann daran dann meine Elemente der Oberfläche mit Datenbindung binden, ohne dass ich wieder unerwünschte Abhängigkeiten schaffe?

Abhängigkeiten

Das kommt auf die Art Deiner Anwendung an. Du kannst ein "lockeres" Model oder ein "strenges" Model bauen.

Das lockere Model gibt großzügigen Zugriff auf die Daten (z.B. ganze DataSets) und verlässt sich darauf, dass die Views diese "Freiheit" nicht missbrauchen. Ein Mißbrauch wäre z.B. das direkte Zufügen neuer Spalten in einer DataTable von einer View aus. Trotzdem ist das Model Herr über die Daten. So könnte das Model z.B. diverse Events des DataSets und seiner Tables abonnieren, um Änderungen (z.B. Neue Datensätze, die per DataGridView angelegt wurden) mitzubekommen und entsprechend darauf zu reagieren (oder auch enzuschreiten und unzulässige Operationen abzubrechen).

Das strenge Model gibt die Daten gar nicht erst so freizügig raus. Alles wird in kleinen Häppchen (z.B. in Rows oder einzelnen Daten-Objekten) angeboten. Für die Manipulation der Daten müssen grundsätzlich entsprechende Methoden bzw. Properties bemüht werden. Diese Variante eignet sich dann besonders gut, wenn man in größeren Teams arbeitet, oder die Anwendung vorsieht, dass Endanwender oder sonstige Dritte später PlugIns für die Anwendung schreiben. Was die Entwickler zukünftiger PlugIns möglicherweise für einen Klumpatsch-Code erzeugen, kann man nicht wissen, deshalb bietet man ihnen die Dinge so an, dass sie nichts falsch machen können. Ein strenges Model kommt einer unmissverstänlichen Plugin-Architektur entgegen.

Dataset laden

Hallo,

erstmal möchte ich mich Icarus666 anschließen und für dieses Beispiel danken.
Es hat auch mir den Einstieg zu MVC ermöglicht.

Nun möchte ich in meiner Anwendung in einem Lieferanten Editor alle zugehörigen Daten zu einem ausgewählten Lieferanten bearbeiten oder neu erstellen können.

betroffene Tabellen in meiner MsSql2005 Datenbank wären: alle in dem angefügten Ausschnitt gezeigten. Die ID Spalten sind Guid Werte.

Erste Frage: ist an der Aufteilung der Tabellen irgendetwas auszusetzen oder auffällig schlecht?

Hauptproblem: Zugriff und aktuallisierung der Datenbank

Meine Ansatz ist alle Tabellen samt Beziehungen im model als dataset zu laden um auf alle Daten direkt in einer Form mit TabPages zugreifen zu können.

model.cs AuszÜge :

        private string GetDsLieferantField(string table, string field)
        {
            // Wenn ein Lieferant geladen ist ...
            if (IsLieferantLoaded)
                // Feldinhalt zurückgeben
                return "" + _dsLieferant.Tables[table].Rows[0][field].ToString();
            else
                // Leeren String zurückgeben
                return "";
        }
        
        
        private void SetDsLieferantField(string table, string field, object value)
        {
            // Wenn ein Lieferant geladen ist ...
            if (IsLieferantLoaded)
            {
                // Bestehenden Wert lesen
                object oldValue = _dsLieferant.Tables[table].Rows[0][field];

                // Feldwert festlegen
                _dsLieferant.Tables[table].Rows[0][field] = value;

                // Wenn der Wert geändert wurde ...
                if (value.ToString() != oldValue.ToString())
                {
                    // Änderungsflag setzen
                    _lieferantDirty = true;

                    // Wenn für SupplierChanged Ereignisprozeduren registriert sind ...
                    if (LieferantChanged != null)
                        // Ereignis feuern
                        LieferantChanged(this, null);
                }
            }
            else
                // Fehlermeldung erzeugen
                RaiseError("Es ist kein Lieferant geladen!");
        }



private void LoadDsLieferant(Guid LieferantID)
        {
            if (LieferantID != Guid.Empty )
            {
                // Dataset Lieferant mit dem zu bearbeitenden Lieferant laden
                _dsLieferant = new DataSet();
                _dsLieferant = _lieferantenManager.GetLieferantByID(LieferantID);

                //n:1 Tabellen komplett laden, zwecks Auswahl in ComboBox Controlls
                DataTable anredeTableRec = _lieferantenManager.GetAnredeTable();
                //.......
                
                //hier die Adressen, Kontakt usw. Tabellen über n:m Tabellen laden 
                //mit Einschränkung für den einen Lieferanten
                
                // Tables und Relations dem Dataset hinzufügen
                _dsLieferant.Tables.Add(anredeTableRec);
                _dsLieferant.DataSetName = "DsLieferant";
                // usw....
                _dsLieferant.Relations.Add("Lieferant2Anrede", _dsLieferant.Tables["Anrede"].Columns["AnredeID"], _dsLieferant.Tables["Lieferant"].Columns["AnredeID"], true);
                
            }
	 }

LieferantenManager.cs Auszug:

public DataSet GetLieferantByID(Guid lieferantID)
        {
            // Parameterliste erzeugen
            SqlParameter[] inputParams = new SqlParameter[1];

            // Parameter zur Übergabe des Lieferantenschlüssels erzeugen
            inputParams[0] = new SqlParameter("@lieferantID", SqlDbType.UniqueIdentifier, 0, "LieferantID");
            inputParams[0].Value = lieferantID;
            
            // Einen Lieferanten abrufen und zurückgeben 
            return DBAccess.RequestDataDs(DBCONNECTION, ("SELECT * FROM Lieferant WHERE LieferantID= @lieferantID"), inputParams, "Lieferant", "Lieferant");
        }

public DataTable GetAnredeTable()
        {
            return DBAccess.RequestDataTb(DBCONNECTION, "SELECT * FROM Anrede ORDER BY ModDate", null, "Anrede");
        }

Ist es dann möglich alle betroffenen Tabellen in der Datenbank anhand des Veränderten Datasets zu aktuallisieren, oder muß ich jede Tabelle einzeln mit einem DataAdapter aktuallisieren?
Was für eine Vorgehensweise ist zu empfehlen.

ist überhaupt irgend etwas von meiner Überlegung zu gebrauchen?

ich würde auch gerne noch detailiertere Fragen stellen und mein vorgehen näher erläutern, falls mir jemand helfen möchte.

mfg

Auszug aus der Datenbank:

Model-Aufbau

Der Ansatz, die Datensätze aus verschiedenen Tabellen in ein DataSet zu laden und im Model zu halten ist generell gut. Wenn Du allerdings Daten hast, die sich rasch "vermehren" (z.B. Bestellungen und deren Positionen), solltest Du den Ansatz verfeinern.

Stell Dir vor Deine Lieferantenverwaltung läuft mal fünf Jahre lang und die Anwender erfassen eifrig Bestellungen. Bald werden einzelne Lieferanten hunderte oder tausende von Bestellungen haben. Diese könnten auch mal locker 200 Positionen haben. Wenn Du all diese Daten immer komplett lädst, wird Deine Anwendung immer langsamer. Spätestens bei 500 Bestellungen mit Positionen müssen die Anwender Kaffee trinken gehen, bis das Formular alles geladen und angezeigt hat.

Du solltest größere oder schnell wachsende Tabellen deshalb immer erst dann laden, wenn der Benutzer die Daten benötigt (z.B. erst dann, wenn er auf die Registerkarte "Bestellungen" wechselt). Außerdem solltest Du Daten, die der Benutzer momentan nicht benötigt, ger nicht erst von der Datenbank abrufen (z.B. standardmäßig nur die offenen Bestellungen anzeigen, statt alle; Das werden selten mehr als fünf sein).

Beim speichern sollten die Daten auf Korrektheit und Konsistenz geprüft werden (Dass z.B. kein Lieferant ohne gültige Zahlungsbedingungen gespeichert wird). Das sollte auf JEDEN FALL in der Geschäftslogik (Klasse LieferantenManager) passieren und nicht im Model. Da Du mehrere Tabellen hast, solltest Du auch Transaktionen verwenden (das geht mit dem TransactionScope-Objekt aus dem Namensraum System.Transactions ganz einfach). Du könntest z.B. eine Save-Methode schreiben, der Du mehrere Tabellen in einem DataSet übergibst. Das Model-DataSet direkt würde ich nicht unbedingt verwenden, da dort meistens noch zusätzliche Tabellen enthalten sind, die man nur zu Anzeige benötigt (z.B. Anrede). So könnte eine solche transaktionale Speicher-Methode aussehen:


using System.Transactions;

...

// Speichert Änderungen an Lieferanten und Lieferantenadressen in einer Transaktion.
public void SaveSupplierData(DataSet data)
{
    // Adressen prüfen (Ist eine Rechnungsadresse angelegt etc.)
    CheckAddresses(data);

    // Adress-Verknüpfungen prüfen
    CheckAddressLinks(data);

    // Zahlungsbedingungen prüfen
    CheckPaymentTerms(data);

    // Anrede prüfen
    CheckSalutation(data);

    using(TransactionScope scope=new  TransactionScope(TransactionScopeOption.Required))
    {
        // Lieferant speichern
        SaveSuppliers(data.Tables["Suppliers"]);

        // Adressen speichern
        SaveAddresses(data.Tables["SupplierAddresses"]);

        // Adress-Verknüpfungen speichern
        SaveAddressesToSuppliers(data.Tables["SupplierAddresses2Suppliers"]);

        // Transaktion abschließen
        scope.Complete();
    }
}

Jede der aufgerufenen SaveXXX-Methoden verwendet intern einen DataAdapter, um die jeweilige Tabelle zu speichern.

Ich helfe Dir gerne, wenn Du weitere Fragen hast.

Model-Aufbau

Hallo,

ich habe jetzt versucht Daten aus einem Dataset mit n:1 und n:m Beziehungen darzustellen:

		private void FillList()
		{
            // Liste leeren
            listViewLieferanten.Items.Clear();
			// Alle Lieferanten durchlaufen
            foreach (DataRow row in _model.ListedDsLieferanten.Tables["Lieferant"].Rows)
			{ 
				// Neuer Listeneintrag
                //ListViewItem item = listViewLieferanten.Items.Add(row["LieferantTypID"].ToString());
                ListViewItem item = listViewLieferanten.Items.Add(row.GetParentRow("Lieferant2LieferantTyp")["Name"].ToString());
                item.SubItems.Add(row["LieferantNummer"].ToString());
                item.SubItems.Add(row["Matchcode"].ToString());
                item.SubItems.Add(row["Name1"].ToString());
				item.SubItems.Add(row["Name2"].ToString());
                
                item.Tag = (Guid)row["LieferantID"];
			}
        }
  private void FillList()
        {
            // Liste leeren
            listViewAdressen.Items.Clear();

            // Alle Adressen zu ausgewähltem Lieferanten durchlaufen
            // wobei die eigenschaft _model.AdressenTable  _dsLieferant.Tables["Adresse"] liefert
            foreach (DataRow row in _model.AdressenTable.Rows)
            {
                // Neuer Listeneintrag
                ListViewItem item = listViewAdressen.Items.Add(row["Land"].ToString());
                item.SubItems.Add(row["PLZ"].ToString());
                item.SubItems.Add(row["Ort"].ToString());
                item.SubItems.Add(row["Strasse"].ToString());
                item.SubItems.Add(row["Zusatz"].ToString());
                
                DataRow lieferantAdresseRow = row.GetChildRows("Adresse2LieferantAdresse")[0];
                item.SubItems.Add(lieferantAdresseRow.GetParentRow("LieferantAdresse2AdresseTyp")["Name"].ToString());
                
                item.Tag = (Guid)row["AdresseID"];
            }
        }

ist das so der sauberste weg, oder kann man das auch mit Databinding bewerkstelligen? Ich vermute, daß dann die Datenänderungen einfacher zu handhaben sind (?).
Hatt man noch mehr Vorteile mit einem komplett aufgebautem Dataset der aktuell benötigten Daten, ausser den Zugriff über Child und Parent?
Sollte man immer nur über Eigenschaften auf das Dataset im model zugreiffen (zweiter Block), oder kann man dies auch direkt machen wie im ersten Block.

vielleicht nur ein kurzer ratschlag zu diesen Themen, wenn ich erstmal eine gerade linie in meiner vorgehensweise habe, dann wiederholt sich der Rest ja laufend.

ps.: zu den Aktualisierungen der Daten bin ich noch nicht gekommen, werde aber Deinen vorgeschlagenen Weg gehen(oder es zumindest versuchen). also schon mal danke für die Antwort.
Ist es eigendlich hier im Forum erwünscht Code zu posten, oder sollte man dies auf ganz ganz kurze Ausschnitte begrenzen?

Verknüpfte Tabellen

Ob Du das DataSet direkt veröffentlichst, oder die einzelnen DataTables spielt auf den ersten Blick keine Rolle, da man über die DataSet-Eigenschaft von DataTable auch an das übergeordnete DataSet kommt.

Ich würde trotzdem eher die einzelnen DataTables als Eigenschaften veröffentlichen. Das macht die Schnittstelle des Models "freundlicher". Man sieht mit einem Blick auf das Klassendiagramm des Models schnell, was für Daten angeboten werden. Wenn alles über eine einzige Holzhammer-DataSet-Eigenschaft geht, kann weiss man erst zur Laufzeit, was da alles drin ist. Besonders wenn im Team gearbeitet wird und Kollegen mit dem Code arbeiten müssen, zahlt sich eine "selbsterklärende" API aus.

Wichtig bei der ganzen Sache ist, dass das Model mitbekommt, wann neue Datensätze angelegt, bestehende geändert oder gelöscht werden. Dafür sollte das Model Ereignisse des DataSets bzw. der einzelnen DataTables abonnieren (z.B. DataTable.RowChanged, um Änderungen an Zeilen zu überwachen).

Generell ist es ok, Code zu posten. Kurz fassen ist meistens besser, als seitenlange Listings zu posten, da die meisten keine Lust haben das alles zu lesen. Bei komplexeren Themen (wie z.B. MVC) lassen sich größere Code-Fetzen oft nicht vermeiden. Wichtig ist, dass der Code ausreichend kommentiert ist.
Gar nicht gern sehen wir Leute, die Code posten, der irgendeine Exception wirft und wollen dass andere ihren Code debuggen, weil sie selber zu faul sind (Trifft auf Deine Posts hier ja nicht zu).

Databinding

hallo rainbird,

hab jetzt ein wenig mit databindings aus probiert und hätte da mal die ein oder andere frage.
Kann es sein, dass das arbeiten mit bindings zur Darstellung von Daten einfacher ist, aber wenn es um änderungen geht man doch besser jedes datenfeld einzeln im model veröffentlicht wie ich es auch weiter oben mit den lieferantenfeldern gemacht habe und dies dann für alle tabellen in der Anwendung?
ist es das was du mit dem strengen model meinst?
Und liegt ein weiterer Vorteil darin, dass die Daten Prüfung (wie von Dir angeregt) so wesentlich einfacher und direkter erfolgen kann?
Ich frage nur lieber nochmal nach, da es ja doch eine grundlegende entscheidung zum aufbau der anwendung ist und ich allzuviele neuanfänge oder umstrukturierungen vermeiden möchte.

Original von luciluke
Kann es sein, dass das arbeiten mit bindings zur Darstellung von Daten einfacher ist, aber wenn es um änderungen geht man doch besser jedes datenfeld einzeln im model veröffentlicht wie ich es auch weiter oben mit den lieferantenfeldern gemacht habe und dies dann für alle tabellen in der Anwendung?

Würde ich nicht sagen. Der Vorteil von databinding ist ja, dass Benutzereingaben direkt in die DataTable übernommen werden.

ist es das was du mit dem strengen model meinst?

Teilweise. Ein strenges Model würde für die einzelnen Operationen Methoden anbieten, statt einfach das ganze DataSet zu veröffentlichen. So könnten solche Methoden z.B. aussehen:

public void CreateAddress()
{
    ...
}

public void DeleteCurrentAddress()
{
    ...
}

public void SaveChanges()
{
   ...
}

public string AddressName1
{
    get { ... }
    set { ... }
}


public string AddressName2
{
    get { ... }
    set { ... }
}
[

public string AdressStreet
{
    get { ... }
    set { ... }
}

public string AddressZIP
{
    get { ... }
    set { ... }
}

public string AddressCountry
{
    get { ... }
    set { ... }
}

Hoffentlich ist rübergekommen, was ich mit "streng" meine.

Und liegt ein weiterer Vorteil darin, dass die Daten Prüfung (wie von Dir angeregt) so wesentlich einfacher und direkter erfolgen kann?

Du Prüfung der Daten und die eigentliche Speicherung findet nicht im Model statt, sondern in der entsprechenden Geschäftslogik-Komponente auf dem Applikationsserver (so vorhanden).

Hallo Rainbird,
erstmal will ich mich meinen Vorpostern anschließen und mich bedanken für das Beispiel welches die mir noch sehr nebulöse, schleierhafte Welt MVC's, Patterns etc. etwas Licht ins Dunkel bringt.

Du Prüfung der Daten und die eigentliche Speicherung findet nicht im Model statt, sondern in der entsprechenden Geschäftslogik-Komponente auf dem Applikationsserver (so vorhanden).

Genau damit habe ich noch so ein Problem:

Wenn ich es richtig verstanden habe ist in deinem Beispiel BusinessComponents gleich Geschäftslogik. Mal vom DBHelper abgesehen.

Theoretisch kann doch die Geschäftslogik schon auf Datenbank-Ebene abgebildet sein. Zum Beispiel lasse ich nur Datensätze zu die eine korrekte Postleitzahl haben.
Wird gegen diese Regel verstossen, dann wirft die Datenbank eh eine Exception die zum Client durchgereicht wird und die Transaction wird abgebrochen.

Das wäre für mich die einfachste Lösung. Ich brauche nichts zu prüfen, da die Datenbank das für mich schon macht.

Wie wir nun aber wissen, möchte irgend jemand schon bei der Eingabe in der Textbox bzw. im DataGridView wissen bzw. ist gefordert das keine Buchstabeneingabe möglich sein soll.

Es sollte/muß also im View geprüft werden. Oder wo findet das nun statt?
Wo würdest du nun in deinem Beispiel die Postleitzahl prüfen, wenn es gefordert wäre das nur Zahlen eingegeben werden dürfen?

Mir ist eben auch noch folgendes durch den Kopf gegangen.
Ist es nicht gleich besser bei größeren Anwendungen die Models und Views als jeweils ein Plugin zu implementieren, welche der PluginHost, die Hauptanwendung(Controller), dann ladet?

Gruß falangkinjau

Geschäftslogik schwammig

Geschäftslogik ist ein schwammiger Begriff, da nirgends genau definiert ist, was Geschäftslogik ist, und was nicht.

Ich unterscheide zwischen trivialer Geschäftslogik und komplexer Geschäftslogik. Trivial wäre z.B. das bilden einer Summe aus diversen Posten. Sobald die Logik aber Netzwerkzugriffe (z.B. auf die Datenbank, auf Webservices oder auf den Applikationsserver) oder Zugriffe auf Dateien benötigt, sehe ich sie nicht mehr als trivial an. Triviale Dinge lassen sich immer als statische Funktion abbilden (Paremter übergeben, Ergebnis zurückbekommen). Alles andere ist komplex.

Komplex wäre z.B. das Abbuchen der Mengen einzelner Artikel im Lager innerhalb einer Transaktion beim freigeben eines Auslagerungsauftrages.

Komplexe Geschäftslogik gehört idealerweise auf einen Applikationsserver. Alles mit SQL (z.B. in Gespeicherten Prozeduren auf dem SQL Server) abzubilden, ist deshalb nicht sinnvoll, da SQL auf die Datenbankwelt beschränkt ist. Was ist, wenn z.B. nach erfolgter Auslagerung ein XML-Dokument von der ERP-Anwendung an die Versandanwendung eines Drittanbieters gesendet werden muss? Eine C#-Komponente (die z.B. auch die Windows Workflow Foundation verwenden kann) auf einem Applikationsserver kann das ohne weiteres erledigen. Eine gespeicherte Prozedur hätte damit schon ihre Probleme. Das Debuggen, Testen und Versionieren von Gespeicherten Prozeduren ist zudem umständlich. Der Datenbankserver sollte nicht mit dem verarbeiten der Geschäftslogik belastet werden, sondern den "Kopf" frei haben, um schnelle Antwortzeiten bei SQL Abfragen zu ermöglichen.

Sowas am Client abzubilden ist im Sinne von schlanken und flexiblen Geschäftsprozessen nicht Sinnvoll, da jede kleine Änderung des Prozesses eine Neukompilierung des Clients und das erneute Deployment auf den Arbeitsstationen nach sich zieht.

Triviale Geschäftslogik kann auch auf dem Client laufen. Manchmal wird die selbe triviale Geschäftslogik an verschiedenen Stellen benötigt (Möglicherweise auch am Server und am Client). In diesem Fall schreibe ich eine separate Assembly. Die triviale Geschäftslogik bilde ich als statische Funktionen in diversen Klassen ab (In VB6 würde man Module sagen), die in diese separate Assembly kommen. Diese Assembly kann ich nun in meinen Geschäftskomponenten auf dem Applikationsserver verwenden und auf dem Client.

Angenommen ich schreibe eine Anwendung zur Auftragsverarbeitung. Ein Auftrag hat mehrere Positionen. Diese haben Mengen, Einheiten, Einzelpreise, Steuersätze, Rabatt, Aufschläge etc.. Am Client möchte ich jederzeit die Auftragssummen und die Beträge der einzelnen Steuersätze (16% und 7% z.B.) sehen. Auf dem Applikationsserver werden die Aufträge gebucht. Auch da möchte ich auf jeden Fall die korrekten Summen ausrechnen können. Also ein Fall für triviale Geschäftslogik in einer separaten Assembly. So würde die Signatur der Funktion dazu aussehen:

public static void CalculateOrderTotals(DataSet orderToCalculate, Guid orderID)
{
    // Summen berechnen und in die entsprechenen Felder in den DataTables eintragen
    ...
}

Der Client nutzt diese diese Funktion für die schnelle und komfortable Berechnung und Darstellung der Summen. Die Geschäftskomponente auf dem Applikationsserver nutzt die Funktion um entgültigen Summen vor der Buchung zu berechnen.

Validierung der Daten sollte möglichst zentral auf dem Applikationsserver durchgeführt werden. Serverkomponenten sollten sich nicht darauf verlassen, dass der Client prüft und saubere daten sendet. Morgen wird für die Anwendung z.B. ein weiterer Client für den Pocket PC geschrieben. Vielleicht vergisst der Kollege vom Mobile Development Team eine bestimmte Eingabe zu prüfen. Dann hätte die Anwendung schon ein Problem. Geschäftsanwendung sollten robust sein.

Du sagst alles in SQL zu machen wäre am einfachsten. Das stimmt nur, wenn es sich um eine ganz einfache Anwendung handelt. Die meisten Anwendung beginnen einfach, werden aber immer komplexer. Viele Anwwendung leiden darunter, dass die Entwickler sich nicht für eine Architektur entschieden haben, die mit den zukünftigen Anforderungen mitwachsen kann. Diese Anwendung kommen alle früher oder später in den "Frickel-Modus". Über die Hälfte aller kommerziellen Geschäftsanwendungen haben dieses Problem (So sind meine Erfahrungswerte). Ich hatte dieses Problem auch schon mit eigenen Entwicklungen und habe daraus gelernt.

Es heißt immer, es sei schwierig, verteilte Anwendungen zu entwickeln. Ich meine, dass das in zeiten von Remoting und WCF nicht mehr stimmt. Man muss sich nur immer überlegen, was auf dem Server und was auf dem Client laufen muss. Und man darf nicht vergessen, dass Netzwerkzugriffe langsamer sind, als lokale Aufrufe. Deshalb muss man so sparsam wie möglich mit ihnen sein (Das aber, ohne auf dem Client das Frickeln anfangen zu müssen).

Hallo Rainbird,

Ich sehe schon dass es wie mit vielen anderen Sachen ist.
Große Begriffe und wie du auch schreibst sehr schwammig.

Nein, das ist mir schon klar das natürlich nicht alles und wenn überhaupt auf den Datenbankserver passieren soll. Obwohl dir diese auch gerne xml zurückgeben kann, wenn du es möchtest und darfst. Können doch alle großen DB's oder irre ich mich da.

Mir ging es bei meiner Ausführung nur um das grundsätzliche Verständnis der Frage, wo sollte oder kann eine Validierung der Daten im Context eines MVC-Windowsapplikation stattfinden.
Wie du schon schreibst, muß man sich halt vorher genau überlegen was eigendlich gefordert ist, um sich dann für eine Architektur festzulegen.

Danke und Gruß
falangkinjau

Xml

Ich habe nicht gemeint, Abfrageergebnisse als XML-Dokumente an den Client zurückzugeben (Das wäre mit SQL XML kein Problem). Ich habe die Automatisierung von Geschäftsprozessen gemeint. Eine Gespeicherte Prozedur wird nur schwer direkt mit anderen Anwendungen kommunizieren können.

Zukünftige Anwendungen bestehen aus vielen kleinen Teilen (Diensten/Komponenten), die über definierte Schnittstellen miteinander kommunizieren.

bug?

hallo rainbird.

ich mache ja noch viele fehler, bekomme diese aber eigentlich schon recht schnell mit hilfe des debuggers behoben, doch nun habe ich folgendes verhalten der anwendung:
im prinzip, habe ich auch erst eine lieferanten liste view( wie bei dir im beispiel) und öffne den Lieferanteneditor mit doppelklick(lieferant laden usw. )
das bearbeiten mit dem editor funktioniert. löschen auch. soweit ok.

wenn ich nun einen lieferanten mit doppelklick öffne, ist ja der butten für neu enable (lieferantDirty=false).
wenn ich den editor jetzt wieder schliesse, geht das auch, ohne die meldung "änderung wirklich verwerfen?", so wie es sein sollte.

jetzt kommts.

wenn das öffnen und schliessen, wie oben beschrieben öffters mache (egal ob es der gleiche lieferant ist oder unterschiedliche) ist irgendwann ( nach ca. 10mal aber nicht exakt feststellbar) der "neu" button disable und die form lässt sich auch nicht mehr schliessen, ohne das die meldung "änderung wirklich verwerfen?" erscheint. drücke ich "ja" erscheit das dialogfeld erneut, ich drücke wieder "ja", wieder kommt das dialogfeld usw... nach ca. 20mal(auch keine genauen werte ermittelbar) "verwerfen ja" drücken schließt sich die form wie gewünscht und ich kann weiter einen lieferanten auswählen, diesen bearbeiten oder löschen als sei nichts geschehen, bis zum nächsten fehlverhalten s.o. .

ich bin ratlos!!!!????

das verhalten ist übrigens nicht nur im debugger da, sondern auch ohne ide .

vielleicht hast du da ja einen verdacht was es sein könnte?!

ein letztes mal danke für deine hilfe, werds auch nicht mehr schreiben. Du kannst Dir meines dankes für jede antwort in zukunft sicher sein!

Dispose

Ich denke, dass die Formularinstanz einfach noch nicht vom GC abgeräumt ist. Kommt der Fehler auch, wenn Du Dispose, statt Close aufrufst?

Table

Hallo,

der verursacher war eine integer spalte in der datenbank, obwohl ich die werte in string convertiert hatte.
habe jetzt halt eine nvarchar spalte gemacht und muß später nochmal schauen, wie der fehler genau zustande kam.
mir ist nur nicht klar, warum es mal ging und mal nicht.
und vorallem warum keine fehlermeldung kam.
aber egal, lieber jetzt am anfang als später.

eine kurze frage hätte ich allerdings noch.

die speicherung erflogt bei dir im code:
(ausschnitt)

// Änderungen speichern
     _supplier=_supplierManager.SaveSuppliers(_supplier);

wobei du schreibst die table zurück geben wäre wichtig bei .Net Remoting

meine Lieferanten Table befindet sich aber in einem DataSet und ich bin noch nicht drauf gekommen wie ich diese mit der zurück gegebenen table überschreibe oder aktuallisiere. ich habe es folgendermassen gemacht:
(ausschnitt)

_lieferantTable =_lieferantenManager.SaveLieferant(_dsLieferant.Tables["Lieferant"]);
                _dsLieferant.Tables["Lieferant"].AcceptChanges();

wobei _lieferantTable eigentlich keine funktion hat, aber

_dsLieferant.Tables["Lieferant"] =_lieferantenManager.SaveLieferant(_dsLieferant.Tables["Lieferant"]); 

geht ja nicht. Wie komme ich an die eigentliche tabelle ran?
oder ist das so wie ich es gemacht habe mit AcceptChanges schon ok?

mfg luciluke

Parallelitätsverletzung

Hallo Rainbird,

ich muss meinem beitrag leider noch etwas erweitern.

folgendes problem:

ich erzeuge einen neuen lieferanten, gebe ein paar daten ein, speichere und schliesse den editor wieder. das funktioniert auch.
ich öffne den gleichen lieferanten, bearbeite ihn, speichere wieder und alles funktoniert wie erwartet.
nur wenn ich nach dem neu erstellen und einigen eingaben speichere, den editor nicht schliesse, weitere änderungen mache und dann versuche zu speichern, bekomme ich eine fehlermeldung "Parallelitätsverletzung".
ich hab mir jetzt schon fast alles zu diesem thema durchgelesen und komme zu keiner lösung.
vielleicht hat es ja etwas mit der fehlenden rückgabe der table beim update zu tuen, was ja auch ein problem für mich darstellt s.o. .
es wäre toll, wenn du mir da weiterhelfen könntest, da ich jetzt festgestellt habe, dass dieser fehler( wenn es denn einer ist) auch in deiner Beispielanwendung auftritt.

mfg LuciLuke

Prallelitätsverletzung

Du musst die Tabelle nach dem speichern neu Abrufen (damit Du die neuen Werte hast), sonst klappt das zweite Speichern eventuell nicht, da die Originalwerte der DataTable nicht mit der Datenbank übereinstimmen. Betroffen sind Autowert-Spalten, Spalten, die Erstellungs- und Änderungszeitpunkte speichern und alles, was nicht vom Client aus, sondern auf dem Applikations Server an der Tabelle geändert wird. Dieses Verhalten ist so beabsichtigt und verhindert, dass verschiedenen Benutzer im Multiuser-Betrieb sich gegenseitig ihre Änderungen überschreiben.

Deshalb gibt meine Save-Funktion auch eine DataTable zurück. Wenn Du ein DataSet benutzt, solltest Du auch eine separate Save-Funktion für dieses DataSet bauen. Wenn Du mehrere Tabellen speicherst, solltest Du das immer in einer Transaktion (Namensraum System.Transactions) machen.

edit:

Meine Anwendung ist nur ein Beispiel und bestimmt nicht fehlerfrei. Das ganze ist schnell zusammengezimmert und soll ja nur beispielhaft zeigen, wie eine MVC-Anwendung aufgebaut werden kann. Ich schau mir das aber bei Zeiten mal an und lade dann eine verbesserte Version hoch.

Mvc

Hallo Rainbird,

Ich habe das dataset nach einer neuanlegung komplett neu geladen und das klappt dann ja auch ohne probleme.

jetzt habe ich jedoch folgende verständniss frage:

mein dataset soll ja die kompletten daten für den einen ausgewählten Lieferanten halten. Adressen, Kontakte, und halt die ganzen zugehörigen zwischen Tabellen, wie LieferantAdresse usw.
Das Saven mach ich so wie du es empfohlen hast, in dem ich die einzelnen Table dem Adapter übergebe.


        // Speichert Änderungen an Lieferanten und Lieferantenadressen in einer Transaktion.
        public void SaveLieferantData(DataSet dsData)
        {
 
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            {
                using (SqlConnection connection = new SqlConnection(DBCONNECTION))
                {
                    connection.Open();
                    // Lieferant speichern
                    SaveLieferant(connection, dsData.Tables["Lieferant"]);
                    // Adressen speichern
                    SaveAdresse(connection, dsData.Tables["Adresse"]);
                    // Adress-Verknüpfungen speichern
                    SaveLieferantAdresse(connection, dsData.Tables["LieferantAdresse"]);
                 
                    connection.Close();
                    // Transaktion abschließen
                    scope.Complete();
                }
            }
         }

(ist das so mit der Transaktion eigentlich korrekt?)

wobei ich halt bei neuanlegung einer Lieferanten Adresse die Verknüpfungstabellen Daten im Code erstelle .

       public void CreateAdresse()
        {
            Guid _adresseGuid = Guid.NewGuid();
            DataRow _adresseRow = AdressenTable.NewRow();
            _adresseRow["AdresseID"] = _adresseGuid;
            AdressenTable.Rows.Add(_adresseRow);

            Guid _lieferantAdresseGuid = Guid.NewGuid();
            DataRow _lieferantAdresseRow = LieferantAdresseTable.NewRow();
            _lieferantAdresseRow["LieferantAdresseID"] = _lieferantAdresseGuid;
            _lieferantAdresseRow["AdresseID"] = _adresseGuid;
            _lieferantAdresseRow["LieferantID"] = LieferantID;
            _lieferantAdresseRow["AdresseTypID"] = AdresseTypTable.Rows[0]["AdresseTypID"];
            LieferantAdresseTable.Rows.Add(_lieferantAdresseRow);
            
            // im set von AdresseID wird AdresseLoaded gefeuert
            AdresseID = _adresseGuid;

        }

ist das so die richtige Vorgehensweise, oder könnte das dataset diese verknüpfungen mit hilfe der Relations selber erstellen?
es stellt sich mir die Frage, wofür die relations eigentlich gut sind, wenn ich alles im code selber steuere.
in einem anderen beitrag, hast du folgendes empfohlen Verweis
also auch z.B. die child und parent abfragen selbst zu erstellen(wenn ich das richtig verstanden habe), wobei diese daten bei meinem datset ja schon geladen wären.

Frage:
Sollte bzw. kann ich verknüpfte Tabellen Daten mithilfe der Relations erstellen,bearbeiten und löschen, oder sollte ich selber dafür sorgen, dass die daten stimmig sind?

wenn ich z.B. eine Adresse aus der Dataset.Adressen Table (sind ja beliebig viele je Lieferant) löschen möchte kann ich dies ja nicht über SaveieferantData machen , da die reihenfolge der Aktuallisierung ja dann nicht stimmt.
Schreibe ich dann einfach noch eine Methode DeleteData mit der umgekehrten Reihenfolge, oder gibt es da andere Möglichkeiten?

Wie Du siehst besteht bei mir eine grundlegende Verständniss Frage, wie ich das mit den Aktuallisierungen angehe.
Mit Relations und Constraints arbeiten, oder selber die Daten "Verwalten"?

Ich hoffe, mein Problem einigermassen klar dargelegt zu haben und wäre für eine Entscheidungshilfe sehr dankbar.

mfg luciluke

ps.: ich wollte bestimmt keine kritik oder so üben, falls es sich in meinem vorherigen Beitrag so angehört haben sollte.

Logik

Relations bei DataSets können Dir helfen, bereits bei der Eingabe der Daten immer konsistent zu bleiben. Außerdem bieten sie einen gewissen Komfort. Was aber eigentlich zählt, ist was hinterher in der Datenbank steht.

Dass z.B. beim Löschen einer Adresse automatisch auch vorher die Verknüpfungen zu den Lieferanten gelöscht werden, ist Geschäftslogik. Mittels DataSet sollte man das nicht abbilden, da ein DataSet nur ein passiver Datentransportcontainer ist. Die Save-Methode muss das sicherstellen. Ich würde eine entsprechende Lösch-Logik in einer privaten Methode kapseln und diese dann in der Save-Methode aufrufen. z.B.:

private void CheckForAddressDeletions(DataSet supplierData) ...

Es stellt sich allerdings die Frage, ob die Datenbank-Struktur so überhaupt glücklich gewählt ist? Ein Lieferant hat n Adressen (1:n Beziehung). Eine Adresse kann doch nicht mehreren Lieferanten zugeordnet werden (n:m Beziehung). Wenn ich eine löschen würde, hätte möglicherweise ein anderer Lieferant gar keine Adresse mehr, wel er mit der gelöschten verknüpft war. Wenn zwei Firmen im selben Gebäude sind, würde ich lieber zwei Adress-Datensätze anlegen.

Um die Daten nach dem speichern neu abzurufen kannst Du auch einen einfacheren Weg gehen:

public DataSet SaveLieferantData(DataSet dsData) ...

Speichern und zurückgeben des "abgesegneten" DataSets geht am besten in einem Methodenaufruf.

Was die Transaktionen betrifft: Lass das mit der Connection. Das ist vollkommen überflüssig. Du kannst in jeder Save-Methode eine eigene Connection aufmachen. Es könnte sogar eine Connection zum SQL Server A, eine zum SQL Server B und eine zum Oracle Server C sein. Der Distributed Transaction Coordinator Dienst (kurz DTC) kümmert sich darum (Er redet direkt mit den einzelnen Datenbank Servern; Wenns sein muss sogar über XA mit Unix-Systemen), dass alle Datenbankserver an der selben Transaktion teilnehmen, wenn Du System.Transactions verwendest. Deshalb nennt man dieses Konzept "Verteilte Transaktionen". Es spielt keine Rolle, ob die Transaktion über eine Datenbank auf einem Server oder zwanzig Datenbanken auf zwanzig verschiedenen Servern geht. Wenn Du auf verteilte Transaktionen aufbaust, hast Du auch dann keine Probleme, wenn Deine Anwendung mal von 5000 Benutzern gleichzeitig bedient wird, die Daten aus drei verschiedenen SQL Servern verwenden. In einem solchen Fall kannst Du Dich dann entspannt zurücklehnen und Dich freuen, dass Du an Deiner Transaktionslogik nicht eine Zeile ändern musst. Die vorhandene Windows Infrastruktur nimmt Dir alles ab.
System.Transaction hat nix mit normalen ADO.NET Transaktionen (System.Data.SqlClient.SqlTransaction) zu tun.

Achtung! Ich gehe davon aus, dass die Geschäftslogik (z.B. die SaveLieferantData-Methode) auf einem Applikationsserver liegen, wie in meinem Beispiel. Wenn die Geschäftslogik auf dem Client läuft (Also im GUI-Prozess), könntest Du hinterher einen erhöhten Konfigurations- und Deployment-Aufwand haben, da auf jedem Client der DTC laufen, und auch entsprechend konfiguriert sein müsste. Bei Einsatz eines Applikationsservers muss nur dort der DTC laufen. Bei einer 2-Tier-Architektur (Windows-Forms-Client greift direkt auf SQL Server) ist von System.Transactions Abstand zu nehmen.

Reporting

Hallo Rainbird,

ich habe jetzt soweit alles mit den kunden, liefernaten, adressen usw. im griff und wollt mich erstmal nochmals herzlichst bedanken für deine immer sehr schnellen und hilfreichen tipps zu meinen fragen.

allerdings hätte ich noch zwei fragen zur zeit.
1.
ich habe ja die kunden und lieferanten in der datenbank in getrennten tabellen, da es einige unterschiede gibt und dies später für die buchhaltung wohl auch besser ist(kreditoren, debitoren).
nun habe ich dies mit zwei fast identischen datasets und den dazugehörigen, auch fast identischen editoren reallisiert.
ist das so OK, oder sollte man bei ähnlichen datenstrukturen irgendetwas mit vererbung oder so verwenden (damit hatte ich mich noch nicht beschäftigt und habe deshalb direkt obigen weg genommen)?
2.
die anwendung soll mit hilfe den noch hinzukommenden artikel, artikelgruppe, vorgang Tabellen, rechnugen lieferscheine und angebote ermöglichen.
frage:
womit erstelle bzw. drucke ich z.b. eine rechnung?
ich hab schon einiges über crystal reports usw. gesehen und habe mich auch schon kurz mit dem reportig service beschäftigt, wobei mir dieser auf den ersten blick zu aufwendig für diese aufgabe erschien.
wie archiviere ich die vorgänge, sodass sie bis zu einem gewissen zeitraum noch änderbar sind, danach aber keine beziehungen mehr zu den artikel,kunde usw. tabellen mehr haben, damit man nicht ewig alte artikel usw. nicht löschen kann?
ich habe mir das so vorgestellt, das man z.b. nach vollständigem zahlungseingang einen schluß für alle einzelvorgänge (angebot, lieferschein, rechnug) eines vorganges macht und die zu diesem zeitpunkt bestehenden formulare als pdf archiviert.
ist meine überlegung sinnvoll, oder wie sollte man es anders machen?
in den anwendungen, die ich mir testweise mal angeschaut habe, bleiben meistens die verknüpfugen bestehen, oder werden dann wahlweise irgendwie umgeschrieben, damit man überhaupt änderungen an artikeln vornehmen kann.
es würde mich sehr interessieren, wie du dieses lösen würdest bzw. schon mal gelöst hast.
-die anwendung soll auf kurze sicht nicht für bilanzen gewinn/überschuß rechnug oder ähnliches genutzt werden, sondern wirklich nur bestellungen, angebote lieferscheine rechnugen erstellen und verwalten-

frohe weihnachten schonmal an alle
von luciluke

ps.: das mit dem dataset in einem zurückgeben, habe ich noch nicht geschafft.
ich baue aber das dataset auch im model aus tabellen zusammen die ich einzeln hole (z.b. die lieferanten daten über den liefernatenmanager und die dazu gehörigen adressen über den adressenmanger), oder sollte das dataset im, z.b lieferantenmanager erstellt werden, um dann komplett abgerufen zu werden? dafür müßten jedoch die einzelnen "manager" untereinander zugriff haben.
ist dies so üblich und besser?
-die anwendung ist von der struktur her so aufgebaut wie in deinem beispiel-

Beziehungen

zu 1: Ich würde das komplett ohne vererbung machen und lieber alle Klassen, Controls etc. "doppelt" (also einmal für Lieferanten und einmal für Kunden) bauen. Dann können beide Entitäten völlig unabhängig voneinander weiterentwicklet werden.

zu 2: Du kannst einen Artikel nicht mehr löschen, sobald er einmal verkauft oder eingekauft wurde (Zumindest möchte ich Dir das nicht empfehlen zu tun) . Genauso ist es mit Kunden und Lieferanten. Es darf keine Vorgänge ohne verknüpfte Firma geben. Stattdessen legt man ein bit-Feld an, welches Datensätze als "archiviert" markiert. Diese archivuerten filtert man bei allen angezeigten Listen weg, dann stören sie niemanden mehr.

Bei Vorgängen solltest Du alle Daten kopieren (Adresse, Kunden-Nr, Artikeltexte, Artikelnummern, Preise etc.), aber trotzdem auch die Schlüssel speichern. Versuch Dokumentenorientiert zu denken. Eine Rechnung oder ein Lieferschein ist ein Dokument. Der Kopdatensatz und die Positionsdatensätze speichern die Daten des Dokuments. Alle Daten eines solchen Dokuments sollten dort enthalten sein.

Wenn eine Firma umzieht, darf sich z.B. die Rechnungsadresse alter Rechnung nicht einfach ändern. Wie sollte man sonst nachvollziehen können, an welche Adresse die Rechnung tatsächlich gesendet wurde, ohne an die Papierordner zu gehen (ERP-Software soll das wühlen in papier-Archiven überflüssig machen und nicht auch noch fördern) Bei steuerlich relevaten Dokumenten (Rechnungen/Gutschriften) solltest Du am besten noch ein Datei-Archivierung (z.B. als PDF) vorsehen. Dann hat man eine digitale Kopie des Originals und kann das ohne erneutes Rendering am Bildschirm anzeigen. Außerdem braucht man die PDF-Ausgabe, um Dokumente per Mail zu versenden.

Wenn Du einen zerntralen Applikationsserver (Remoting/WCF) hast, empfehle ich Dir dort auch gleich eine Historie-Funktion einzubauen, die alle Änderungen mit Zeitpunkten und Benutzern in eine Tabelle wegschreibt. Die lückenlose Nachvollziehbarkeit aller Transaktionen ist in einem ERP-System eine wichtige Sache.

Für statische Dokumente (Rechnungen, Lieferscheine, Etiketten etc.) solltest Du Crystal Reports nehmen.

Für Auswertungen und Controlling-Berichte (die auch interaktiv sein können) solltest Du SQL Server Reporting Services verwenden. Das eingebaute Rechtesystem der SQL Reporting Services schützt z.B. Berichte und Auswertungen der Geschäftsleitung vor neugierigen Augen. Außerdem kann man schnell neue Berichte auf dem Server veröffentlichen, ohne dass man den Client ändern und neu verteilen müsste. Berichte sind sofort einsatzbereit, sobald sie hochgeladen wurden.

Du kannst auch alles mit SQL Reporting Services machen. Bei komplexen statischen Berichten hat aber Crystal eindeutig die Nase vorn. Beim Etikettendruck würde ich allerdings sehr von SQL Reporting Services als Berichts-Engine abraten, da das redern auf dem Server und die Übertragen einfach zu lange dauert. Etiketten müssen sofort aus dem Etikettendrucker rauschen, da es bei Lagerprozessen etc. unter anderem auch um Geschwindigkeit geht.

Wo sollen nun die Eingaben kontrolliert werden?

Hallo,

ich verfolge den Thread nun schon länger, aber eine Frage blieb bei mir immer noch offen - auch wenn ihr schon weiter seit mit euren Gedanken.

Wo kontrolliere ich jetzt die Eingaben? Also wo begrenze/überprüfe (letzteres um zu verhindern das in Strings irgendwelcher SQL Code steht) ich diese oder überprüfe auf Logik?

Des weiteren Stellt sich für mich die Frage wo berechne/?? ich Eingabe Vorschläge, also z.B. in meinem Projekt gibt man für wissenschaftl. Versuche Daten ein, daraus errechnet sich ein neuer Wert der auch in eine Datenbank soll. Schreibe ich dafür ein Extra Event ins Model, also Wert berechnet und die View fragt diesen ab?

Wäre nett wenn ihr oder einer das nochmal erklären könnte.

MfG Florian

// no comment

SQL Parameter

Hallo seikeinfloh

wie du sicherlich mitbekommen hast, bin ich hier nicht der experte, aber ich würde gerne helfen, wenn die fragen konkreter wären. allgemeine vorgehensweisen, wie der datenbank zugriff mit hilfe von sql parametern sind ja schon auf der ersten seite dieses Threads mit code gezeigt worden.
Auch die frage wo die eingaben überprüft werden sollten wurde hier schon ausgiebig diskutiert. mein fazit daraus war, das es keine eindeutige "vorschrift" sondern nur empfehlungen, mit für und wieder dazu gibt.
mir persönlich hat rainbirds satz
zitat:
"Was aber eigentlich zählt, ist was hinterher in der Datenbank steht. "

mit am besten geholfen meine ständigen überlegungen, ob dies nun der "beste" weg sei oder nicht, teilweise einzustellen.
grundlegende vorgehensweisen (wie z.b. sql parameter, transaktions, allgemeine regeln von MVC usw.) berücksichtige ich natürlich auch, aber wenn etwas funktioniert und schnell ist, was soll dann daran allzu verkehrt sein. den code zu perfektionieren (falls nötig) habe ich auf das ende verlegt, da ich dann auch weiß was ich überhaupt alles benötige und wärend des schreibens des programmes stösst man eh immer wieder auf neue sachen, die man dann für verbesserungen benutzen kann.
aus diesem grund hab ich auch schon länger nichts mehr gepostet, da sich vieles von selber erledigt, wenn man eine so reichhaltige einführung, wie in diesem thread bekommen hat.

wie gesagt ich würde gerne helfen, da auch mir hier sehr geholfen wird, aber als ich was schreiben wollte, habe ich festgestellt, dass eigentlich zu allen deiner fragen, so wie ich sie verstanden habe, hier schon sehr viel geschrieben wurde und ich nichts hinzufügen könnte ausser verweise auf bereits gesagtes.

in der hoffnung nichts falsches geschrieben zu haben

luciluke

Original von seikeinfloh
Wo kontrolliere ich jetzt die Eingaben? Also wo begrenze/überprüfe (letzteres um zu verhindern das in Strings irgendwelcher SQL Code steht) ich diese oder überprüfe auf Logik?

Du solltest IMMER Parameter (SqlParameter, etc.) verwenden, um Werte mit einer SQL-Anweisung mitzugeben. Dann musst Du nicht überprüfen, ob SQL-Code in den Werten steht. Parametrisierte SQL-Abfragen sind außerdem performanter, da Parameter die wiederverwendung des Ausführungsplans im SQL Server begünstigen.

Daten vom Client sollten vor dem Speichern in die Datenbank auf Richtigkeit gepürft werden. Die Frage, wo Daten geprüft werden, ist nicht so eindeutig zu beantworten. Eigentlich gehört das zur Geschäftslogik und sollte deshalb auch in den Geschäftslogik-Klassen gemacht werden. Zum Zwecke der Benutzerfreundlichkeit möchte man bestimmte Dinge sofort (also bereits auf dem Client) prüfen. Wenn der Benutzer 20 Felder ausfüllen muss und erst am Ende beim klicken auf "Speichern" mitgeteilt bekommt, was er alles falsch gemacht hat, ist das frustrierend. Die Prüfung darf aber keines Falls alleine dem Client überlassen werden. Es könnte ja auch verschiedene Clients (Windows, Web, Mobile) geben. Alles doppelt implementieren ist aber auch nicht der Knaller. Hinterher sind die Prüfregeln an verschiedenen Stellen unterschiedliche implementiert.
Ich habe dieses Problem in meinen Projekten so gelöst: Die Prüfregeln habe ich in statische Methoden einer Helper-Klasse gepackt und in eine separate Assembly gesteckt. Diese Tool-Assembly kann nun vom Client (Benutzeroberfläche) und Server (Geschäftslogik-Komponenten die in einem separaten Prozess laufen) gleichermaßen verwendet werden. Wichtig ist, dass man genau definiert, was Prüfregeln sind und was nicht. Die erwähnten statischen Funktionen dürfen nur mit den übergebenen Parametern arbeiten und NIEMALS Datenbankzugriffe machen oder Dienste aufrufen.
Zum leichteren Verständnis ein Beispiel:


/// <summary>
/// Stellt Funktionen zur Validierung von Kontaktdaten zur verfügung.
/// </summary>
public class ContactValidationHelper
{
    /// <summary>
    /// Privater Standardkonstruktor.
    /// </summary>
    private ContactValidationHelper() {}

    /// <summary>
    /// Prüft eine Postadresse auf Gültigkeit.
    /// </summary>
    /// <param name="street">Straße</param>
    /// <param name="city">Ort</param>
    /// <param name="zip">PLZ</param>
    /// <param name="country">Land</param>
    /// <returns>Wahr wenn ok, ansonsten Falsch</returns>
    public static bool CheckPostalAddress(string street, string city, string zip, string country)
    { 
        // Post-Adresse prüfen ...
    }

    /// <summary>
    /// Prüft eine E-Mail-Adresse auf Gültigkeit.
    /// <remarks>
    /// Es wird nur das Eingabeformat geprüft. Ob die E-Mail-Domäne tatsächlich
    /// existiert muss die Geschäftslogik via DNS-Lookup sicherstellen.
    /// </remarks>
    /// </summary>
    /// <param name="eMailAddress">E-Mail-Adresse</param>
    /// <returns>Wahr wenn ok, ansonsten Falsch</returns>
    public static bool CheckEMailAddress(string eMailAddress)
    { 
        // E-Mail-Adresse prüfen ...
    }
}

Original von seikeinfloh
Des weiteren Stellt sich für mich die Frage wo berechne/?? ich Eingabe Vorschläge, also z.B. in meinem Projekt gibt man für wissenschaftl. Versuche Daten ein, daraus errechnet sich ein neuer Wert der auch in eine Datenbank soll. Schreibe ich dafür ein Extra Event ins Model, also Wert berechnet und die View fragt diesen ab?

Mit Berechnungen verhält es sich ähnlich. Die endgültige Berechnung sollte NIEMALS der Client (Sprich die Oberfläche) durchführen (Das Model ist auch Teil der Oberflächen-Schicht). Wenn die Berechnung aber in einer Assembly gekapselt ist, können Client und Server die selbe Berechnung verwenden. Der Client kann so während der Eingabe der Daten schon Zwischenergebnisse anzeigen (Das ist z.B. auch bei der Auftragserfassung sehr hilfreich).

Ob Views solche Helper-Klassen direkt verwenden dürfen oder über das Model gehen müssen, ist Ansichtssache. Ich halte die Views möglichst schlank und würde den Aufruf über das Model zugänglich machen. Bei Web-Anwendungen ist es zudem geschickt.

Nur als frage, warum implementieren deine Businessobjecte nicht die Validierung
selber, und ausserdem IDataErrorInfo?

Dann ist alles an der Stelle an die es gehört, nur einmal vorhanden, und
ist durch die std. Mechanismen beim Binden ( z.B. ErrorProvider ) einfach
zu behandeln.

Und dann das Validieren noch mit Attributen vorbereiten ( wie z.B. herbies Artikel zeigt )
und schon geht das ziemlich schnell.

Hi,

ich verfolge diesen Thread schon von Anfang an und bin grade dabei
den MVC-Ansatz von Rainbird in einen unserer Projekte zu verwenden.
Nun bin ich an den Punkt der Prüfung (Validierung) von Benutzereingaben
angelangt und bin mir nach einigen Überlegungen nicht im klaren an welche Stelle
nun eine Prüfung am günstigsten ist. Speziell geht es im Moment um einfache
Abfragen, z.B. das in einem bestimmten Feld eine Eingabe erfolgen muss.
An welcher Stelle sollte eine Prüfung erfolgen?
Vorstellen könnte ich zwei Möglichkeiten:

  1. In der Businesslogik und werfen von Exceptions
  2. Im Model und dem feuern eines z.B. ValidationErrorEvents

Was ist eine gängige Vorgehensweise für diesen Problemfall?

Vielen Dank!

Hallo Rainbird,
du hattest mal in einem deiner Antworten folgendes erwähnt:

Original von Rainbird

Meine Anwendung ist nur ein Beispiel und bestimmt nicht fehlerfrei. Das ganze ist schnell zusammengezimmert und soll ja nur beispielhaft zeigen, wie eine MVC-Anwendung aufgebaut werden kann. Ich schau mir das aber bei Zeiten mal an und lade dann eine verbesserte Version hoch.

Bist Du mal zu der verbesserten Version gekommen? Ich denke es würde einige sehr interessieren. Mich eingeschlossen natürlich.

Noch mal aufs Model Konzept zurück zukommen.

]Original von Rainbird
Angenommen ich will ein CRM-System bauen, welches Kunden, Angebote und Projekte verwalten kann. Dazu würde ich zunächst drei Model-Klassen in drei unterschiedlichen Assemblies (DLLs) erstellen. Die könnten z.B. so heißen:

CustomerManagement
OfferManagement
ProjectManagement

Wie würde das Hauptmodel anhand von deinem MVC Beispiel aussehen?

Krischan100

Geschäftslogik

@FZelle:
Normalerweise machen die Business-Objekt die Validierung selber. Es gibt aber Szenarien, in denen der Client bereits Validieren muss (Und sei es nur, um den gewünschten Komfort zu bieten). Meine Business-Objekte laufen natürlich auf einem Applikationsserver. Ich möchte für triviale Dinge, wie das ausrechnen einer Zwischensumme oder die Prüfung, ob alle Muss-Felder eingegeben wurden, nicht extra einen Netzwerkzugriff haben. Das führt sonst schnell dazu, dass bei jedem zweiten Klick ein Netzwerkzugriff gemacht wird. Das ist nicht sinnvoll. Deshalb lagere ich in solchen Fällen die Validierung aus, damit sie auch vom Client direkt verwendet werden kann.

@Chaosfreak:
Vielleicht macht es auch in Deinem Projekt Sinn, die Validierung in eine separate Tool-Bibliothek auszulagern, die von Geschäftslogik und Model referenziert wird. Die Endgültige Validierung sollte aber IMMER die Geschäftslogik machen.

@krischan100:
Ich bin noch nicht dazu gekommen, das MVC-Beispiel zu überarbeiten. Sobald ich eine neue Version fertig habe, stelle ich sie hier zur Verfügung.

Zum Thema Hauptmodel:

Ich würde die drei Module (CustomerManagement, ProjectManagement und OfferManagement) via Microkernel zu Laufzeit laden. Damit wäre die Anwendung unbegrenzt Erweiterbar (Um z.B. ein Model SalesReporting einzubauen, wären nur ein paar Einträge in der App.config nötig). Eine solche Anwendung benötigt ein Hauptfenster. Die Views der einzelnen Module kann man dann wunderbar als MDI-Childs (oder im Visual Studio-Style in Registerkarten) laufen lassen. Das Hauptmodel würde sich um das Laden und Integrieren der einzelnen Module kümmern. Das Hauptfenster könnte eine Navigations-Bar (a la Explorer oder Outlook) haben und natürlich Menüs und Symbolleisten anbieten. Die Logik der Navigation und das erweitern von Menüs sowie das abblenden von inaktiven Funktionen muss schließlich irgendwo implementiert werden. Außerdem müssen irgendwo die Instanzen der geöffneten Kunden- und Projektfenster gehalten werden. Das wäre für mich die Aufgabe das Hauptmodels. Zusammen mit dem Hauptfenster würde es die "Plattform" für die CRM-Anwendung bilden. Auch das Login am Applikationsserver (Falls denn dort eine Sitzungverwaltung implementiert ist) kann das Hauptmodel zentral durchführen (Sonst muss sich ja jedes Model darum selbst kümmern).

Das Problem ist, dass Business-Software immernoch viel zu starr programmiert wird. Die Anwendungen lassen oft zu wenig Freiraum für Erweiterungen und/oder Anpassungen. Mit MVC und einer Mikrokernel-Implementierung (also ein paar App.Config-Eintröge und ein bisschen Reflection) lassen sich flexible und komfortable Business-Anwendungen zusammenzimmern. Ich habe damit durchweg gute Erfahrungen gemacht.

Ich weiss was Du meinst, aber 90% der entwickleten SW arbeitet nicht
mit Appservern ( und braucht sie auch nicht ), weshalb das nur dann bedacht werden muss.

Und schon mal CAB angeschaut?

App-Server

Ich hab mit CAB auch schon angesehen. Das ist eine feine Sache. Macht im Prinzip genau das, was ich hier in diesem Thread selber gebastelt habe. Mir ist dabei aber zuviel vorgegeben. Wen das nicht stört, findet in CAB bestimmt eine solide Plattform. Ich möchte das aber lieber selber schreiben. Das soll nicht heißen, dass ich generell alle Infrastruktur selbst schreibe. Ich nutze gerne Office, WCF, Remoting und den BizTalk Server (Ein Parade-beispiel für fertige Infrastruktur).

Du hast natürlich Recht. Ein App-Server macht nur dann Sinn, wenn Personenübergreifende Geschäftsprozesse zentral gesteuert und überwacht werden sollen/müssen. Bei 90% der Anwendungen ist das nicht der Fall. Aber bei den Anwendungen, mit denen ich mein täglich Brot verdiene ist das ein wichtiger Punkt. Deshalb muss jetzt nicht jeder einen App-Server aufsetzen, wenn er ein paar Kundenadressen aus einer Datenbank lesen will.

Business-Anwendungen werden zukünftig immer mehr "untereinander" Kommunizieren. Bei einer klassischen Business-Lösung mit fettem Windows-Front-End und einem dicken Datenbank-Server klappt das aber nicht. Das Front-End "unterhält" sich ja nur mit Menschen. Alles andere muss dann direkt mit der Datenbank reden. Was ist aber mit den Geschäftsregeln? Die gelten doch auch, wenn Automatisiert wird, nicht nur bei Benutzereingaben. Wenn die Geschäftsregeln aber in der Client-EXE implementiert sind, ist nix mit erzwingen und monitoren von Geschäftsregeln. Entweder sie werden nochmal implementiert (momentan sind ja Webservices ganz groß) oder sie müssen alle von der Datenbank durchgesetzt werden (Stored Procedure und Trigger-Salat). Oder man lässt sie in einem Applikations-Server laufen. Dann hat man wirklich die Kontrolle über die Geschäftsprozesse.

Die meisten Business-Anwendungen die ich kenne sind einfach ungenügend automatisierbar. Menüs und Formulare anpassen können sie alle. Es fehlen aber die Maschine-zu-Maschine-Schnittstellen. Wie werden heute denn Geschäftsprozesse umgesetzt, die mehrere Anwendungen betreffen? Meistens mit CSV-Imports, Polling und direkten ODBC-Zugriffen auf die Datenbanken.
Bei der Automatisierung mit externen Partnern sieht es nicht besser aus.
Wo sind die ganzen Webservices? Welcher Logistikdienstleister bietet einen Webservice an, um eine Sendung aufzugeben? Welche Bank bietet einen Webservice, um die gültigen Wechselkurse in-Time abzurufen? Alle stellen sie Tools mit Windows- oder Weboberfläche zur Verfügung, wo Menschen alles von Hand eingeben können. Mit Automatisierung ist da nicht viel.

Ich predige deshalb gerne ein bischen "App-Server". In der Hoffnung dass es mehr Geschäftslogik-Komponenten gibt, die nicht mit einer Benutzeroberfläche verheiratet sind und explizite Schnittstellen für die Kommunikation haben und somit echte Automatisierung in den Firmen ermöglichen. Nicht "Alles aus einer Hand", sondern eher Lego-Prinzip. Bei Steuerelement klappts doch auch, warum nicht bei Geschäftslogik?

Nochmal auf das Thema mit dem MicroKernel zurückzukommen. Kannst Du ein gutes Beispiel nennen, wo man sich solch eine Realisierung anschauen kann?
Wenn ich es Richtig verstanden habe, dann kommt die Grundlegende GUI(das Mainfenster (MDI usw.) in den MicroKernel mit rein bzw. der MicroKernel ist in meiner MasterModel Klasse!? Habe ich es bis dahin Richtig verstanden oder lieg ich total daneben?

Krischan100

@Rainbird:
So unflexible ist CAB garnicht 😉

Also ich möchte die Commands,Events und Services nicht mehr missen.
Und natürlich kann man auch hier einen Appserver benutzen.

Und mit den Webservices hast Du auch recht.
Ich habe mal vor Jahren ein Projekt gemacht, wo die Transportlogistiker in
einer gemeinsamen Oberfläche für Sendungsverfolgung abfragbar sein sollten.

@krischan100:
Schau Dir doch auch ruhig mal CAB an:
www.codeplex.com/smartclient
oder
www.cabpedia.com

Altlasten

Original von FZelle
So unflexible ist CAB garnicht 😉

Es ist bestimmt super. Das hab ich ja auch gesagt. Ich muss mich aber z.B. momentan noch oft mit COM herumschlagen.
In meinem aktuellen Projekt habe ich ein gigantisches Access 2003-Formular welches nicht ohne weiteres komplett auf .NET umgestellt werden kann. Also stell ich es eben teilweise auf .NET um. Das Model und ein Teil der Views sind in C# geschrieben, andere Views sind Access-Formulare, die über einen zwischengeschalteten COM-Wrapper mit dem Model reden. Die alten Access-Forms und ActiveX-Controls werden Stück für Stück durch .NET Forms und UserControls abgelöst, die alle mit meinem schönen, sauberen .NET Model reden. Das Model redet wiederum mit dem App-Server.

Wenn ich eine neue Anwendung "from-the-scratch" hochziehen würde, wäre CAB natülich auch für mich eine Überlegung wert. Erfahrungsgemäß hat man aber selten die Möglichkeit alles neu zu machen (Zumindest bei umfangreichen Anwendungen).

Was selbst gebautes passt eben immer genau.

MVC - Geschäftslogik

Hi,

[offtopic: hatte etwas Stress in letzter Zeit - wahrscheinlich auch etwas länger noch - deshalb konnte ich mich erst jetzt wieder damit beschäftigen.]

Hab ein Beispiel im Internet gefunden was mir geholfen hat, hier mal der Link:

http://www.microsoft.com/germany/msdn/library/vs2005/samples/default.mspx

Vielleicht wurde das schon mal gepostet aber denke das das nicht schadet zweimal zu schreiben. Es zeigt sehr schön wie man das ganze in mehrere Schichten aufteilen kann die dann zusammen erst das ganze Problem lösen.

Ich denke die Lösung wird sein - wie Rainbird es geschrieben hat, eine Hilfsklasse zum Berechnen schreiben, die dann beide Teile, Server - Client, benutzen können.

MfG Florian

// no comment

TransactionScope Fehlerbehandlung

hallo, ich hätte da mal folgende fragen, da ich auch nach längerem suchen wenig über die benutzung von transaktionscope gefunden habe(ausser bei ms).

Die Aktuallisierung meiner Datenbank (sql2005 express) mache ich wie folgt bei allen relevanten Datasets (z.B. Lieferante, Kunde usw.).
wobei die Datasets immer nur einen datensatz enthalten, mit den dazugehörigen detail tabellen. also nicht alle kunden oder alle artikel.

Model.cs:(verkürzter Code)


        public void SaveArtikelChanges()
        {
            // Wenn Eingaben zum speichern anstehen ...
            if (IsArtikelDirty)
            {
                DataSet _dsChanges = DsArtikel.GetChanges();
                _artikelManager.SaveArtikelData(_dsChanges);
                LoadDsArtikel();
                //Flag zurücksetzen
                _artikelDirty = false;

                if (ArtikelSaved != null)
                    // Ereignis feuern
                    ArtikelSaved(this, null);
            }
        }


hierzu eine zwischenfrage: ist das mit _dsChanges so ok, oder sollte ich das gesamte dataset übergeben, dann könnte ich es auch wieder zurückgeben?
dieses problem hatte ich ja schon in meinem vorletzten beitrag erwähnt.

ArtikelManger.cs


        public void SaveArtikelData(DataSet _dsChanges)
        {
            using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required))
            {
                if (_dsChanges.Tables.Contains("LieferantTyp"))
                {
                    //Die Kontrolle könnte auch SaveConfigTable übernehmen
                    if (_dsChanges.Tables["LieferantTyp"].Rows.Count != 0)
                    {
                        SaveArtikelTable(_dsChanges.Tables["LieferantTyp"]);
                    }
                }
                usw. für weitere Tabellen im DataSet mit Namen "X"
                if (_dsChanges.Tables.Contains("X"))
                {
                    if (_dsChanges.Tables["X"].Rows.Count != 0)
                    {
                        SaveArtikelTable(_dsChanges.Tables["X"]);
                    }
                } 
                // Transaktion abschließen
                scope.Complete();
            }
        }

        private void SaveArtikelTable(DataTable table)
        {
            using (SqlConnection connection = new SqlConnection(DBCONNECTION))
            {
                connection.Open();
                // Änderungen an Lieferanten speichern
                DBAccess.UpdateDataTb(connection, table);
                connection.Close();
            }
        }

dbAccess.cs


        internal static void UpdateDataTb(SqlConnection connection,DataTable table)
        {
            // Datenbankbefehl für Schemaabruf erzeugen 
            SqlCommand command = new SqlCommand(string.Format("SELECT *   FROM {0} WHERE 0=1", table.TableName), connection);

            // Datenadapter erzeugen
            SqlDataAdapter adapter = new SqlDataAdapter(command);
            
            // Befehlsgenerator erzeugen
            SqlCommandBuilder builder = new SqlCommandBuilder(adapter);
            
            // Änderungen speichern
            adapter.Update(table);

            // Ressourcen freigeben
            builder.Dispose();
            adapter.Dispose();
            command.Dispose();             
       }

1.:
ist das so mit using TransaktionScope und nochmal using sqlconnection in ordnung?
2.:
darf bzw. sollte ich während der TransaktionScope anweisung try catch blöcke benutzen?

daraus resultierend 3.: mehrere fragen
wie behandele ich einen fehler innerhalb der using TransaktionScope anweisung, wenn z.B. ein update nicht möglich ist, weil ein anderer user schon daten in der betreffenden zeile verändert hat, bzw. muß ich die überhaupt behandeln, da so wie ich es verstanden habe ein rollback ausgeführt wird wenn ein fehler aufgetreten ist.
ist dieser rollback 100% sicher, oder sollten noch weitere maßnahmen nach einem mißglückten update erfolgen?
wie und wo kann ich wenn diese fehler auswerten, um den user zu informieren?

eigentlich möchte ich nur sicherstellen, dass auch unvorhersehbare fehler (die eingaben des users und die zuordnung der detailtabellen usw. werden natürlich vorher geprüft) beim update nicht zu einem fehler in der datenbank führen kann, so das diese nicht mehr reagiert.

mfg luciluke

Fehler innerhalb von Transaktionen behandeln

Hallo luciluke,

Dein TransactionScope und die using-Blöcke sind völlig korrekt. GetChanges ist okay, wobei ich meistens das ganze DataSet übertrage. Es ist eine Frage der Größe des DataSets.

Try-Catch-Blöcke solltest Du einsetzen, wenn Du bestimmte Ausnahmen erwartest und diese kontrolliert serverseitig (also innerhalb Deiner Geschäftslogik) handhaben willst. Bei einer unbehandelten Ausnahme, wird die Complete-Methode ja nicht aufgerufen, da die Ausführung sofort unterbrochen wird. Das Rollback ist 100%ig sicher und wird ausgeführt, wenn ein TrasactionScope-Block verlassen wird, ohne Complete aufzurufen.

Der Client muss natürlich über fehlgeschlagene Transaktionen informiert werden. Standardmäßig werden aufgetretene Ausnahmen an den Client weitergegeben. Die Meldungstexte sind aber nicht unbedingt Endbenutzer freundlich. Hier können Try-Catch-Blöcke für mehr komfort sorgen. Die Geschäftslogik kann eigene Ausnahmenklassen zurückgeben, die detaillierte Informationen über den Fehler enthalten (z.B. im Falle einer Parallelitätsverletzung: Welcher Benutzer hat welche Änderungen zuerst geschrieben?). Diese eigenen Ausnahmen kann der Client gut auswerten und dem Endbenutzer gleich Lösungen anbieten (z.B. die Änderungen des anderen Benutzers und die eigenen über einen Dialog manuell zusammenführen). Ob so eine, doch recht aufwändige, Merge-Funktion sinnvoll ist, kommt darauf an, wie häufig Parallelitätsverletzungen in Deiner Anwendung vorkommen.

Bei Rollbakcs kannst Du Dir sicher sein, dass alle Tabellen wieder den Stand von vor der Transaktion haben. Sämtliche UPDATEs, DELETEs und INSERTs die innerhalb der Transaktion ausgeführt wurden, sind nach einem Rollback rückgängig gemacht. Die einfachste Lösung für Ausnahmen sieht so aus:*Benutzer über den Fehler informieren und warum dieser aufgetreten ist (z.B. über eine MessageBox) *DataSet neu abrufen, damit der Client die aktuellen Stand der Daten hat

Bei diesem Lösungsansatz muss der Benutzer seine Änderung allerdings nochmal eingeben und hoffen, dass ihm diesmal niemand mehr dazwischen schreibt.

Ein anderer Ansatz ist das setzen einer Sperre (auch bekannt als "auschecken"). Ein Client muss die Daten so explizit zu Bearbeitung anfordern. Wenn ein anderer die selben Daten bereits bearbeitet, ist ein Sperrschalter gesetzt. Der Client wird so bereits von der Eingabe von Änderungen mit der Meldung abbrechen, dass ein anderer Benutzer die Daten gerade exclusiv bearbeitet.
Diese Lösung sollte gewählt werden, wenn viele Benutzer gleichzeitig die selben Daten ändern.

Fehler innerhalb von Transaktionen behandeln

hallo rainbird,

vielen dank für die ausführliche hilfestellung, so kann ich diesen kompletten abschnitt des programmes mit gutem gewissen zu ende bringen.

mfg luciluke