Hallo,
folgendes Zitat hat mich dazu animiert, hier mal eine architektonische Frage zum MVVM-Pattern zu stellen, da ich in meinem Team eine bisher noch nicht geklärte Diskussion zum Grad der Abstraktion führe.
Zitat von Abt
Die Austauschbarkeit von UI-Pattern erreichst Du mit einer Drei-Schichten-Architektur.
Das Model kannst Du i.d.R. wiederverwenden, aber nicht das ViewModel.
Das ViewModel ist vereinfacht ausgedrückt die UI-Logik für die View - und die sind sehr eng miteinander gekoppelt. Sowohl die Implementierung wie auch die Technik.View und ViewModel haben zudem i.d.R. eine Abhängigkeit zu einem konkreten UI-Framework.
Die View kannst Du also problemlos innerhalb von WPF tauschen. Du kannst aber i.d.R. eben nicht das ViewModel zwischen Frameworks sharen.
Das ist auch nicht die Idee dahinter.
Wenn wir Oberflächen entwickeln, habe wir in der Regel eine Assembly, dieMyProject.UI
heißt. Hier wird alles implementiert, was zum MVVM-Pattern gehört. Die meisten Klassen sindinternal
gekennzeichnet und Aufrufe von Fenstern werden über Hilfsmethoden angeboten, wo das Verbinden von UI und ViewModel geschieht. Das Laden der Daten geschieht per IoC angeforderten Provider-Klassen, welche die Daten für die Bindung dem ViewModel zur Verfügung stellen oder Präsentationsobjekten.
Ich persönlich weigere mich für ViewModels als auch Präsentationsobjekten eine pauschale Abstraktion durch Interfaces einzuführen, vor allem da all diese Klassen internal sind. Ich habe für mich noch kein Szenario gehabt, in dem mir diese pauschale Abstraktion geholfen hätte. In manchen Fällen habe ich konkrete Gründe, eine Schnittstelle für spezielle Aufgaben einzuführen, aber auch diese sind in der Regel auch nur innerhalb der Assembly sichtbar.
Das meist angeführte Thema ist die Testbarkeit dieser Klassen. Aber selbst hier sehe ich keinen Grund von meiner Vorgehensweise abzuweichen. PO-Objekte tragen nur Informationen und jegliche Test-Implementierung dieser Klassen würde wieder genauso aussehen. ViewModels sind schon etwas kritischer, aber hier kann die Entkoppelung für den Test über den IoC-Container gelöst werden. Müssen also auch nicht gesondert für den Test implementiert werden. Ansonsten folge ich dem Gedanken von Abt, dass die Wiederverwendbarkeit all dieser Klassen nur sehr bedingt möglich ist, sodass sich der Overhead der Abstraktion meiner Meinung nach in keiner Weise lohnt.
Ich folge hier inzwischen einen sehr pragmatischen Ansatz, der für das Umfeld bei meinem Arbeitgeber gut funktioniert. Ich habe gemerkt, dass gewisse Komplexitäten von allen Entwicklern verstanden und umgesetzt werden müssen. Es hat sich gezeigt, dass gewisse architektionische Ansätze ad absurdum geführt werden, wenn diese nicht von allen verstanden und auch umgesetzt werden. Uns fehlen hier auch gewisse Konstrollstrukturen wie Softwarearchitekten oder Code Reviews. Auch der Wissenstransfer zur korrekten Anwendung unserer eigenen Frameworks und Strukturen ist noch ausbaufähig. Somit empfinde ich die Abstraktion eher als Over-Engineering.
Konkret haben wir in einem Projekt diese abstrakte Variante umgesetzt. Der UI-Bereich besteht nun aus mindestens vier Assemblies wobei eine davon sich um die Abstraktion der Modelle und eine andere um die Abstraktion der ViewModels und der DataProvider kümmert. Architektionisch ist das alles aus theoretischer Sicht sehr schön gelöst. Nur hat sich der Implementierungsaufwand und somit auch die Zeit erhöht. An der Umsetzung des Projektes selbst war ich nicht beteiligt, merke aber dass ich erhebliche Probleme habe, mich im Quellcode zu orientieren. Am Projekt beteiligte Entwickler verstehen den Grund der Komplexität nicht und sind davon eher genervt. Das Entwickeln geht in dem Projekt nicht mehr so flüssig von der Hand.
Ich hoffe, ich konnte meine Gedanken einigermaßen gut niederschreiben. Mich interessiert, wie Ihr das Thema seht bzw. auch angeht. Darf auch gerne allgemein gehalten sein, da es nicht unbedingt ein MVVM spezifisches Thema ist.
Grüße
Du scheinst grundsätzlich Verständnisprobleme bei logischen Operatoren sowie mit LINQ zu haben. Ich kann Dir nur empfehlen, Dich damit ein wenig auseinander zu setzen. Der Lösungsansatz von MrSparkle ist bereits vollständig korrekt.
query = query.Where(entity => entity.MachineId == machineId)
Dieser Ausdruck reduziert bereits Deine Ergebnismenge. Die Kombination von weiteren Where-Ausdrücken kann als Und-Verknüpfung interpretiert werden. Somit ist folgender Code völlig unnötig, da über die beiden vorherigen if-Bedingungen die Ergebnismenge bereits auf diese Bedingungen reduziert wurde.
if ((lindeId > 0) && (machineId > 0))
{
query = query.Where(entity => entity.LineID == lineID && entity.MachineId == machineId)
}
Am besten mal austesten, um zu verstehen, was hier passiert. Das hilft ungemein.
Dieser Lösungsansatz hilft nur, wenn alle Deine Filter-Parameter die Ergebnismenge immer reduzieren sollen. Bei Entweder-Oder-Filterungen braucht man eine etwas angepasste Strategie. Aber so wie ich das lese, sollte das bei Dir wohl eher nicht der Fall sein.
Grüße
An sich lässt sich das ganz einfach für Strings lösen. Man baut sich eine Liste mit den Strings auf und filtert die nicht benötigten Elemente mit der Where-Methode aus System.Linq aus. Die verbleibenden Elemente können dann verarbeitet werden.
var a = "Wert 1";
var b = "Wert 2";
var list = new List<string> { a, b };
var filtered = list.Where(x => x != "Wert 1"); // ggf. mit string.Equals arbeiten, wenn Groß-/Kleinschreibung ignoriert werden soll
foreach (var value in filtered)
{
// Verarbeiten
}
Wenn komplexere Strukturen entstehen, macht es Sinn, diese in Klassen bzw. in Interfaces auszulagern. Hierbei werden dann die Klassen/Interfaces in der Liste gehalten und können über die Eigenschaften gefiltert werden. Funktioniert also analog.
Hi,
ich habe nicht im Detail nachgeschaut, aber könnte es nicht sein, dass eine Where-Bedingung in einem Include nicht zulässig ist?
Mein Lösungsansatz wäre hier ein Mapping TeamPlans
in Project
zu erstellen, dass den ForeignKey ProjectId
und TeamId
berücksichtigt. Dann bräuchte man keine Where-Bedingung im Include.
Grüße
Wie soll denn die Methode PostCard aufgerufen werden? Soll das durch eine manuelle Aktion gestartet werden oder irgendwie automatisch passieren?
Die Frage ist, ob hier ein Event überhaupt notwendig ist. Dein UserControl könnte eine Methode habe, wo der darzustellende Text von außen übergeben werden kann. Sehr wahrscheinlich wird Dein Fenster die Kontrolle über Deine Klasse ApiRequest haben, da dort die Instanz erzeugt wird und somit auch Kontrolle über die die Methode PostCard. Passe die Methode an, sodass sie Deinen erstellter String als Rückgabewert liefert. Wenn Du Dir dann noch die im Fenster dargestellten UserControls merkst, kannst Du den Rückgabewert der Methode des Controls übergeben.
Ach ja, schau Dir mal das Konzept von async und await an. Verwende await statt .Result. Async-Methoden sollten darüber hinaus immer einen Task als Rückgabewert liefern - bis auf Events.
Vermeide MessageBox-Aufrufe in der Logik. Die Aufrufe sollten immer nah an der Oberfläche sein bzw. da wo eine Aktion gestartet wird, also z.B. in einem abonnierten Event. Wird so eine Methode von einem Dienst verwendet, hast Du ein Problem.
Ich habe das Gefühl, dass hier die Logik vertauscht ist. Darüber hinaus ist ein switch-Block ist für ein bool meist ungeeignet, nutze hier einen if-Block.
switch (Filter.HasFlag(_typeOfMessage) == false)
{
case true:
Filter |= _typeOfMessage; break;
default:
Filter &= ~_typeOfMessage; break;
}
Sollte wohl eher so aussehen:
if (Filter.HasFlag(_typeOfMessage))
{
Filter &= ~_typeOfMessage; // entfernen, wenn bereits vorhanden
}
else
{
Filter |= _typeOfMessage; // hinzufügen, wenn noch nicht vorhanden
}
Hi,
mein Ansatz wäre, dass für ein Objekt (Person, Tier, Auto) an einem Ort (Zimmer, Straße, Kneipe) mit einer gewissen Wahrscheinlichkeit ein Ereignis eintreten kann. Das kann ein Ereignis sein, auf das man keinen Einfluss hat oder eine Reaktion zulässt. Also eine Handlung zulässt. Verhältnisse zwischen Objekten würde ich über Beziehungen abbilden, die definieren wie der aktuelle Stand der Beziehung (z.B. Angestelltenverhältnis) ist.
Ich würde es für den Anfang auch sehr einfach halten. Kümmere Dich erst einmal darum, wie Deine Ereignisse ausgelöst werden und welche Komplexitäten Du hier benötigst. Tritt Dein Ereignis immer nur an einem Ort auf? Kann es auch an mehrere Orten auftreten? Lege ich im Zweifel neue Ereignis-Duplikate an oder versuche ich diese für andere Orte wieder zu verwenden? Die Frage ist in diese Fall, lässt Du Redundanz zu oder reduzierst Du diese durch komplexere Objektstrukturen.
Wichtig ist auch, was siehst Du am Anfang als gegeben an. Hat Dein Charakter immer einen Job? Wenn nein, dann kann er seinem Chef nicht begegnen. Hat Dein Charakter immer ein Haustier? Das kann Auswirkung darüber haben, welche Ereignisse zulässig sind. Heißt Du brauchst ggf. Eigenschaften am Ereignis, dass ein Chef benötigt wird. Kann heißen, dass das Ereignis gar nicht ausgewählt werden darf oder eben nichts passiert, wenn das Ereignis aufgrund der Bedingungen für das Eintreten nicht eintreten kann.
Ich hoffe, ich habe es einigermaßen verständlich geschrieben. Auf die Datenhaltung bin ich nicht eingegangen, da die sich in der Regel aus den gewählten Strukturen von ganz allein ergeben. Es sei nur angemerkt, dass das Pflegen der Daten sehr schnell und einfach gehen sollte und die Daten sich ggf. unabhängig von der eigentlichen Programminstallation aktualisieren lassen sollten.
Ach ja, ich fänd es witzig, wenn man "ruhig bleiben" auswählt, aber der Charakter sich mit einer gewissen Wahrscheinlichkeit trotzdem für "eskalieren" entscheidet, weil zu einem bestimmten Prozentsatz immer "eskalieren" ausgewählt wurde.
Meine Erfahrung mit solchen großen Datenmengen ist, dass es Sinn macht, die Dinger in Ordnern (durchaus mehrere Ebenen) zu organisieren, sodass die Anzahl der Dateien maximal bei einigen Tausend liegt. Ich weiß nicht, ob es eine Auswirkung hat, wenn man große Dateimengen in .NET abfragt. Aber spätestens, wenn man sich die Dinger im Windows Explorer anzeigen lassen will, dann darf man warten.
Wir schreiben die Daten in das lokale Profil des Benutzers, genauer gesagt in die Registry. Datenbank ist hier - denke ich - nicht notwendig, da es sich um unwichtige Informationen handeln, die der Nutzer jederzeit durch die Positionierung des Fensters wieder herstellen kann.
Interessanter ist aber, dass wir die Positionierung abhängig von der Gesamtauflösung (WorkingArea) speichern. Warum? Durch die ganze Homeoffice-Geschichte kommt es häufiger vor, dass auf den Rechner lokal oder remote zugegriffen wird und hier unterschiedliche Monitor-Konstellationen vorhanden sind.
Hi,
wir haben in unseren Projekten immer mal wieder das Problem, dass wir nicht auf Eigenschaften des DataContextes in DataTemplates zugreifen können. Wir nutzen hier eine Hilfsklasse, um das umsetzen zu können. Haben wir irgendwann mal entdeckt und ist recht hilfreich.
/// <summary> Bindet den DataContext gegen Elemente, die nicht im Visual-Tree auftauchen </summary>
public sealed class DataContextBinder : Freezable
{
/// <summary> Dependency-Property von <see cref="DataContext"/> </summary>
public static readonly DependencyProperty DataContextProperty =
DependencyProperty.Register(nameof(DataContext),
typeof(object),
typeof(DataContextBinder),
new PropertyMetadata(null));
/// <summary> zu bindende DataContext </summary>
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
/// <summary> Erzeugt eine neue Instanz von <see cref="DataContextBinder"/> </summary>
protected override Freezable CreateInstanceCore()
=> new DataContextBinder();
}
Definition in der XAML-Datei:
<Window.Resources>
<utils:DataContextBinder x:Key="DataContextBinder" DataContext="{Binding}"/>
</Window.Resources>
Verwendung:
<DataTemplate>
<TextBox Text="{Binding Source={StaticResource DataContextBinder}, Path=DataContext.TextProperty}" />
</DataTemplate>
Vielleicht ist es ja mal nützlich.