Laden...

ein Programm viele Daten ...

Erstellt von Cannon vor 14 Jahren Letzter Beitrag vor 14 Jahren 15.468 Views
C
Cannon Themenstarter:in
282 Beiträge seit 2008
vor 14 Jahren

Ich versuche das gerade anders zu lösen. Was man aber zumindest machen muss, ist für jede einzelne "Tabellenzeile" (hier eine Klasse) eine einzelne IEditableObjekt-Schnittstelle zu implementieren. Da kommt man nicht rum. Nehmen wir mal wieder das Beispiel "Contact".

Die Klasse "Contact", definiert eine Zeile in der Tabelle "Contacts". Wenn ich jetzt die Schnittstelle an die Klasse Contact anhänge, dann sollte das Problem gelöst sein. Allerdings ist die Lösung nun doch etwas komplizierter. Die exemplarische Vorgehensweise könnte so aussehen, (wenn der DataContext mit SqlMetal generiert wurde):

  • neue Klassendatei erzeugen
  • die Klasse "Contact" als partial definieren
  • die Schnitsstelle IEditableObjekt implementieren

Und jetzt wirds schwieriger. Meine Idee (als C#-Programmierer) wäre den Inhalt Klasse bei BeginEdit binär zu kopieren in ein Backup, bei CancelEdit zurückzukopieren. Nur die Frage ist, wie komme ich an die einzelnen Objekte ran, also wenn ich hier z.B. BeginEdit habe, wo ist die Quelle zum kopieren ... ???

C
Cannon Themenstarter:in
282 Beiträge seit 2008
vor 14 Jahren

Wen es noch interessiert. Ich habe eine interessante und recht einfache Lösung gefunden:

http://www.codeproject.com/KB/database/IEditableObject.aspx

Zusammengepackt als Klasse kann das so aussehen. Und man kann bei SqlMetal als eine Basisklasse übergeben "/entitybase:EntityEdit". Somit muss man keinen COde mehr extra anfassen und kann CancelEdit und EndEdit direkt auf eine BindingSource anwenden.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Reflection;
using System.Collections;

public abstract class EntityEdit : IEditableObject
{
	Hashtable props = null;

	#region IEditableObject Members

	/// <summary>
	/// Set object in edit mode and save current values
	/// </summary>
	public void BeginEdit()
	{
		//exit if in Edit mode
		if (null != props) return;

		//enumerate properties
		PropertyInfo[] properties = (this.GetType()).GetProperties(BindingFlags.Public | BindingFlags.Instance);

		props = new Hashtable(properties.Length - 1);

		for (int i = 0; i < properties.Length; i++) {
			//check if there is set accessor
			if (null != properties[i].GetSetMethod()) {
				object value = properties[i].GetValue(this, null);
				props.Add(properties[i].Name, value);
			}
		}
	}

	/// <summary>
	/// Reject changes made to object since method BeginEdit() was called
	/// </summary>
	public void CancelEdit()
	{
		//check for unapporpriate call sequence
		if (null == props) return;

		//restore old values
		PropertyInfo[] properties = (this.GetType()).GetProperties(BindingFlags.Public | BindingFlags.Instance);
		for (int i = 0; i < properties.Length; i++) {
			//check if there is set accessor
			if (null != properties[i].GetSetMethod()) {
				object value = props[properties[i].Name];
				properties[i].SetValue(this, value, null);
			}
		}

		//delete current values            
		props = null;
	}

	/// <summary>
	/// Commit changes in object since method BeginEdit() was called
	/// </summary>
	public void EndEdit()
	{
		//delete current values            
		props = null;
	}

	#endregion
}

5.299 Beiträge seit 2008
vor 14 Jahren

Cool - gleich in meine Sammlung!!

Der frühe Apfel fängt den Wurm.

365 Beiträge seit 2004
vor 14 Jahren

Usercontrols haben niemals selbständig zugriff auf externe daten, sondern
bekommen sie immer von aussen zugewiesen.

Sei es durch einen Presenter ( MVP ) oder wenn du "normal" arbeitest, dann entweder
durch die Form in der sie sind, oder sogar von der Form die diese Form aufgerufen hat.

Hallo FZelle,

ich schreibe gerade eine verteilte Anwendung und orientiere mich hierbei stark an Rainbirds Appserver samt Anwendungsbeispiel.

Es gibt dort ein UserControl namens ProductEditPanel. Dieses UserControl hat ein Property ProductID, welches von außen gesetzt wird um einen Artikel zu laden. Das eigentliche Laden der Artikeldaten (Bezeichnung, Einheit etc.) erledigt das UC allerdings selber und ruft die Daten hierfür selbständig vom entsprechenden Service ab.

Ist diese Vorgehensweise deiner Meinung nach falsch oder habe ich dich falsch verstanden?

Und falls ja, kannst du mir zu diesem konkreten Beispiel sagen, inwiefern ich mich hier einschränke?

Gruß

Christoph

F
10.010 Beiträge seit 2004
vor 14 Jahren

Ich habe mir Rainbirds demo nicht angeschaut, aber wenn da das UserControl selber
die Daten über ein festverdrahtetes Objekt holt, dann muss
Rainbird da schon einen echten Grund für haben, denn das ist ja das, was eigentlich
in vernünftigen Anwendungen nicht gemacht werden sollt.

Holt das UC allerdings über ein per Interface gesetztes Object die Daten ( ein Repository
oder einen DAL), dann kann das u.U. ok sein.

365 Beiträge seit 2004
vor 14 Jahren

Ja, der Service wird über den Anwendungsserver geladen und ist über die app.config austauschbar. Von daher habe ich die generelle Ablehnung auch nicht verstanden.

F
10.010 Beiträge seit 2004
vor 14 Jahren

Naja, die wenigsten, die UC's erstellen, die selbständig die Daten holen, haben
das Prinzip der losen Kopplung verstanden und benutzen deshalb festverdrahtete
Funktionalitäten.

Aber selbst wenn es entkoppelt gemacht wird hat es im hinblick auf Erweiterbarkeit
und Wartbarkeit einen faden Beigeschmack.

Ein UC ist zur Anzeige und Bearbeitung von Daten gedacht, nicht zu deren
beschaffung.
"Seperation of Concern" ist da das Stichwort.

C
Cannon Themenstarter:in
282 Beiträge seit 2008
vor 14 Jahren

Um nochmal einen leichten Streifzug durch das Thema zu führen. Ich kann an der Bindingsource direkt neue Datensätze erstellen bzw. löschen.

Allerdings sollte die Verwaltung auch wieder der "Presenter" übernehmen. Das heißt User klickt "Datensatz zufügen", dann wird der im Presenter erzeugt und ans UserControl übergeben. Das UserControl fügt den Datensatz dann der BindingSource zu. Gleiches gilt auch beim löschen von Daten. Die Sicherheitsabfrage macht der Presenter und dann wird entschieden, ob die Methode "LöscheDatensatz" im UserCOntrol aufgerufen wird. Das heißt aber ich muss ich jeder userControl-Klasse Funktionalität für löschen, einfügen und bearbeiten einbringen.

Ist das so korrekt?

F
10.010 Beiträge seit 2004
vor 14 Jahren

Ja.
Wobei Funktionen die von UC's delegiert werden, immer als Events/Delegates ausgelegt
sein sollten.

Es sagt ja keiner, das durch diese Herangehensweise weiniger Code zu schreiben wäre 😉

3.728 Beiträge seit 2005
vor 14 Jahren
Aktive UserControls

... aber wenn da das UserControl selber
die Daten über ein festverdrahtetes Objekt holt, dann muss
Rainbird da schon einen echten Grund für haben, denn das ist ja das, was eigentlich
in vernünftigen Anwendungen nicht gemacht werden sollt.

Holt das UC allerdings über ein per Interface gesetztes Object die Daten ( ein Repository
oder einen DAL), dann kann das u.U. ok sein.

Im konkreten Fall lassen sich die UserControls von einer Factory Proxy-Objekte für den Konsum bestimmter Geschäftslogik-Komponenten (Man könnte auch Dienste sagen) erzeugen und nutzen die Geschäftslogik über diese Proxies. Die Implementierung der Geschäftslogik müssen die UserControls dazu nicht kennen, sondern nur ein Interface. Die UserControls sind also nicht "hart" damit verdrahtet. Trotzdem bestimmt der Entwickler des UserControls selber, WANN er Geschäftslogik-Komponenten aufruft und sich von der Factory entsprechende Objekte/Proxies erzeugen lässt. Diese Vorgehensweise hat sich im Gegensatz zu Dependecy-Injection in der Praxis sehr bewährt (das ist meine persönliche Erfahrung!). Das macht schon deshalb Sinn, da der Status bei Windows-Anwendungen im GUI-Code (also in Forms und UserControls) verwaltet wird und deshalb die Entscheidungen, wann etwas aufgerufen/angestoßen/abgefragt wird, der GUI-Entwickler treffen muss.

Bei Dependency Injection wird der GUI-Entwickler der Möglichkeit beraubt, zu entscheiden, wann Geschäftslogik-Objekte/Komponenten/Dienste erzeugt bzw. aufgerufen werden. Ein Factory-Aufruf ist ein 1:1-Ersatz für "Foo bar=new Foo();". Deshalb kommen auch Entwickler mit wenig Architektur-Hintergrundwissen im Team sehr leicht damit zurecht. Sie müssen sich nicht erst in ein kompliziertes Container-Framework einarbeiten.

Auch wenn ich in meinem n-Tier-Beispiel die Factory zur Erzeugung von Remotng-Proxies verwenden, die es dem Client ermöglichen, Geschäftskomponenten auf dem Applikationsserver zu konsumieren, ist die Architektur nicht darauf beschränkt. Die Factory könnte die Geschäftskomponenten auch lokal erzeugen (z.B. per Reflection laut App.config). Ich habe in einem realen Projekt den Applikationsserver sogar einmal optional zuschaltbar gemacht. So konnten die Clients auch ohne Appserver arbeiten, bis dieser vollständig Einsatzbereit war. Bei diesem Projekt wurde eine Fat-Client-Anwendung auf eine 3-Tier-Architektur migriert. Die Produktiv-Clients haben so über mehrere Monate ohne Applikationsserver, aber bereits mit der finalen Architektur gearbeitet.

Es ist auch ein Unterschied, ob man allgemeine Steuerelemente, wie z.B. ein TreeView, schreibt, oder ob man seine Benutzeroberfläche nur in handliche Komponenten aufteilen möchte. Im Falle meines n-Tier-Beispiels werden UserControls verwendet, um ein Standard-Formular zur Verwaltung der Artikelstammdaten über einen Plug-In-Mechanismus erweitern zu können. Die Lagerbestandsliste ist z.B. ein UserControl aus dem Modul "Lagerverwaltung", welches aber trotzdem als zusätzlicher Reiter im Artikelstamm-Formular angezeigt werden soll (Wenn ich einen Artikel ausgewählt habe, möchte ich sofort die Lagerbestände abfragen können, ohne dazu ein neues Formular öffnen zu müssen). So können einzelne GUI-Teile von verschiedenen Teams entwickelt werden, und man kann sie später wie Lego zur Gesamtanwendung zusammenstecken. Deshalb müssen UserControl auch keineswegs immer wiederverwendbare Steuerelemente sein. UserControls ermöglichen komponentenorientierte Entwicklung komplexer GUIs. Sie dürfen, wenn so eingesetzt, fast alles, was ein Form auch dürfte.

Wenn verschiedene UserControls (die vielleicht von verschiedenen Teams entwickelt wurden) auf einem Formular platziert werden und mit einander kommunizieren müssen, natürlich ohne sich dabei direkt zu kennen, ist diese Kommunikation natürlich über entsprechende Interfaces abzuwickeln.

F
10.010 Beiträge seit 2004
vor 14 Jahren

Du hälst also nichts von MVP, MVC oder MVVM?

3.728 Beiträge seit 2005
vor 14 Jahren
MVC & Co.

Ehrlich gesagt, Nein!
Ich war davon eine Zeit lang selber sehr angetan, gerade zu euphorisch. Ich habe mit verschiedenen Ansätzen in Richtung MVC (MVP und MVVM sind Varianten davon, deshalb scher ich das jetzt über einen Kamm) experimentiert und das auch in realen Projekten eingesetzt.

Am Ende wurden dadurch mehr Probleme geschaffen, als gelöst. Hauptproblem ist die hohe zusätzliche Komplexität, die man sich ungewollt einkauft. Je komplexer die GUI, desto mehr Event-Feuerwerk und mehr Infrastruktur-Code um alles mögliche voneinander zu entkoppeln. Das erschwert das Debugging und begünstigt Fehler, die beim Testen nicht gefunden werden.
Ein weiteres Manko ist, dass Designer und Assistenten für DataBinding etc. nur teilweise, schlecht oder gar nicht genutzt werden können, wenn man MVC & Co. implementiert. Ob Designer überhaupt sinnvoll sind und nicht, ist eine Philosophie-Frage, aber Tatsache ist, dass sie es bei korrekter Anwendung schaffen den 0-8-15-Kram effektiv zu erledigen, ohne auf eine saubere Architektur verzichten zu müssen. Wenn ich alles, was mir die Designer normal abnehmen, von Hand schreiben muss, brauche ich mindestens 10 Mal so lange. Das ist betriebswirtschaftlich nicht sinnvoll.

Es ist trotzdem gut diese Muster zu kennen und sich damit auseinander zu setzen. Aber man sollte sie nicht zu feingranular einsetzen. Wenn ich ein komplexes Formular habe, welches aus vielen Komponenten (z.B. aktive UserControls) zusammengesetzt ist, muss ich mir natürlich Gedanken machen, wie die Kommunikation zwischen diesen Komponenten und/oder dem Host-Formular ablaufen soll. Da kann ein von MVC & Co. inspirierter Ansatz eine feine Sache sein.

Ich muss deshalb aber nicht eine Methode auf dem Model aufrufren, damit dieses dann die eigentliche Geschäftslogik-Komponente aufruft und dem Controler per Event das Ergebnis mitteilt, damit dieser der Form sagen kann, sie soll eine MessageBox anzeigen. Das ist einfach kompletter Unfug. Wichtig ist, dass meine Haupt-Geschäftslogik nicht im GUI-Code steckt und nicht direkt irgendwelche MessageBoxen etc. aufruft. Meistens sind 80% des Aufwandes einer Anwendung auf der GUI-Seite. Dann sollte ich den/dem GUI-Entwicklerteam(s) keine Entwurfsmuster auf bürden, die sie am Ende mehr behindern, als dass sie nützen. Deshalb kann man sehr grob zusammenfassen:

Forms und UserControls dürfen Alles, außer direkt auf die Datenbank zugreifen und selber Hauptgeschäftslogik implementieren! Ich unterscheide zwischen Hauptgeschäftslogik und trivialer Geschäftslogik. Unter Letztere fällt z.B. das berechnen einer Gesamtsumme oder ein RegEx zur Prüfung der Mail-Adresse direkt bei der Eingabe.

MVVM wird ja häufig zusammen mit WPF propagiert. WPF ist in meinen Augen aber noch nicht fertig und taugt für reale Projekte noch nicht. Die derzeitigen Tools sind mies! Für Dinge, die in Windows.Forms komfortabel gelöst waren, muss man plötzlich haufenweise Code schreiben? Das kann nicht die Revolution sein. Natürlich muss man irgendwann mal anfangen, das GUI-System von morgen zu entwickeln. Aber WPF macht noch in zu vielen Bereichen zu viele Schritte rückwärts. Man kann zwar jetzt die Formulare mit dem Frontpage-Nachfolger entwerfen, dafür ist der Visual Studio-WPF-Designer aber mies.

Vielleicht bringen MVC & Co. ja im Web-Bereich mehr? Kann ich nicht sagen, bin kein richtiger Web-Entwickler.

F
10.010 Beiträge seit 2004
vor 14 Jahren

Ja, MVC und deren reale Implementierungen bringen meisst alle von Dir genannten Nachteile mit sich, deshalb verwende ich auch MVP.

Wenn Du hier nicht PassivView benutzt sondern den Supervising Controller/Presenter
kannst Du genau so die Designer fürs binden benutzen, wie sonst auch.
Und der View "spricht" auch nur mit dem Presenter, und den Daten des Models.

Änderungen am Model bekommt der Presenter per INotifyPropertyChanging oder
INotifyPropertyChanged mit, und genau dort kann man dann auch Valdierung
im Presenter oder im Model selber machen, und das Ergebnis über IDataErrorInfo
nach aussen bekannt machen.

Dein Model muss also nur 3 Interfaces implementieren.
Willst du nur mit POCO's arbeiten, kannst du diese z.b. in ein ViewModel stecken, was mit einem Proxy sogar trannsparent geht.

Dann noch per DI oder Servicelocator ( oder beides ) den Rest gemacht, und schon
wird es recht einfach wartbar und testbar.

Insgesamt ist das dann gerade genug entkoppelt, aber eben die meisten der von Dir angesprochenen Probleme tauchen nicht auf.