Laden...

Forenbeiträge von Tris Ingesamt 18 Beiträge

12.07.2022 - 14:27 Uhr

Hallo Abt,

Das mit der indizierten View klingt gut, mit dem Thema werde ich mich mal auseinandersetzen. Vielen Dank für den Tipp.
LG Tris

09.07.2022 - 16:51 Uhr

Verwendetes Datenbanksystem: Postgresql (npgsql)

Hallo,
für eine interne Anwendung habe ich in einer Datenbank mehrere Tabellen, die als "Kataloge" (Gerätetypen, Orte, etc) dienen. Die User selbst sollen an den Daten dieser Tabellen keine Änderungen durchführen können. Mich würde gern mal eure Sicht der Dinge interessieren - insbesondere, würdet ihr das überhaupt über "Katalogtabellen" realisieren oder ist eine bessere Herangehensweise, die Kataloge als eigenständige SQLite mit der Clientanwendung (Blazor Wasm/Client-Side, hosted) bereitzustellen und die ausgewählten Katalogeinträge mit deren/einer internen GUID zu hinterlegen (was allerdings keine echten 1:n Relationen ermöglicht).

Hinterlegt man (nicht-hobbymäßig) solche Katalogdaten überhaupt in der gleichen Datenbank, wo auch die eigentlichen Daten liegen oder gibt es hier einfachere und/oder bessere Ansätze?

Danke für's Lesen 🙂
LG Tris

10.01.2019 - 14:07 Uhr

Zuerst einmal muss die "OnInitialized"-Methode aus dem ViewModul in deine app.xaml.cs. Dort dann allerdings parameterlos und als override stehen.

Module-Klassen (abgeleitet von IModule) dienen theoretisch der Entkopplung der Programmbausteine und sind für die Bekanntmachung ihrer (internen) Klassen/Interfaces/Views/Whatever beim IoC/DI-Container verantwortlich. Navigation(Requests) kommen erst viel später in der Kette. Siehe auch Prism Library - Initializing

Auch solltest du die Injizierung des ViewModels (erfolgt bei dir noch manuell in "MainWindowShell.cs") dem Prism-Framework überlassen. Hier gibt es ein Attachend Property namens "AutoWireViewModel", welches in der View (xaml) auf true zu setzen ist. Prism bringt einen eigenen ViewModelLocator mit, der anhand der Namensgebung das ViewModel ermittelt und an DataContext bindet (hierbei werden auch gleich weitere Abhängigkeiten - Konstruktorparameter - aufgelöst und injiziert), siehe Prism Library - ViewModelLocator

"InitializeShell":
Hier registrierst du Sachen, die nichts mit der eigentlichen Initialisierung der Shell zu tun haben, oder?
Ich weiß nicht, ob ich dich hier richtig verstehe. Wenn du ein Wpf-Projekt frisch anlegst, dann wird in der app.xaml schon ein MainWindow angelegt, das initial auch angezeigt wird. Vermutlich ist bei dir deswegen InitializeShell leer. Das funktioniert aber meines Wissens nur, solange der Konstruktor dieses MainWindows parameterlos ist. Das ist bei mir nicht so, weil der sein zugehöriges ViewModel im Konstruktor erwartet. Deswegen muss vorher der ganze DependencyInjection-Kladderadatsch durchlaufen.

Der Verweis auf die StartupURI muss aus der app.xaml raus. Durch die Vererbung von PrismApplication in der app.xaml.cs (die bei Programmstart ja zuerst ausgeführt wird) ist das gleichzeitig der Bootstrapper (s. ersten Link), der dir das Grund-Start-Gerüst deiner Anwendung zusammensetzt und als Abschluss eine Shell (Window) anzeigt.

Kannst du bei deinem Projekt mal schauen, wenn du einen Breakpoint vor RequestNavigate setzt, ob in dem Moment bei dir in regionManager.Regions was drinsteht oder ob die Liste leer ist? Ich bin mir nicht sicher, ob das so sein müsste, kommt mir aber schon sinnvoll vor. Bei mir ist das jedenfalls immer leer.

Hab einen Eintrag drin stehen, der für die ShellRegion.

Grad bei so umfänglichen Frameworks, wie auch Prism, ist die Dokumentation und der Beispielcode echt Gold wert. Hast du Prism Library - Dokumentation schon intensiv durchgearbeitet?

LG Tris

09.01.2019 - 13:13 Uhr

Views und ViewModels sollten auch aus einem anderen Projekt (dll) zu laden sein. Hast du dieses dem "Hauptprogramm" per ModuleCatalog bekannt gemacht? Dort dann auch gleich die Views per

containerRegistry.RegisterForNavigation<ViewName>(eindeutiger_string_für_view)

registriert?

Zu "RegisterTypes" deiner "PrismApplication":
Versuch doch mal, den beiden RegisterForNavigation-Methoden einen eindeutigen String für die spätere Navigation mitzugeben. Der sollte dann, wenn navigiert werden soll, für die RequestNavigate-Methode des RegionManagers erforderlich sein.

"InitializeShell":
Hier registrierst du Sachen, die nichts mit der eigentlichen Initialisierung der Shell zu tun haben, oder? Bei meinen "Projekten" (mega-hobbymäßig, hab's weder studiert noch arbeite ich in ner Softwareschmiede) fehlt die Implementierung komplett, das überlasse ich Prism.

Hier nochmal Beispielcode für die RegisterTypes-Implementierung und der OnInitialized-Impl. (wird aufgerufen, wenn "alle anderen Sachen" erledigt sind und das Window/die ShellView angezeigt wird):

    public partial class App : PrismApplication
    {
        // Modulregistrierung per App.config
        protected override IModuleCatalog CreateModuleCatalog()
        {
            return new ConfigurationModuleCatalog();
        }

        protected override Window CreateShell()
        {
            return Container.Resolve<ShellView>();
        }

        protected override void OnInitialized()
        {
            base.OnInitialized();

            var regionManager = Container.Resolve<IRegionManager>();
            // im "leeren" Window wird DashboardView als "erster Content" geladen/angezeigt
            regionManager.RequestNavigate(RegionNames.ShellRegion, ViewContentNames.DashboardView);
        }

        protected override void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.Register<IDialogCoordinator, DialogCoordinator>();

            containerRegistry.Register<ShellView>();
            containerRegistry.Register<ShellViewModel>();
        }
    }

ViewContentNames enthält statische Strings für die View-Bezeichner, damit sich keine Rechtschreibfehler einschleichen - du kannst natürlich auch einfach Strings nehmen.

Views aus anderen Modulen kannst du dann per registrieren (und mit ihrem Namen ansprechen), wenn sie registriert wurden:

    public class UIModule : IModule
    {
        public void OnInitialized(IContainerProvider containerProvider)
        {
            //
        }

        public void RegisterTypes(IContainerRegistry containerRegistry)
        {
            containerRegistry.RegisterForNavigation<DashboardView>(ViewContentNames.DashboardView);
            containerRegistry.RegisterForNavigation<CustomersView>(ViewContentNames.CustomersView);
            containerRegistry.RegisterForNavigation<CustomerAddEditView>(ViewContentNames.CustomerAddEditView);
            containerRegistry.RegisterForNavigation<SettingsView>(ViewContentNames.SettingsView);
        }
    }

Hoffe, das hilft dir ein wenig weiter. Falls nicht, kannst du ja vielleicht eine abgespeckte Variante deines Programms oder nen bissl mehr Code der Implementierungen einstellen, dann sieht man mehr vom Ablauf.

LG Tris

31.01.2018 - 19:40 Uhr

Danke für die Antworten. Die 3-Schichten-Architektur muss ich mir wohl nochmal genau zu Gemüte führen (scheute mich bisher immer davor, zusätzlich zum DbContext und DbSet des EF noch eine zusätzliche Abstraktionsschicht einzubauen). Aber darauf wird es dann, trotz des Mehraufwands, mal hinauslaufen, danke 😃

30.01.2018 - 16:55 Uhr

Hallo,

ich bin hier eher der stille Mitleser, habe jetzt aber dennoch mal eine Frage zur "best practice".

Angenommen, ich habe einen SQL Server mit einer Personentabelle. Die Personendaten können durch Benutzer geändert werden. Als Wohnort/Plz soll ein Katalogeintrag aus einer lokal vorhandenen (WPF/Client) SQLite-Datenbank ausgewählt werden müssen. Der Primary Key der Ort/PLZ-Auswahl würde dann in der Personentabelle gespeichert werden. Weiterhin sind die Ort/PLZ-Kombinationen in der SQLite-DB nicht durch Benutzer veränderbar, dh, die Dateisignatur wird bei jedem Programmstart geprüft und dieser ggf. verhindert, sobald sie nicht mehr stimmt.

Um nun Personendaten anzuzeigen, würdet ihr eigene (BL?) Personenobjekte als Zwischenschicht zwischen der Daten- und Anzeigeschicht verwenden, die die Daten aus der SQL Server-DB und SQLite-DB zusammenführen oder vielleicht mit anonymen Typen selbst arbeiten? Bisher habe ich immer nur mit den Entity-Objekte aus dem EF Core direkt herumgespielt, also gebunden, etc. Würdet ihr zu so einer Zwischenschicht generell immer raten? Oder bin ich komplett auf dem falschen Weg und der beste Weg ist es, alle Daten zusammen in der SQL Server-Db zu halten?

LG Tris

04.12.2010 - 11:33 Uhr

Hallo,

in meinem Programm würde ich gern die Berichtsgenerierung (ich nutze hierzu PDFSharp/MigraDoc) in Plugins auslagern (je Bericht eine dll). Dazu nutze ich MEF, wobei ich ein Verzeichnis mit dll's bei Programmstart einlese und so die Pluginliste erstelle.

Für Datenbankabfragen etc. nutze ich das EF 4. Durch den Nutzer ausgewählte Datensätze möchte ich als Parameter eines RelayCommands an ein Plugin übergeben, womit dort dann ein Bericht generiert werden kann.

Das alles funktioniert soweit schonmal. Für mich gäbe es jetzt zwei Möglichkeiten, die mir einfielen, wie der Zugriff auf Eigenschaften der übergebenen Datensätze machbar wäre (da das Plugin die durch EF generierten Klassen nicht kennt, ist ein cast ja leider nicht möglich):

  1. Zugriff mittels einer generischen Methode, die einen Datensatz (als object) entgegennimmt und durch Reflection das angeforderte Property (wird als Parameter übergeben) zurückgibt

  2. Abbildung der durch das EF erzeugten Klassen in Interfaces / (Hilfs-)Klassen. Diese kämen in eine eigenständige dll (bpsp. PluginHelper.dll), die vom Hauptprogramm sowie den Plugins verwendet werden würde. Dadurch könnte ich nach der Übergabe von Daten zum Plugin wie von Intellisense gewohnt auf deren Eigenschaften zugreifen.

Pkt. 2 wäre anfangs zwar deutlich mehr Arbeit, da diverse EF - Klassen dann in Interfaces und Klassen abgebildet werden müssten, würde aber den Zugriff ohne die in Pkt. 1 genannte generische Methode ermöglichen.

Wie würdet ihr sowas umsetzen? Alles zu kompliziert? Gibt es andere, einfachere, Lösungsansätze?

Gruß, Tris

21.10.2010 - 09:16 Uhr

Hallo WhiteGloves,

mach aus der List<T> mal eine ObservableCollection<T>. Die List<T> gibt Änderungsbenachrichtigungen an ihren Items nicht weiter, so dass die Oberfläche davon nichts "mitbekommt".

Die ObservableCollection leerst und füllst du dann einfach über .Clear() bzw. .Add() mit neuen Items.

Zumindest hat das bei mir so funktioniert.

Gruß, Tris

20.09.2010 - 17:37 Uhr

Danke für die Antworten. Der Ansatz mit den Klassen klingt interessant, damit werde ich die nächsten Tage mal etwas "herumspielen". 😃

@witte
Ich vermute mal, dass bei Abfrageergebnissen per eSQL diese Änderungsverfolgung, welche ich im Moment durch den Objektkontext/-StateManager habe, fehlt? Das habe ich bisher eigentlich sehr gemocht (und funktionierte alles ohne mein "Zutun" g) und würde nur ungern darauf verzichten.

20.09.2010 - 13:53 Uhr

Hallo,

zur Vorgeschichte:
Ich verwende als DB den MS SQL Server 2008, als Abfragesprache LINQ to Entities mit .NET 4.

Nach einer Loginmaske wird automatisch eine Filter-GUI angezeigt, wo man die Möglichkeit hat, benutzerdefinierte Filter zu erstellen. So kann man seine eigenen Filter, bestehend aus einem FilterField (der Feldname, in dem gesucht werden soll), einem FilterOperator (der Vergleichsoperator) und einer TextBox als Suchbegriff, erstellen.

Um eine gefilterte Abfrage / ObjectQuery zu erstellen, habe ich derzeit so ein riesiges Codegerüst, dass ich mich frage, ob es vielleicht noch eine andere, einfachere Möglichkeit gibt, die ich bisher übersehen habe / nicht kannte.

So schaut's aus:


            // "BaseQuery"
            var fQuery = from a in objContext.auftraege
                         .Include("personen")
                         select vg;


            foreach (var filterItem in currentFilterList)
            {
                switch (filterItem.FilterField.Description)
                {
                    #region Nachname
                    case "Nachname":
                        string strNachname = filterItem.TextValue;

                        switch (filterItem.FilterOperator.Description)
                        {
                            case "enthält":
                                fQuery = fQuery.Where(q => q.personen.Count(p => p.Nachname.Contains(strNachname)) > 0);
                                break;

                            case "beginnt mit":
                                fQuery = fQuery.Where(q => q.personen.Count(p => p.Nachname.StartsWith(strNachname)) > 0);
                                break;

                            case "endet auf":
                                fQuery = fQuery.Where(q => q.personen.Count(p => p.Nachname.EndsWith(strNachname)) > 0);
                                break;
                        }
                        break;
                    #endregion

                    #region Eingangsdatum
                    case "Eingangsdatum":
                        DateTime dtEingangsdatum = filterItem.DateValue;

                        switch (filterItem.FilterOperator.Description)
                        {
                            case "liegt vor / ist gleich":
                                fQuery = fQuery.Where(q => q.int_Eingangsdatum.Date <= dtEingangsdatum.Date);
                                break;

                            case "liegt nach / ist gleich":
                                fQuery = fQuery.Where(q => q.int_Eingangsdatum.Date >= dtEingangsdatum.Date);
                                break;
                        }
                        break;
                    #endregion

                    #region OrdNummer
                    case "OrdNummer":
                        string strOrdNummer= filterItem.TextValue;

                        switch (filterItem.FilterOperator.Description)
                        {
                            case "enthält":
                                fQuery = fQuery.Where(q => q.int_OrdNummer.Contains(strOrdNummer));
                                break;

                            case "beginnt mit":
                                fQuery = fQuery.Where(q => q.int_OrdNummer.StartsWith(strOrdNummer));
                                break;

                            case "endet auf":
                                fQuery = fQuery.Where(q => q.int_OrdNummer.EndsWith(strOrdNummer));
                                break;
                        }
                        break;
                    #endregion
                }

            }

... und das ist nur auszugsweise.

Da es insgesamt 14 Feldnamen gibt, die ihrerseits nach 3 Filteroperatoren gefiltert werden können, sieht der ganze Code echt unübersichtlich aus.

Vielleicht hat von euch jemand einen Tipp / Link mit Codebeispielen, wie ich solche Abfrage-Queries besser generieren kann.

Gruß,
Tris

P.S.
Ich dachte auch schon darüber nach, einfach alle Datensätze ab einem bestimmten Jahr abzurufen und dann per "Filter - Predicate" vom DataGrid rauszufiltern. Das scheint mir aber irgendwie keine gute Idee, zumal das über die Zeit ja nicht unbedingt weniger Datensätze in der DB werden.

07.08.2010 - 09:06 Uhr

Hallo gfoidl,

den UI Scheduler verwende ich, da die Listen an Comboboxen gebunden sind und ohne diesen zur Laufzeit eine Exception geworfen wird.

Die TPL mag ich ungern vergessen, daher war dein Hinweis auf lock genau das Richtige für mich. Vielen Dank, damit funktioniert's jetzt.

Gruß, Tris

06.08.2010 - 09:59 Uhr

verwendetes Datenbanksystem: MS SQL Server 2008

Hallo,

ich versuche, mittels zweier Tasks auf die AdventureWorksLT2008R2 - Datenbank zuzugreifen und zwei Listen (Customer und SalesOrderHeader) zu befüllen.

Mein Code ist in einem ButtonClick - Event hinterlegt:

        private void button1_Click(object sender, RoutedEventArgs e)
        {
            custList.Clear();
            orderheaderList.Clear();


            var uiScheduler = TaskScheduler.FromCurrentSynchronizationContext();


            var custTask = Task.Factory.StartNew<List<Customer>>(() =>
                {
                    var cQuery = context.Customer;

                    return cQuery.ToList();
                }).ContinueWith((task) =>
                    {
                        foreach (var item in task.Result)
                        {
                            custList.Add(item);
                        }
                    }, uiScheduler);

           // Task.WaitAll(custTask)   <-- Anwendung friert ein


            var ohTask = Task.Factory.StartNew<List<SalesOrderHeader>>(() =>
                {
                    var soQuery = context.SalesOrderHeader;

                    // custTask.Wait()   <-- Anwendung friert ein

                    return soQuery.ToList();
                }).ContinueWith((task) =>
                    {
                        foreach (var item in task.Result)
                        {
                            orderheaderList.Add(item);
                        }
                    }, uiScheduler);
        }

Beim Ausführen gibt mir VS 2010 eine Exception mit bspw. Inhalt
"Typ 'AdventureWorksLT2008R2Model.FK_CustomerAddress_Address_AddressID' wurde mehrmals zugeordnet." (immer unterschiedlich, mal "Unter-Entities", mal Referenzen) zurück.

Meiner laienhaften Beurteilung nach kommt der Objektkontext/-StateManager ins Schlingern, wenn Objekte + Unterobjekte/Referenzen durch Task A hinzugefügt werden sollen, dies aber gerade durch Task B geschehen ist. (verbessert mich bitte, ich stecke in der Taskprogrammierung und dem, was drumrum hängt, leider nicht so tief drin, wie ich gern würde 😉 )

Meine Frage jetzt: Gibt es eine Möglichkeit, mit einem Objektkontext und mehrere Tasks Tabellen abzufragen, ohne dass diese sich ins Gehege kommen?

Ein custTask.Wait() bzw. Task.WaitAll(custTask) lassen meine Anwendung einfrieren (im Code auskommentiert)

Danke im Voraus,
Tris

12.01.2010 - 10:00 Uhr

Hallo,
habe das mal in Grundzügen nachgebaut, bei mir funktioniert es.

Mein "SongModel" - speichert einfach nur einen Titel:


    public class SongModel : ViewModelBase
    {
        private string _title;
        public string Title
        {
            get { return _title; }
            set
            {
                _title = value;
                NotifyPropertyChanged("Title");
            }
        }


        public SongModel(string title)
        {
            Title = title;
        }
    }

Hier die Codebehind-Datei von Window1:


    public partial class Window1 : Window
    {
        public ObservableCollection<SongModel> SongCollection { get; set; }

        public Window1()
        {
            InitializeComponent();

            SongCollection = new ObservableCollection<SongModel>();

            SongCollection.Add(new SongModel("Song 1"));
            SongCollection.Add(new SongModel("Song 2"));
            SongCollection.Add(new SongModel("Song 3"));
 
            comboBox1.ItemsSource = SongCollection;
        }
}

Und in der xaml - Datei steht dann u.a. folgendes:


        <ComboBox Height="23" Margin="12,12,12,0" Name="comboBox1" VerticalAlignment="Top" 
                  IsReadOnly="True" IsSynchronizedWithCurrentItem="True"
                  ItemsSource="{Binding}"
                  DisplayMemberPath="Title"/>
        <TextBox Margin="12,101,12,0" Name="textBox1" Height="23" VerticalAlignment="Top" 
                 Text="{Binding SelectedItem.Title, ElementName=comboBox1, UpdateSourceTrigger=PropertyChanged}"/>

Das "ViewModelBase" der SongModel - Klasse kommt von Cinch. Ich war einfach zu faul, INotifyPropertyChanged zu implementieren 😉

Gruß, Tris

11.01.2010 - 08:23 Uhr

Hallo,

dieses Problem hatte ich neulich in einem ähnlichen Fall. Da hatte ich letztendlich "Mode=TwoWay" gelöscht, danach funktionierte alles wie ich wollte.

Woran das liegt, kann ich nur vermuten. Vermutlich versucht die TextBox bei Programmstart, in der ComboBox ein Item mit dem Wert der TextBox.Text - Property zu finden und auszuwählen. Da "Text" beim Start vermutlich leer ist, schlägt da irgendwas fehl und die Anzeige/Binding "zerschießt" sich.

Gruß, Tris

27.09.2009 - 15:28 Uhr

Hallo ErfinderDesRades,

Den Hack verstehe ich als "Newbie" leider noch nicht richtig. Ich wüsste nicht mal, wo genau der hin gehört 😦

Irgendwie scheint mir dieses Entity Framework noch viel zu wirr... Mir will nicht in den Kopf, dass so ein hoch entwickeltes Teil kein Ereignis/Meldung wirft, wenn ein neuer Datensatz darin eingefügt wird, damit Steuerelemente ihren Inhalt updaten können.

Werde wohl doch wieder zu WinForms (und den entsprechenden Datasets/BindingSources) zurück. Eine eigene Zwischenschicht zu schreiben, um beim EF zu bleiben, ist bei mir noch nicht drin.

Vielen Dank für deine Antworten...

Gruß, Tris

26.09.2009 - 14:44 Uhr

Hallo ErfinderDesRades,
erstmal danke für die schnelle Antwort.

Musste leider feststellen, dass ich nicht geschrieben habe, dass mein Testprojekt ein WPF - Projekt ist. Wie würdest du es denn dort lösen (BindingSource's müssten dort ja dann direkt im Code erstellt werden)?

Habe derweil schonmal Google und die msdn Seiten befragt, werde da aber nicht fündig für mein Problem. Befürchte schon, dass ich grundlegend irgendwas falsch mache (gerade beim "Aktuell halten nach dem Einfügen von neuen Daten im EF" müsste es sonst doch schon diverse andere Aufschreie gegeben haben!?).

Gruß, Tris

26.09.2009 - 11:26 Uhr

verwendetes Datenbanksystem: MS SQL Server 2008 (Express)

Hallo zusammen,

ich schreibe im Moment an einem kleinen Testprogramm in C# / spiele mit dem Entity Framework unter Visual Studio 2008 herum.

Folgendes Problem: Das Testprogramm bietet praktisch eine GUI zur Eingabe von Personendaten, die dann in die DB vom SQL Server gespeichert werden. Die Abfrage per LINQ klappt soweit gut. Füge ich allerding ein neues Personenobjekt der Entität "Personen" hinzu, würde ich dies auch gern im DataGrid anzeigen lassen. Nur leider aktualisiert da "irgendwo" etwas nicht...

Liegt es daran, dass ich das Objekt im der Codedatei direkt per "Context.AddToPersonen(...)" hinzufüge? Sollte (muss?) ich solche Aktionen, also bspw. das Hinzufügen, über eine Art Zwischenschicht erledigen und dafür alles neue Klassen schreiben, die die INotifyPropertyChanged - Schnittstelle implementieren (ich hoffe nicht)? Oder lest ihr in solchen Fällen einfach per LINQ die kompletten Datensätze neu aus der DB ein (wäre irgendwie auch etwas zuviel des Guten - vor allem, wenn es später mal ordentlich mehr werden)? Gibt's noch andere Lösungswege für das Problem des Nicht-Aktualisierens?

Gruß, Tris

17.04.2009 - 23:35 Uhr

Hallo zusammen.

Ich spiele zur Zeit ein wenig mit dem DataGrid vom WPF Toolkit herum. Ist das gute Stück nicht auf readonly gesetzt, bietet es ja die Möglichkeit, Zellen direkt zu bearbeiten.
Jetzt zu meinem Problem: Die Benutzereingaben validiere ich soweit schonmal, dass die fehlerhafte Eingaben / Zellen rot umrandet werden. Wie aber prüfe ich in der *.cs - Datei auf Fehler in den DataGrid-Rows?

Ich möchte praktisch auf den üblichen "OK" - Button klicken und dort abschließend dem Benutzer mitteilen, dass es fehlerhafte Eingaben (die rot umrandeten) im DataGrid gibt.

Habe ich irgend ein Property übersehen?

Per Debug konnte ich herausfinden, dass das Grid ein "HasRowValidationError" - Property besitzt, worauf ich per Code aber leider nicht zugreifen kann.

Für Vorschläge und Ideen wäre ich euch dankbar...

Gruß, Tris