none
MVVM - Strukturierung RRS feed

  • Frage

  • Hallo zusammen,

    ich habe mir in den letzten Tages einiges zu MVVM angelesen, habe dennoch einige Fragezeichen im Kopf.

    Momentan ist es so das ich ein ViewModel habe in diesem ist quasi alles enthalten bis auf eine Page (diese hat ein eigenes VM), die Eigenschaften verwende ich allerdings auch fast alle auf jeder Seite.

    Was ist der sauberste bzw. beste Weg (auch Performancemäßig)? Einigen Seiten oder Fragerunden nach soll alles einer Page in ein Model, andere gehen auf ein Element (z.B. Liste) oder View. Ich kann allerdings teilweise dem Element VM zustimmen da an eine Page mehrere VM's gehangen werden können und diese somit sauber getrennt sind. Habt Ihr da praktische Erfahrungswerte?

    Noch eine Frage nebenbei zum Speicher, meine Anwendung benötigt laut Taskmanager 26 MB, wenn ich mir in VS mit dem Performance Analyser einen Snapshot erstelle und mir die Objekte anzeigen lasse komme ich auf einen Heap von 3 MB. Wo geht dieser ganze Speicher hin und wie kann dies optimiert werden? Oder benötigt das Rahmenprogramm tatsächlich so viel Speicher.

    Vielen Dank + Grüße

    A.Kuller

    Donnerstag, 15. Juni 2017 21:05

Antworten

  • Hi A,

    erst mal was deinen Speicher verbrauch angeht. Einmal geht der GC hin und reserviert sich größere Block am Speicher als er wirklich brauch. Das liegt daran, das der Prozess des Reservierens relative aufwändig ist. (Wenn ich die Zahl richtig im Kopf habe sind es bei ein 32bit System 16MB und 64bit 32MB, genauen werte kann man über google finden). 

    Dann kommt meines Wissens noch hinzu, das der GC bei nicht Verwalteten Code, nicht genau weiß wie viel Speicher er brauch, sondern da grob schätzt. Und auch nicht genau weiß wann der Speicher wieder Freigegeben wurde. (Das ist jetzt aber eher halb wissen)

    Grundlegend ist da der Taskmanager sehr ungenau. 

    Was dein Problem mit MVVM angeht (Ich hab da jetzt die Beiträge nur überflogen).
    Bei der Konkreten Implementation gibt es schon einige Unterschiedliche Ansätze.

    Ich persönlich rate meistens dazu sich mal Prism vom Microsoft anzuschauen. Man muss das Framework jetzt nicht vermenden (und sollte es auch nicht unbedingt verwenden, da es schon sehr "Schwergewichtig" ist).

    Grundlegend werden dort aber schon mal viele wichtige Punkte angesprochen, die man für eine Anwendung brauchen kann. (Kommunikation zwischen lose gekoppelten Komponente, DI, IoC-Container usw.)

    Vielleicht noch ein paar Anmerkungen.  Ich hatte beim überfliegen, das du ein SingelTon verwendet. Es gibt Bereiche in denen ein SingelTon richtig ist, meist wird es aber falsch verwendet um zB. global Daten zur Verfügung zu stellen, was für einige Probleme sorgen kann. Schau dir da mal lieber DI  und IoC-Container an. (Die Links hab ich mir jetzt nicht durchgelesen, sollte aber einen einstieg geben und sind bei Prisem mit dabei).

    Dann greifst du im VM direkt auf den Context vom EF zu, hier bietet sich meist das Repository Pattern an.

    MFG

    Björn 


     

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:47
    Freitag, 23. Juni 2017 10:53
  • Nun wenn du dir DI und IoC noch angeschaut hättest, wäre das die Lösung für dein Problem gewesen.

    Mit DI  Injizierst du Abhängigkeiten in eine Klasse. Dein VM erzeugt sich also das Repository nicht selbst sondern es wird über den Konstruktor (oder über ein Property) in die Klasse gegeben. Bei Unit Test lässt sich das dann ganz einfach Mocken. 

    Mit dem IoC Container kannst du dir dann an einer Zentralen stelle, die Abhängigkeiten auflösen. Die IoC Container haben meist andere Vor- und Nachteile hier musst du dir einfach mal ein paar anschauen und schauen was du brauchst.

    Die Repositorys können ein IQueryable zurückliefern, damit kannst du dann an anderen Stellen die Eigentliche Abfrage schreiben. Mit EF wird dann die Abfrage gegen die Datenbank erst Ausgeführt wenn du z.B. toList aufrufst. Für die CRUD Operationen kannst du eine Generische Basis Klasse verwenden, das ist kein Problem. 

    Für Komplexere Abfragen lohnt es sich meist zusätzlich eine Eigene Methode Bereit zu stellen, wenn du die Abfrage dann an anderen Stellen brauchst musst du nur die Methode aufrufen.

    Es gibt aber den Ansatz im DAL (Reposetorys), die Entitäten (EF "Datenbank tabelle") auf Modelle (Objekt) zu Mappen. Das hat den Vorteil, das du in Zukunft einfacher deine Datenbank ändern kannst und dann nur noch den DAL anpassen musst.

    MFG

    Björn

    Montag, 3. Juli 2017 06:47
  • Hallo A.,

    ja, das "übergeordnete" VM geben wir als Parameter mit. Muss man sicherlich nicht so machen, Peter, aber wenn man viele gemeinsame Daten hat, ist es sicher übersichtlicher als alles in ein VM zu packen.

    Schlanker ist sicher die Nutzung von Messaging in unserm Fall von GalaSoft.MvvmLight.

    In einer gesonderten Klasse definiert man dazu einen Token, z.B. (wir nutzen VB.NET)

    Namespace Messaging    
    Public Class Channel        
    Public Shared GemeinsameDatenChannelToken As New Object    
    End Class
    End Namespace

    Der Sender führt z.B. folgende Anweisung aus

    Messenger.Default.Send(Of String)(daten, Channel.GemeinsameDatenChannelToken)

    Der Empfänger abonniert die Nachrichten mit

    Messenger.Default.Register(Of String)(Me, Messaging.Channel.GemeinsameDatenChannelToken, AddressOf ShowIncomingData)
    Wenn die Nachricht eintrifft wird ShowIncomingData ausgeführt.

    Herzliche Grüße

    Peter

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:47
    Montag, 26. Juni 2017 13:28
  • Hi A.,
    DAL = Datenzugriffsschicht bedeutet nicht, dass dort die Datenbank 1:1 nachgebaut und vielleicht auch noch geladen wird. Stelle die vor, die Datenbank hat 100 Tabellen und in jeder Tabelle 1 Mio Datensätze, gesamt vielleicht 10 GB Speicherplatz. Da funktioniert dieser Ansatz nicht.

    Das Datenmodell im DAL beinhaltet üblicher weise nur einen kleinen Auszug aus der Datenbank, was sowohl Tabellen, Spalten und auch Datensätze bedeutet. Jedes Datenmodell im DAL wird für einen konkreten Prozessablauf entwickelt. Es werden damit nur ein paar Spalten und nur ein paar Datensätze geladen, verarbeitet und Änderungen wieder abgespeichert. 


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:46
    Freitag, 7. Juli 2017 10:51
  • Hi A.Kuller,

    am schönsten ist es natürlich, wenn du direkt am Start, die Abhängigkeiten auflöst. Dann hast du genau einer Stelle an dem die Abhängigkeit ändern muss. 

    Grundlegend kannst du dir aber Pro Page/View eine eigene IoC Instanz erzeugen, der dir dann für den Bereich die Abhängigkeiten verwaltet.

    Teils erlauben dir aber auch die Container ihre Abhänigkeiten zu ändern bzw dann auch zu ergänzen.

    Da musst du dir mal anschauen was die Unterschiedlichen IoC Container dir da bieten.

    Zum DAL hat dir Peter ja schon was gesagt.

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:46
    Freitag, 7. Juli 2017 14:51

Alle Antworten

  • Hi A.,
    es gibt keinen allgemeingültigen sauberen bzw. besten Weg. Generell sollten Daten nur an einer Stelle verwaltet werden. Das ist erst einmal bei nur einer Instanz nur eines VM gewährleistet. Andererseits kann das Programm dadurch aber schlecht verwaltbar werden. Besser ist es, wenn es möglich ist, Sachverhalte zusammenzufassen und je Sachverhalt eine eigene VM-Instanz einer ggf. eigenen Klasse zu halten. Wo da das Optimum ist, kann man nur aus der gesamten Aufgabenstellung ableiten. So kann es sinnvoll sein, für zentrale Eigenschaftswerte nur eine Instanz zu halten, für abgegrenzte Prozesse oder Datenobjekte jeweils eine gesonderte Instanz. Das ist möglich, da eine View-Instanz mehrere VM-Instanzen nutzen kann und eine VM-Instanz von mehreren View-Instanzen genutzt werden kann.

    Bezüglich Speichernutzung ist zu berücksichtigen, dass neben dem Heap auch noch Stacks und Programmcode Platz belegen.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks


    Freitag, 16. Juni 2017 11:20
  • Hallo Peter,

    Danke für deine Antwort.

    Diese eine Instanz wird über Singleton realisiert, oder gibt es dort eine bessere Lösung die ich noch nicht gesehen habe?

    Momentan habe ich eine Listview mit einer List realisiert (wegen EF .ToList()), macht es Sinn dies auf Observable Collection zu ändern? Habe dort noch nicht so richtig den Unterschied festgestellt (ich weiß es gibt 1000'e Tutorials dazu). Gibt es eine Möglichkeit das wenn ich das Model mit x:Bind in XAML binde den Text eines Elements live zu aktualisieren und die Elemente zu sortieren? Mit {Binding Update...Trigger=PropertyChanged} klappt es (nach Optimierungsleitpfaden soll x:Bind verwendet werden).

    Hier Mein Code:

    XAML:

    <Page
        x:Class="UI.Pages.Tasks"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:UI.Pages"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:views="using:UI.Views"
        xmlns:models="using:UI.Models"
        xmlns:formatter="using:UI.Formatter"
        mc:Ignorable="d"
        d:DesignWidth="900">
        <Page.Resources>
            <formatter:ConvertListItemStateToFont x:Key="ConvertStateToFontStyle" />
            <formatter:ConvertItemUsageToVisibility x:Key="ConvertUsageToVisibility" />
            <formatter:ConvertSelectedItemToVisibility x:Key="ConvertSelectedItemToVisivility" />
            <x:String x:Key="mus">fgh</x:String>
        </Page.Resources>
    
        <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
            <Grid.RowDefinitions>
                <RowDefinition Height="100"/>
                <RowDefinition/>
            </Grid.RowDefinitions>
            <Grid.ColumnDefinitions>
                <ColumnDefinition Width="380"/>
                <ColumnDefinition/>
            </Grid.ColumnDefinitions>
            <TextBlock HorizontalAlignment="Left" Margin="10,10,0,0" TextWrapping="Wrap" Text="Aufgaben" FontWeight="Bold" VerticalAlignment="Top"/>
            <TextBlock Text="Legen Sie neue Aufgaben an oder ändern Sie diese." Margin="10,35,10,0" VerticalAlignment="Top" Grid.ColumnSpan="2" />
            <StackPanel Margin="10,60,10,0" VerticalAlignment="Top" Orientation="Horizontal">
                <TextBox HorizontalAlignment="Left" TextWrapping="NoWrap" Text="{Binding TaskSearch, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="275" PlaceholderText="Suchen" AutomationProperties.LandmarkType="Search"  />
                <Button x:Name="ButtonAdd" Content="Anlegen" Margin="10,0,0,0" HorizontalAlignment="Right" Width="75" Click="ButtonAdd_Click" />
            </StackPanel>
            <ListView x:Name="ListTasks" Margin="10,10,10,10" IsItemClickEnabled="True" SelectionChanged="ListTasks_SelectionChanged"  ItemsSource="{x:Bind _TaskViewModel.Tasks, Mode=OneWay}" SelectedValuePath="Id" Grid.Row="1" SelectedItem="{x:Bind _TaskViewModel.TaskSelected, Mode=TwoWay}">
                <ListView.ItemContainerTransitions>
                    <TransitionCollection/>
                </ListView.ItemContainerTransitions>
                <ListView.ItemTemplate>
                    <DataTemplate x:DataType="models:Order">
                        <TextBlock Text="{x:Bind Name, Mode=OneWay}" FontStyle="{x:Bind Active, Converter={StaticResource ConvertStateToFontStyle}, Mode=OneWay}" FontWeight="{x:Bind Active, Converter={StaticResource ConvertStateToFontStyle}, Mode=OneWay}" />
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
            <StackPanel Grid.Column="1" Margin="10" VerticalAlignment="Top" Grid.Row="1" Width="350" HorizontalAlignment="Left" Visibility="{x:Bind _TaskViewModel.TaskSelected, Converter={StaticResource ConvertSelectedItemToVisivility}, Mode=OneWay}">
                <TextBox x:Name="TextBoxTaskName" TextWrapping="NoWrap" Text="{x:Bind _TaskViewModel.TaskSelected.Name, Mode=TwoWay}" VerticalAlignment="Top" Header="Name der Aufgabe" MaxLength="32" />
                <!--<TextBox x:Name="TextBoxTaskName" TextWrapping="NoWrap" Text="{Binding Path=SelectedItem.Name, ElementName=ListTasks, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" VerticalAlignment="Top" Header="Name der Aufgabe" MaxLength="32" LostFocus="{x:Bind _TaskViewModel.SaveItem}"/>-->
                <ToggleSwitch Margin="0,10" HorizontalAlignment="Left" VerticalAlignment="Top" Header="Aufgabe aktiv?" IsOn="{x:Bind _TaskViewModel.TaskSelected.Active, Mode=TwoWay}" />
                <!--<ToggleSwitch Margin="0,10" HorizontalAlignment="Left" VerticalAlignment="Top" Header="Aufgabe aktiv?" IsOn="{Binding Path=SelectedItem.Active, ElementName=ListTasks, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Toggled="{x:Bind _TaskViewModel.SaveItem}"/>-->
                <Button Content="Löschen" HorizontalAlignment="Left" VerticalAlignment="Top" Visibility="{x:Bind _TaskViewModel.TaskSelected.Used, Converter={StaticResource ConvertUsageToVisibility}, FallbackValue=Collapsed,Mode=OneWay}">
                    <Button.Flyout>
                        <Flyout x:Name="FlyoutDelete">
                            <StackPanel>
                                <TextBlock TextWrapping="Wrap" Text="Wollen Sie diese Aufgabe löschen?" Width="320"/>
                                <Button Margin="0,10,0,0" Content="Löschen" Click="ButtonFlyoutDelete_Click" />
                            </StackPanel>
                        </Flyout>
                    </Button.Flyout>
                </Button>
                <Button Content="Löschen" HorizontalAlignment="Left" VerticalAlignment="Top" Click="{x:Bind _TaskViewModel.Remove}"  Visibility="{x:Bind _TaskViewModel.TaskSelected.Used, Converter={StaticResource ConvertUsageToVisibility}, ConverterParameter=invert, FallbackValue=Visible, Mode=OneWay}"/>
            </StackPanel>
        </Grid>
    </Page>

    MVVM:

    using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.ComponentModel; using System.Linq; using System.Runtime.CompilerServices; using System.Text; using System.Windows.Input; using System.Threading.Tasks;

    using UI.Models namespace UI.ViewModels { public class TaskSettingsViewModel : demo { public TaskSettingsViewModel() { updateTasks(); } private List<Order> _Tasks = new List<Order>(); public List<Order> Tasks { get => _Tasks; set => SetProperty(ref _Tasks, value); } private Order _TaskSelected; public Order TaskSelected { get => _TaskSelected; set { if (value != TaskSelected) { SetProperty(ref _TaskSelected, value); } } } public void SaveItem() { if (TaskSelected == null) return; Data.Task item; Order change = TaskSelected; using (Context db = new Context()) { item = db.Tasks.FirstOrDefault(w => w.TaskId == change.Id); if (item != null) { if (item.Title != change.Name || item.Valid != change.Active) { item.Title = change.Name; item.Valid = change.Active; db.Update(item); db.SaveChanges(); updateTasks(); } } } TaskSelected = Tasks.FirstOrDefault(w => w.Id == change.Id); } public void updateTasks() { using (Context db = new Context()) { //Tasks = db.Tasks.Where(w => w.TaskId != 1 && w.Title.ToLower().Contains(TaskSearch.ToLower())).OrderBy(o => o.Title.ToLower()).Select(s => new Order() { Active = s.Valid, Id = s.TaskId, Name = s.Title, Used = (s.Times.Count == 0 ? false : true) }).ToList(); Tasks = db.Tasks.Where(w => w.TaskId != 1 && w.Title.ToLower().Contains(TaskSearch.ToLower())).OrderBy(o => o.Title.ToLower()).Select(s => new Order(s.TaskId, s.Title, s.Valid, (s.Times.Count == 0 ? false : true))).ToList(); } MainViewModel.Instance.Tasks.Clear(); } private string _TaskSearch = String.Empty; public string TaskSearch { get => _TaskSearch; set { if (value != TaskSearch) { SetProperty(ref _TaskSearch, value); updateTasks(); } } } public void Remove() { using (Context db = new Context()) { db.Tasks.Remove(db.Tasks.FirstOrDefault(w => w.TaskId == TaskSelected.Id)); db.SaveChanges(); updateTasks(); } } public void add() { var a = new Data.Task() { Title = "", Valid = false }; using (Context db = new Context()) { db.Tasks.Add(a); db.SaveChanges(); updateTasks(); } TaskSelected = Tasks.FirstOrDefault(w => w.Id == a.TaskId); } } public class demo : NotificationBase, INotifyCollectionChanged { public event NotifyCollectionChangedEventHandler CollectionChanged; public virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e) { if (CollectionChanged != null) { CollectionChanged(this, e); } } } public class NotificationBase : INotifyPropertyChanged { public event PropertyChangedEventHandler PropertyChanged; // SetField (Name, value); // where there is a data member protected bool SetProperty<T>(ref T field, T value, [CallerMemberName] String property = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; RaisePropertyChanged(property); return true; } // SetField(()=> somewhere.Name = value; somewhere.Name, value) // Advanced case where you rely on another property protected bool SetProperty<T>(T currentValue, T newValue, Action DoSet, [CallerMemberName] String property = null) { if (EqualityComparer<T>.Default.Equals(currentValue, newValue)) return false; DoSet.Invoke(); RaisePropertyChanged(property); return true; } protected void RaisePropertyChanged(string property) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(property)); } } } public class NotificationBase<T> : NotificationBase where T : class, new() { protected T This; public static implicit operator T(NotificationBase<T> thing) { return thing.This; } public NotificationBase(T thing = null) { This = (thing == null) ? new T() : thing; } } public class CommandHandler : ICommand { public event EventHandler CanExecuteChanged; private Action _action; public CommandHandler(Action action) { this._action = action; } public bool CanExecute(object parameter) { return true; } public void Execute(object parameter) { this._action(); } } }

    namespace UI.Models {

    public class Order : NotificationBase
        {
            public int Id { get; set; }
            private string _Name;
            public string Name { get => _Name; set => SetProperty(ref _Name, value); }
            private bool _Active;
            public bool Active { get => _Active; set => SetProperty(ref _Active, value); }
            public bool Used { get; set; }

            public Order(int Id, string Name, bool Active, bool Used)
            {
                this.Id = Id;
                this.Name = Name;
                this.Active = Active;
                this.Used = Used;
                PropertyChanged += Order_PropertyChanged;
            }

            public Order()
            {

            }

            private void Order_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                using (Context db = new Context())
                {
                    Data.Task item = db.Tasks.FirstOrDefault(w => w.TaskId == Id);
                    if (item.Title != Name || item.Valid != Active)
                    {
                        item.Title = Name;
                        item.Valid = Active;
                        db.Update(item);
                        db.SaveChanges();
                    }
                }
            }
        }

    }

    Bzw. was ist an meinem ViewModel zu verbessern oder habe ich etwas falsch implementiert?

    Vielen Dank

    A.Kuller


    • Bearbeitet A.Kuller Dienstag, 20. Juni 2017 10:36 Korrektur
    Montag, 19. Juni 2017 12:07
  • Hi A.
    es ist schwer, zu den teilweisen Codeauszügen eine Einschätzung zu geben. Deutlich ist, dass kein abgegrenztes MVVM eingesetzt wurde. Es existiert ein Mix zwischen MVVM und CodeBehind. So etwas ein möglicher Weg. Auch ist die Zuordnung der Klassen zu den Namensräumen nicht schlüssig (models:Order). Es lohnt sich, ein durchgängiges MVVM einzusetzen (ohne CodeBehind). Das verbessert die Übersichtlichkeit und Wartbarkeit, insbesondere bei Tests und späteren Erweiterungen und Anpassungen.

    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks


    Dienstag, 20. Juni 2017 09:26
  • Hallo Peter,

    stimmt sorry. In der CB schaut es wie folgt aus:

    public sealed partial class Tasks : Page
        {
            private TaskSettingsViewModel _TaskViewModel = new TaskSettingsViewModel();
    
            public Tasks()
            {
                DataContext = _TaskViewModel;
                this.InitializeComponent();
            }
    
            private void ButtonAdd_Click(object sender, RoutedEventArgs e)
            {
                _TaskViewModel.add();
                TextBoxTaskName.Focus(FocusState.Keyboard);
            }
    
            private void ListTasks_SelectionChanged(object sender, SelectionChangedEventArgs e)
            {
                ListTasks.ScrollIntoView(ListTasks.SelectedItem);
            }
    
            private void ButtonFlyoutDelete_Click(object sender, RoutedEventArgs e)
            {
                FlyoutDelete.Hide();
                _TaskViewModel.Remove();
            }
        }

    Wenn ich das MVVM-Prinzip richtig verstanden habe soll im VM nur das Datenhandling stattfinden. Da gehört für mich das Scrollen, der Löschdialog oder das setzen des Fokus beim hinzufügen nicht dazu. Kann das eleganter implementiert werden?

    models:Order verweist auf die Klasse Order die ich oben reinkopiert habe, an sich sind das alles einzelne Dateien gewesen, habe diese zusammengefasst. Werde es eben noch mal anpassen, danke für den Hinweis.

    Das diese Lösung noch nicht das gelbe vom Ei ist habe ich leider selbst schon bemerkt.

    Dienstag, 20. Juni 2017 10:32
  • Hallo,

    wir benutzen MVVM und WPF im Zusammenhang mit WPF-Clientprogrammen, aber ich glaube das macht keinen großen Unterschied. Daher erlaube ich mir hier eine Meinungsäußerung. Grundsätzlich verwenden wir erstmal pro Formular auch ein ViewModel. In Spezialfällen kann man dann mal ein ViewModel für ein zweites Formular wiederverwenden. Aber für die Übersichtlichkeit des Systems halte ich es für sinnvoll, meistens von dieser Regel auszugehen. Dabei kennen die ViewModels der untergeordneten Formulare das Haupt-ViewModel und können auf deren Properties zugreifen. Auch Messaging zwischen den ViewModels ist möglich, was man auch dazu einsetzen kann, doppelten Code zu vermeiden.

    Es gibt auch Formular mit Registerkarten, dann würde ich ein ViewModel pro Registerkarte einsetzen und für jede Registerkarte ein Control definieren.

    Herzliche Grüße

    Peter

    Dienstag, 20. Juni 2017 12:17
  • Hallo Peter,

    danke für deinen Beitrag, Ihr greift auf das übergeordnete VM  zu, sicherlich via Singleton oder gebt Ihr dieses immer mit?

    Wie realisiert man Messaging, verstehe es eben so das wenn sich im aktuellen Model eine Eigenschaft ändert, diese im anderen Model auch angepasst wird? Google liefert mir eben nur Frameworks, würde gerne so lange wie möglich darauf verzichten um erst einmal die Grundlagen zu verstehen und es schlank zu halten.

    Was verwendet Ihr für Listen, ObservableCollection oder List?

    Viele Grüße

    A.Kuller

    Donnerstag, 22. Juni 2017 17:07
  • Hi A.
    unklar ist, was Du mit "übergeordnete VM" meinst. Ich kann mir nicht vorstellen, was eine Verschachtelung von ViewModels bringen soll. Solch eine Verschachtelung ist schwer wartbar und birgt die Gefahr von schwer beherrschbaren Beziehungen.

    Unklar ist auch, was Du unter "im anderen Model anpassen" meinst. Die Oberfläche (View) holt sich IMMER die anzuzeigenden Werte. Damit die Oberfläche weiß, wann das zu machen ist, bietet sich das Interface INotifyPropertyChanged an. Auf Basis von Aktivitäten des Bedieners ändert der betreffende ViewModel den Wert einer Eigenschaft im vom Model bereitgestellten Repository. Wenn andere ViewModels die gleiche Eigenschaft aus dem Repository nutzen, bekommen sie über NotifyPropertyChanged das mit und können dieses Ereignis weiter an gebundene Steuerelemente in der Oberfläche leiten.


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 23. Juni 2017 03:50
  • Hi A,

    erst mal was deinen Speicher verbrauch angeht. Einmal geht der GC hin und reserviert sich größere Block am Speicher als er wirklich brauch. Das liegt daran, das der Prozess des Reservierens relative aufwändig ist. (Wenn ich die Zahl richtig im Kopf habe sind es bei ein 32bit System 16MB und 64bit 32MB, genauen werte kann man über google finden). 

    Dann kommt meines Wissens noch hinzu, das der GC bei nicht Verwalteten Code, nicht genau weiß wie viel Speicher er brauch, sondern da grob schätzt. Und auch nicht genau weiß wann der Speicher wieder Freigegeben wurde. (Das ist jetzt aber eher halb wissen)

    Grundlegend ist da der Taskmanager sehr ungenau. 

    Was dein Problem mit MVVM angeht (Ich hab da jetzt die Beiträge nur überflogen).
    Bei der Konkreten Implementation gibt es schon einige Unterschiedliche Ansätze.

    Ich persönlich rate meistens dazu sich mal Prism vom Microsoft anzuschauen. Man muss das Framework jetzt nicht vermenden (und sollte es auch nicht unbedingt verwenden, da es schon sehr "Schwergewichtig" ist).

    Grundlegend werden dort aber schon mal viele wichtige Punkte angesprochen, die man für eine Anwendung brauchen kann. (Kommunikation zwischen lose gekoppelten Komponente, DI, IoC-Container usw.)

    Vielleicht noch ein paar Anmerkungen.  Ich hatte beim überfliegen, das du ein SingelTon verwendet. Es gibt Bereiche in denen ein SingelTon richtig ist, meist wird es aber falsch verwendet um zB. global Daten zur Verfügung zu stellen, was für einige Probleme sorgen kann. Schau dir da mal lieber DI  und IoC-Container an. (Die Links hab ich mir jetzt nicht durchgelesen, sollte aber einen einstieg geben und sind bei Prisem mit dabei).

    Dann greifst du im VM direkt auf den Context vom EF zu, hier bietet sich meist das Repository Pattern an.

    MFG

    Björn 


     

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:47
    Freitag, 23. Juni 2017 10:53
  • Hallo zusammen,

    danke für euren Input.

    Mit dem übergeordnet bezog ich mich auf den Satz von Peter: "Dabei kennen die ViewModels der untergeordneten Formulare das Haupt-ViewModel und können auf deren Properties zugreifen." Das scheint sich aber jetzt schon mit NotifyPropertyChanged  geklärt zu haben.

    Bzgl. der RAM - Auslastung kann ich mir das jetzt erschließen, fand es sehr unverhältnismäßig das eine kleine Anwendung für sich so viel beansprucht.

    Prism, DI, IoC, Repository, sehr viele Informationen die ich mir erst einmal näher anlesen muss und damit auch arbeiten muss.

    Montag, 26. Juni 2017 12:06
  • Hallo A.,

    ja, das "übergeordnete" VM geben wir als Parameter mit. Muss man sicherlich nicht so machen, Peter, aber wenn man viele gemeinsame Daten hat, ist es sicher übersichtlicher als alles in ein VM zu packen.

    Schlanker ist sicher die Nutzung von Messaging in unserm Fall von GalaSoft.MvvmLight.

    In einer gesonderten Klasse definiert man dazu einen Token, z.B. (wir nutzen VB.NET)

    Namespace Messaging    
    Public Class Channel        
    Public Shared GemeinsameDatenChannelToken As New Object    
    End Class
    End Namespace

    Der Sender führt z.B. folgende Anweisung aus

    Messenger.Default.Send(Of String)(daten, Channel.GemeinsameDatenChannelToken)

    Der Empfänger abonniert die Nachrichten mit

    Messenger.Default.Register(Of String)(Me, Messaging.Channel.GemeinsameDatenChannelToken, AddressOf ShowIncomingData)
    Wenn die Nachricht eintrifft wird ShowIncomingData ausgeführt.

    Herzliche Grüße

    Peter

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:47
    Montag, 26. Juni 2017 13:28
  • Hallo zusammen,

    ich habe mich bisher in das Repository Pattern etwas eingelesen, z.B. fand ich diese Tut's recht ausführlich: 

    Allerdings wird in diesen das Testen komplett extern betrachtet (und in so gut wie allen Beispielen geht es um Webanwendungen), wenn ich das so anwende müsste ich in meiner Anwendung quasi den ganzen Code kopieren und die entsprechenden Stellen auf das Repository anpassen. Finde ich sehr unelegant. So kann ich nicht einfach sagen: Lade mir die Anwendung mit MockData, was für einen Test sinnvoll ist.

    Eine Möglichkeit das zu umgehen wäre, das ich davor noch eine Klasse schiebe die sich um die entsprechenden Repos kümmert. Je nachdem ob die Anwendung getestet oder ausgeführt wird, wird das entsprechende Repo verwendet. Klingt aber auch eher kompliziert. Habt Ihr dazu noch Anregungen? Performancemäßig geht es dann langsam auch Berg ab wenn ich noch weitere Schichten davor schiebe.

    Dann bleibt noch die Frage nach Spezialfällen wie komplexen Queries (z.B. Join mehrerer Tabellen + entsprechende Selektion) und Growing. Muss ich dies dann immer vollständig als Methode ausprägen oder gibt es auch da Generika?

    Peter: Danke, schaue ich mir mal an. Ich habe es bei mir jetzt so gelöst das die entsprechende Page 2 VM's (Page + Global) zugewiesen bekommen hat. Mit DI und IoC habe ich mich noch nicht groß beschäftigt sodass ich diese noch über Singleton verwende.

    Viele Grüße

    A.Kuller


    • Bearbeitet A.Kuller Freitag, 30. Juni 2017 08:14 Adresse -> Link
    Freitag, 30. Juni 2017 08:13
  • Nun wenn du dir DI und IoC noch angeschaut hättest, wäre das die Lösung für dein Problem gewesen.

    Mit DI  Injizierst du Abhängigkeiten in eine Klasse. Dein VM erzeugt sich also das Repository nicht selbst sondern es wird über den Konstruktor (oder über ein Property) in die Klasse gegeben. Bei Unit Test lässt sich das dann ganz einfach Mocken. 

    Mit dem IoC Container kannst du dir dann an einer Zentralen stelle, die Abhängigkeiten auflösen. Die IoC Container haben meist andere Vor- und Nachteile hier musst du dir einfach mal ein paar anschauen und schauen was du brauchst.

    Die Repositorys können ein IQueryable zurückliefern, damit kannst du dann an anderen Stellen die Eigentliche Abfrage schreiben. Mit EF wird dann die Abfrage gegen die Datenbank erst Ausgeführt wenn du z.B. toList aufrufst. Für die CRUD Operationen kannst du eine Generische Basis Klasse verwenden, das ist kein Problem. 

    Für Komplexere Abfragen lohnt es sich meist zusätzlich eine Eigene Methode Bereit zu stellen, wenn du die Abfrage dann an anderen Stellen brauchst musst du nur die Methode aufrufen.

    Es gibt aber den Ansatz im DAL (Reposetorys), die Entitäten (EF "Datenbank tabelle") auf Modelle (Objekt) zu Mappen. Das hat den Vorteil, das du in Zukunft einfacher deine Datenbank ändern kannst und dann nur noch den DAL anpassen musst.

    MFG

    Björn

    Montag, 3. Juli 2017 06:47
  • Hallo Björn,

    danke für deine Antwort. Ich habe mir zwischenzeitlich schon ein wenig Lektüre und ein Videotut (zu IoC und DI) angesehen und etwas rumexperimentiert. Im Video wird hauptsächlich Unity behandelt, nebensächlich auch Autofrac. Unity finde ich auch von der Initialisierung eleganter, aber eine Frage dazu hat sich bisher noch nicht ganz erschlossen, ich muss am Anfang alle Klassen die Instanziiert werden dürfen registrieren, kann dies auch auf später verlagert werden (je nachdem auf welcher Page/View ich mich befinde)?

    Die Repositorys können ein IQueryable zurückliefern, damit kannst du dann an anderen Stellen die Eigentliche Abfrage schreiben. Mit EF wird dann die Abfrage gegen die Datenbank erst Ausgeführt wenn du z.B. toList aufrufst. Für die CRUD Operationen kannst du eine Generische Basis Klasse verwenden, das ist kein Problem. 

    DAL: Das heißt ich baue die Datenbank mit Klassenbeziehungen 1:1 nach und lade die Daten mit EF (dachte ursprünglich dieses ist schon der DAL) und schiebe diese in neue Objekte?

    Viele Grüße

    A.Kuller

    Freitag, 7. Juli 2017 09:58
  • Hi A.,
    DAL = Datenzugriffsschicht bedeutet nicht, dass dort die Datenbank 1:1 nachgebaut und vielleicht auch noch geladen wird. Stelle die vor, die Datenbank hat 100 Tabellen und in jeder Tabelle 1 Mio Datensätze, gesamt vielleicht 10 GB Speicherplatz. Da funktioniert dieser Ansatz nicht.

    Das Datenmodell im DAL beinhaltet üblicher weise nur einen kleinen Auszug aus der Datenbank, was sowohl Tabellen, Spalten und auch Datensätze bedeutet. Jedes Datenmodell im DAL wird für einen konkreten Prozessablauf entwickelt. Es werden damit nur ein paar Spalten und nur ein paar Datensätze geladen, verarbeitet und Änderungen wieder abgespeichert. 


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:46
    Freitag, 7. Juli 2017 10:51
  • Hi A.Kuller,

    am schönsten ist es natürlich, wenn du direkt am Start, die Abhängigkeiten auflöst. Dann hast du genau einer Stelle an dem die Abhängigkeit ändern muss. 

    Grundlegend kannst du dir aber Pro Page/View eine eigene IoC Instanz erzeugen, der dir dann für den Bereich die Abhängigkeiten verwaltet.

    Teils erlauben dir aber auch die Container ihre Abhänigkeiten zu ändern bzw dann auch zu ergänzen.

    Da musst du dir mal anschauen was die Unterschiedlichen IoC Container dir da bieten.

    Zum DAL hat dir Peter ja schon was gesagt.

    • Als Antwort markiert A.Kuller Freitag, 14. Juli 2017 16:46
    Freitag, 7. Juli 2017 14:51
  • Hallo A.Kuller,

    Bist Du weitergekommen? Wenn Deine Frage zufriedenstellend beantwortet wurde, wäre es nett von Dir, wenn Du die hilfreichen Beiträge, die Dich weitergebracht haben, als Antwort markieren würdest.

    Gruß,
    Dimitar


    Bitte haben Sie Verständnis dafür, dass im Rahmen dieses Forums, welches auf dem Community-Prinzip „IT-Pros helfen IT-Pros“ beruht, kein technischer Support geleistet werden kann oder sonst welche garantierten Maßnahmen seitens Microsoft zugesichert werden können.

    Freitag, 14. Juli 2017 13:09
    Administrator
  • Hallo Dimitar,

    momentan bin ich noch am lesen, probieren und verstehen. Aufgrund mangelnder Zeit geschieht dies langsamer wie gedacht. Antworten werde ich eben mal schnell markieren damit das Thema geschlossen werden kann (ist ja doch etwas ausgeartet).

    Vielen Dank an alle Beteiligten bisher.

    VG
    A.Kuller

    Freitag, 14. Juli 2017 16:46