myCSharp.de - DIE C# und .NET Community
Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 
 | Suche | FAQ

» Hauptmenü
myCSharp.de
» Startseite
» Forum
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Suche
» Regeln
» Wie poste ich richtig?
» Forum-FAQ

Mitglieder
» Liste / Suche
» Wer ist wo online?

Ressourcen
» openbook: Visual C#
» openbook: OO
» Microsoft Docs

Team
» Kontakt
» Übersicht
» Wir über uns

» myCSharp.de Diskussionsforum
Du befindest Dich hier: Community-Index » Diskussionsforum » Entwicklung » Code-Reviews » Erstes C#, MVVM und SQLite Projekt
Letzter Beitrag | Erster ungelesener Beitrag Druckvorschau | Thema zu Favoriten hinzufügen

Antwort erstellen
Zum Ende der Seite springen  

Erstes C#, MVVM und SQLite Projekt

 
Autor
Beitrag « Vorheriges Thema | Nächstes Thema »
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24


Moritz83 ist offline

Erstes C#, MVVM und SQLite Projekt

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Moin,

wie in meinen beiden anderen Threads bereits angesprochen beschäftige ich mich im Moment mit C#, MVVM, WPF und SQLite (Ich selber habe nur ein wenig VBA (Excel) und Access Erfahrung, sprich dies ist mein erstes Projekt mit C# überhaupt)

Zu meinem "Projekt":
Als Backend dient mir eine SQLite Datei mit 2 Tabellen (Mitarbeiter und Team) und ich möchte jedem Mitarbeiter (ID, Vorname, Nachname) ein Team zuordnen. Dieses Team soll mir im späteren Verlauf eine mandantenfähige Lösung dienlich sein (dazu weiter unten mehr). Das Anlegen ieines neuen Mitarbeiters oder Teams kann via Menu oben vorgenommen warden, das Löschen funktioniert via "Delete". De Änderungen warden allesamt in die SQLite Datei zurück gespielt.
Bitte unter "settings.settings" den Link zur Test.db anpassen bevor das Projekt debuggt wird ;)

Was ich mir wünsche:
Wie bereits gesagt stehe ich ganz am Anfang mit C# und würde mich freuen wenn ihr euch mein kleines Projekt ansehen könntet. Denke da ist eine ganze Menge an Verbesserungspotential vorhanden. Würde mir natürlich auch wünschen das Verbesserungsvorschläge ev. direct an meinem Code aufgezeigt würden.

Zum weiteren Verlauf:
Ich möchte dieses "Miniprojekt" nutzen um eine bestehende Accesslösung abzulösen. Es wird noch 2-3 weitere solche Projekte geben die dann schlussendlich in ein grosses Ganzes übernommen warden … davon bin ich aber 1.) noch weit weit entfernt und 2.) möchte ich so gut als mir möglich Best-Practice betreiben (klar, ein Profi würde das ganz anders angehen)

Link:
<Link entfernt>

Sonstiges:
An dieser Stelle schon mal vielen Dank an alle die meinen Post gelesen haben :)
21.04.2019 20:02 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Bitte stell Dein Projekt auf einer Plattform wie GitHub, GitLab oder Azure DevOps (Public Repository) zur Verfügung. Den Link hab ich entfernt.
Besten Dank!
21.04.2019 20:19 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

au weia, hoffe ich habe das nun richtig gemacht mit GitHub … falls net bitte melden (musste mir erstmal n Account anlegen)

-->  https://github.com/MoritzJuergensen/SQLite (Datenbank befindet sich im Database Ordner --- Pfad muss wie oben angegeben angepasst warden)
21.04.2019 21:32 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
KroaX KroaX ist männlich
myCSharp.de-Mitglied

avatar-4080.jpg


Dabei seit: 31.08.2009
Beiträge: 275
Entwicklungsumgebung: VS2012
Herkunft: Köln


KroaX ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich hab nur mal ganz grob reingeschaut. Ein nächster logischer Schritt wäre ggf. deine Datenabfragen aus deinem Model rauszuholen und somit zu entkoppeln.

Desweiteren ist es empfehlenswert ein Lightweight ORM wie z.B. " Dapper" ( Hier gibt es auch Extensions für SQLite ) einzusetzen damit du nicht selbst den DataReader nutzen musst um die Daten aus der DB in deine Anwendung zu holen.
22.04.2019 14:55 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
MrSparkle MrSparkle ist männlich
myCSharp.de-Team

avatar-2159.gif


Dabei seit: 16.05.2006
Beiträge: 5.186
Herkunft: Leipzig


MrSparkle ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Hier ein paar Tips:

 [Artikelserie] SQL: Parameter von Befehlen
 [Artikel] Drei-Schichten-Architektur
 [Artikel] MVVM und DataBinding
 [Artikel] C#: Richtlinien für die Namensvergabe

Ansonsten macht sowas hier wenig Sinn:

C#-Code:
public void MyMethod(object parameter)
{
  if ((string)paramter == "Team")
    CreateTeam();
  // etc.
}

Erstens sollte man schon den richtigen Datentyp verwenden (in dem Fall string statt object), und zweitens kann man auch direkt die CreateTeam-Methode aufrufen.  Magic Strings sind aber auf keinen Fall zu empfehlen.
22.04.2019 19:00 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

erstmal danke euch Beiden!

@KroaX
Muss ich mir anschauen wie das mit dem ORM funktioniert, lese das hier zum ersten Mal. Yep habe einfach mal "alles" ins ViewModel gepackt, funktioniert aber def. keine optimale Lösung. Hier dran muss ich defitiniv arbeiten

@MrSparkle
denke du sprichst von dieser Methode, oder?

C#-Code:
        private void CreateNewRow(object param)
        {
            string Parameter = (string) param;
            if (Parameter == "Mitarbeiter")
            {
                OC_Mitarbeiter.Add(new Mitarbeiter(99999, "?", "?", 1));
            }
            else if (Parameter == "Team")
            {
                OC_Team.Add(new Team(99999, "?"));
            }
            else
            {
                MessageBox.Show("falscher Parameter");
            }
        }

Die Methode hängt ja am Menu, sollte ich die beiden "Create" Methoden als einzelne Methoden definieren? (Hatte mir das irgendwie über die Zeit unter VBA angewöhnt alles zu verschachteln wenn es ähnliche Sachen sind --- hier wäre in dem Fall eine "CreateTeam" und Eine "CreateMitarbeiter" Methode sinnvoll?)

Zu den Links, Nr. 1 und 4 sind toller Lesestoff aber für 2 und 3 wäre es echt net wenn du mir praktische Beispiele anhand meines Beispiels geben würdest. Denke das würde mir helfen Theorie und Praxis besser miteinander zu verknüpfen. (Natürlich nur wenns net all zu grosse Mühe macht :) )
22.04.2019 22:03 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Im Prinzip brauchst Du hier die Basics von OOP.
Dann kannst Du einfach mit einer generischen Methode sowohl Team wie auch Mitarbeiter darstellen.

Was Du da mit object machst ist im Prinzip völlig untypisiertes Programmieren und daher im Ansatz schon nicht gut.
22.04.2019 22:34 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Habe nun 2 Dinge gemacht:

1.) Ich hab mir endlich n C# Buch (Schrödinger programmiert…) gekauft (und werde nach dem Abschluss dieses MiniProjekts nochmal von 0 anfangen mit dem Buch)
und
2.) Ich habe versucht den Teil mit den Mitarbeitern mit Dapper umzusetzen. Hier erstmal der Code dazu (komplett neu aufgebaut):

Employee.cs

C#-Code:
namespace SQLiteDapper.Model
{
    public class Employee
    {
        public int ID_Mitarbeiter { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Team { get; set; }
    }
}

IEmployeeRepository.cs

C#-Code:
using System.Collections.Generic;

namespace SQLiteDapper.Model
{
    public interface IEmployeeRepository
    {
        List<Employee> GetAll();
        Employee GetById(int id);
        bool Update(Employee employee);
    }
}

EmployeeRepository.cs

C#-Code:
using Dapper;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;

namespace SQLiteDapper.Model
{
    class EmployeeRepository : IEmployeeRepository
    {
        private SQLiteConnection db = new SQLiteConnection(Properties.Settings.Default.connString);
        // Get Employee record by Id
        public Employee GetById(int id)
        {
            return this.db.Query<Employee>("SELECT * FROM Mitarbeiter WHERE [email protected]", new { Id = id }).FirstOrDefault();
        }
        // Retreives the data from the table.
        public List<Employee> GetAll()
        {
            return this.db.Query<Employee>("SELECT * FROM Mitarbeiter").ToList();
        }
        // Update the employee record
        public bool Update(Employee employee)
        {
            string query = "UPDATE Mitarbeiter SET LastName = @LastName WHERE ID_Mitarbeiter = @ID_Mitarbeiter";
            var count = this.db.Execute(query, employee);
            return count > 0;
        }
    }
}

ViewModel.cs

C#-Code:
using SQLiteDapper.Model;
using System.Collections.Generic;

namespace SQLiteDapper.ViewModels
{
    public partial class ViewModel
    {
        public IEnumerable<Employee> Employees { get; set; }
        public ViewModel ()
            {
            // Get data for View Datagrid
            IEmployeeRepository employeeRepository = new EmployeeRepository();
            Employees = employeeRepository.GetAll(); //per Binding ins XAML File
            Employee emp = employeeRepository.GetById(1);
            emp.LastName = "Blödsinn";
            employeeRepository.Update(emp);
        }
    }
}

So, es funktioniert soweitber nun stehe ich aber vor einem Problem das ich ohne Hilfe def. net sauber lösen kann:

Für eine Liste funktioniert onpropertychanged ja nicht. Lohnt es sich die Liste in eine ObservableCollection umzuwandeln (Dapper unterstützt soweit ich weiss OC nicht direkt) oder soll ich diese sehr statischen Daten einfach als "Liste" belassen? (Meinen Datagrid kann ich ja trotzdem ändern und dann per Button oder so den Update Prozess anstossen) --> Falls Ja, wie mache ich das am "Besten" habs versucht aber das klappt bei mir nicht :( )?
24.04.2019 16:56 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Was mir auf die Schnelle auffällt:

- Wenn Du List als Return hast, dann empfange auch List statt IEnumerable ( Intermediate Materialization (C#))
- Prinzipiell ist schon gut, dass Du Datenbanksettings nicht fix hast. Fortgeschritten dann via Dependency Injection
- OOP: Dein Interface ist korrekt nach IEmployeeRepository benannt; man sollte aber der Implementierung die Technik anerkennen. Hier: EmployeeSqliteRepository
Warum: Wenn du mehrere Implementierungen hast (Sqlite, Mssql, Postgres...) sind diese besser zu unterscheiden und einfacher zu überblicken.
- Die Eigenschaft ID_Mitarbeiter kann ganz einfach nur "Id" heissen. Keine Notwendigkeit ein Suffix anzuhängen (und vor allem nich auf Deutsch :-) )
- Die Eigenschaft "Team" ist wohl eher die TeamId, daher auch besser so nennen.
- Bei einem Update ist es üblich(er), dass Du die aktualisierte Entität zurück gibst, statt nur bool
- Bei größeren Anwendungen würde man kein GetAll anbieten, weil die Datenbankgröße niemals vollständig im Memory platz haben könnte.
24.04.2019 17:10 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke für die fixe Antwort!

Das mit der Liste, ID_Mitarbeiter, TeamID und der OOP Benennung habe ich bereits umgesetzt, danke dafür!

Dependency Injection muss ich später mal nachgucken, denke das ist aber imho zweitrangig. Mein grösstes Problem ist das Thema "Update":

In einem Beispiel habe ich folgendes gefunden:

C#-Code:
        private int UpdateStudent(Student student)
        {
            using (var connection = new SqlConnection(sqlConnectionString))
            {
                connection.Open();
                var affectedRows = connection.Execute("Update Student set Name = @Name, Marks = @Marks Where Id = @Id", new { Id = studentId, Name = txtName.Text, Marks = txtMarks.Text });
                connection.Close();
                return affectedRows;
            }
        }

( Originalpost

Sehe ich das richtig das ich jede Eigenschaft überspielen soll? (Beispiel: Ich ändere "LastName" und überspiele dann trotzdem "FirstName" und "TeamID" mit?) Könnte ja rechts das Datagrid einblenden und links dann 2 Textboxen und Eine Combobox mit den Daten füllen. Nach dem Aktualisieren könnte ich via Button den Update Prozess starten.

Oder könnte man nach dem Editieren eines Feldes im Datagrid den Updateprozes starten?




PS:
die grösste Tabelle hat im Moment (nach 2 Jahren) ca. 2000 Einträge, denke GetAll() dürfte da noch funktionieren, oder?
24.04.2019 17:37 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Es macht keinen Sinn 2000 Einträge auf einmal zu laden; Du wirst diese niemals gleichzeitig anzeigen können.

Prinzipiell ja: bei SQL musst Du alle Eigenschaften manuell angeben, die Du aktualisieren willst.
Es gibt aber Dapper Extensions (Dapper.Contrib), die Methoden in vereinfachter Form zur Verfügung stellen, dass Du das SQL nicht manuell schreiben musst.
Projekt ist aber nicht sonderlich aktiv.

Bitte übernehm das Beispiel nicht in dieser Form 1:1.
Das Öffnen von Verbindungen gehört NICHT in eine Abfragemethode.

Wie Du die UI umsetzt ist völlig unabhängig von der Datenbank.
 [Artikel] Drei-Schichten-Architektur
Prinzipiell verwendet man auch andere Klassen in der UI, Logik- und Datenschicht. Selten, dass in der realen Welt alle 3 Layer exakt die gleiche Prseäntation eines Modells haben.

Es gibt auch sehr moderne Ansätze, die überhaupt keine spezifischen Modelle mehr für UI und Logik kennen.
Hier werden jeweils nur die Eigenschaften geladen, die benötigt und in Form von Projektionsklassen bereitgestellt werden.
24.04.2019 18:36 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ne das mit den 2000 war eher auf das Thema Performance bezogen. In der heutigen Lösung werden alle 2000 in einer scrollbaren Liste dargestellt, würde ich so nie wiede realisieren wollen :)

Über das Contrib Projekt bin ich beim googlen auch gestolpert, hatte aber bzgl. der Aktivität ein ähnliches Gefühl wie du … aber bei den paar Eigenschaften spielt es imho noch keine Rolle.

Das Beispiel war eher auf die einzelnen SQL Eigenschaften bezogen, der Rest ist selbst aus meiner Sicht suboptimal gelöst.

Die UI hatte ich nur ins Spiel gebracht weil ja beim vorherigen Beispiel die Inotify Schnittstelle zusammen mit der ObservableCollection alles "selbst" gemacht hat. Ohne die Schnittstelle habe ich ja das Problem das beispielsweise die Daten die dem Team hinterlegt sind nicht mehr automatisch aktualisiert werden sondern ich ja eigentlich bei einer Änderung die Initialisierung erneut anstossen muss (zumindest bei dem was ich bisher zur List gelesen habe)
24.04.2019 21:49 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

stehe jetzt komplett aufm Schlauch :(

Kann mir jemand anhand des neuen Codes zeigen wie mein Code aussehen muss damit bei Änderungen im View (nehmen wir an ich ändere bei einem Mitarbeiter den Nachnamen direkt im Datagrid) die Änderung via OOP in die Datenbank zurück gespeichert werden?
24.04.2019 22:54 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Habs nach langem Würgen hingekriegt, allerdings glaube ich das es net so "gut" ist.

Hier nur die 3 aktualisierten Dateien:

ViewModel.cs

C#-Code:
using SQLiteDapper.Model;
using System;
using System.Collections.Generic;
using System.Windows.Input;

namespace SQLiteDapper.ViewModels
{
    public partial class ViewModel
    {
        public RelayCommand Update { get; set; }
        public List<Employee> Employees { get; set; }
        public Employee MySelectedEmployee { get; set; }
        public ViewModel ()
        {
            // Get data for View Datagrid
            IEmployeeSQLiteRepository employeeRepository = new EmployeeSQLiteRepository();
            Employees = employeeRepository.GetAll(); //per Binding ins XAML
            this.Update = new RelayCommand(_ => UpdateRecord());
        }
        void UpdateRecord()
        {
            IEmployeeSQLiteRepository employeeRepository = new EmployeeSQLiteRepository();
            Employee emp = employeeRepository.GetById(MySelectedEmployee.ID);
            emp.FirstName = MySelectedEmployee.FirstName;
            emp.LastName = MySelectedEmployee.LastName;
            emp.TeamID = MySelectedEmployee.TeamID;
            employeeRepository.Update(emp);
        }
    }

    public class RelayCommand : ICommand
    {
        private Action<object> action;
        public RelayCommand(Action<object> action)
        {
            this.action = action;
        }
        public bool CanExecute(object parameter)
        {
            return true;
        }

        public void Execute(object parameter)
        {
            action(parameter);
        }

        public event EventHandler CanExecuteChanged;
    }
}

IEmployeeSQLiteRepository.cs

C#-Code:
using System.Collections.Generic;

namespace SQLiteDapper.Model
{
    public interface IEmployeeSQLiteRepository
    {
        List<Employee> GetAll();
        //bool Add(Employee employee);
        Employee GetById(int id);
        Employee Update(Employee employee);
    }
}

EmployeeSQLiteRepository.cs

C#-Code:
using System;
using Dapper;
using System.Collections.Generic;
using System.Data.SQLite;
using System.Linq;
using Dapper.Contrib.Extensions;

namespace SQLiteDapper.Model
{
    class EmployeeSQLiteRepository : IEmployeeSQLiteRepository
    {
        private SQLiteConnection db = new SQLiteConnection(Properties.Settings.Default.connString);
        // Get Employee record by Id
        public Employee GetById(int id)
        {
            try
            {
                return this.db.Query<Employee>("SELECT * FROM employees WHERE [email protected]", new { Id = id }).FirstOrDefault();
            }
            catch (Exception exception)
            {
                Console.WriteLine(exception);
                return null;
            }
        }
        // Retrieves the data from the table.
        public List<Employee> GetAll()
        {
        try
            {
                return this.db.Query<Employee>("SELECT * FROM employees LIMIT 20").ToList();
            }
        catch (Exception exception)
            {
                Console.WriteLine(exception);
                return null;
            }
        }
        // Update the employee record
        void Update(Employee employee)
        {
        try
            {
                SqlMapperExtensions.Update(db, new Employee { ID = employee.ID, FirstName = employee.FirstName, LastName = employee.LastName, TeamID = employee.TeamID });

            }
         catch (Exception exception)
            {
                Console.WriteLine(exception);
            }
        }

        Employee IEmployeeSQLiteRepository.Update(Employee employee)
        {
            throw new NotImplementedException();
        }
    }
}

So oder so habe ich hierzu ein paar Sachen die mir nicht klar sind:

- Ist es "sinnvoll" die von Dapper genutzte Liste zu verwenden oder sollte man diese in eine ObservableCollection umwandeln da man dann "INotifyPropertyChanged" nutzen kann? (Würde aber irgendwie Dapper ad absurdum führen in meinen Augen) --> Falls ja, gibt es da eine einfache Möglichkeit?

- Ich führe jetzt das Update eines Mitarbeiters via Button im WPF Formular aus, ist der Code so korrekt mit der ICommand Schnittstelle?

- Generell, ist der Code und der Aufbau so in Ordnung oder gibt es irgendwas "schwerwiegendes" was ich noch ändern sollte?
27.04.2019 22:44 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Datenbank-Objekte werden prinzipiell nicht in UIs verwendet -> Schichtentrennung.
 [Artikel] Drei-Schichten-Architektur

Es ist selten, dass Du alle Felder aus einer Datenbank 1:1 auch so in der UI hast.
Zusätzlich stellt das natürlich eine Abhängigkeit dar, dass jede Änderung an der DB sich durch alle Schichten durchschlägt.

Zusätzlich hast Du keinerlei Logikschickt.
Du hast direkt die Datenbank-Schicht in der UI-Schicht.

Ist pragmatisch; aber natürlich auch risikoreich bei Anpassungen etc.

PS: Select * gilt als Bad Practise, weil Du eben alle Spalten liest - die Du aber evtl. gar nicht brauchst.
28.04.2019 14:54 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke dir für das Feedback.

Habe heute nochmal alles über den Haufen geworfen und wieder von vorne angefangen da ich nicht zufrieden bin .... Macht 0 Sinn weiter zu machen wenn die grundlegendsten Sachen nicht "stimmen".
Habe jetzt ne Möglichkeit gefunden mit Dapper einigermassen ne ObersavbleCollection raus zu kriegen und damit kann ich wieder die Inotify Schnittstelle nutzen.

- das mit dem "*" werde ich so ändern wie du sagst, genau die Sachen raus holen die ich brauche
- das mit der 3 Schichten Architektur schnalle ich nur im Ansatz ein klein wenig aber im Grossen und Ganzen ist das nur ein Fragezeichen für mich (habe schon genug mit MVVM zu tun damit da im Ansatz alles sauber bleibt) aber ich lese mir das mal in aller Ruhe durch und vielleicht kann ich ja einen Teil davon umsetzen



PS: Ich glaube je länger je mehr das so ein Projekt anfangs vielleicht doch 2-3 Nummern zu gross ist...
28.04.2019 16:03 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Papst Papst ist männlich
myCSharp.de-Mitglied

Dabei seit: 28.09.2014
Beiträge: 216
Entwicklungsumgebung: VS2017
Herkunft: Kassel


Papst ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Den Namen deines Repository Interface würde ich generischer halten - warum steht das SQLite im Namen, das kann durchaus Technologie Unabhängig sein!

ObservableCollection kannst du in der GUI schicht durchaus verwenden. An einer DB macht es nur dann Sinn, wenn die DB dich über Änderungen informieren würde.
28.04.2019 18:09 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Habe die letzten Tagen viel gelesen und versucht so viel als möglich umzusetzen und das ganze Projektlein etwas "seriöser" umzusetzen. (DAL, etc) Dapper wurde (entgegen des Titels) noch nicht implementiert, das mache ich erst wenn die Methoden und Co so passen.

Hier der Azurelink -->  https://moritzjuergensen.visualstudio.co...eam/_git/SQL_V1

Ich habe dennoch einige offenen Punkte die mir nicht einleuchten und bei denen mir vielleicht jemand helfen kann:

- die Methoden oder Funktionen im Inferface geben ja etwas zurück, wie bestimme ich ob es beispielsweise ein Bool oder ein Wert ist? (Beispiel: Mitarbeiterupdate ... eigentlich will ich ja nur wissen ob es geklappt hat oder net, sprich ein Bool würde mir genügen oder sehe ich das falsch?)
- wenn jetzt noch mehr Klassen dazu kommen (Beispiel: Projektarten, Projektphasen, etc), macht man dann ein neues Projekt oder "sollte" man Sachen die logisch zusammen passen (Beispiel: Alles was im Konfigurationsmenu ist) in einem Projekt vereinen?


Wäre echt froh wenn Verbesserungen anhand meines Codes dargestellt würden, dann kann ich gleich vergleichen wo ich Fehler gemacht habe.

Vielen Dank an Alle die mir helfen :)
06.05.2019 19:56 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von Moritz83:
wie bestimme ich ob es beispielsweise ein Bool oder ein Wert ist?

Prinzipiell mit dem Rückgabetyp von Methoden.

Im Falle von Datenbank-Operationen gibst Du i.d.R. jedoch das aktualisierte Objekt zurück.
Wenn etwas schief geht, dann wirfst Du eine Exception.

Nur dass etwas schief gegangen ist; das reicht Dir ja nicht.

Zitat von Moritz83:
macht man dann ein neues Projekt oder "sollte" man Sachen die logisch zusammen passen (Beispiel: Alles was im Konfigurationsmenu ist) in einem Projekt vereinen?

 [Artikel] Drei-Schichten-Architektur

Was genau in einzelnen Projekten liegt, das kommt auf das Projekt an.
Wenn es für alles immer eine pauschale Lösung geben würde, bräuchte man keine Entwickler und Architekten. Augenzwinkern
06.05.2019 20:48 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Würdest du mir anhand meiner Update Employee Methode zeigen wie es aussehen sollte? Dann könnte ich analog die anderen Methoden entsprechend ändern.

Werde den 3 Schichten Artikel nochmals lesen und mir überlegen wie ich das in grösserem Ausmasse umsetzen könnte
06.05.2019 22:38 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Erstmal rundum:

- Halte Dich an die C# Guidelines. Parameter in Methoden schreibt man klein; ansonsten schnelle Verwechslungsgefahr mit Eigenschaften.
- Missbrauche den out-Parameter nicht
- Warum Dein Projekt keinen richtigen Namen hat sondern SqlDapper heisst, erschließt sich mir nicht. Warum nennst Du es nicht sowas wie Mitarbeiterverwaltung oder sowas?
Man nennt ja nicht ein Produkt nach dem Namen der eingesetzten Technologie
- der DAL ist nur eine virtuelle Trennung der Verantwortlichkeiten in einer Anwendung. Namentlich existiert dieser aber nicht; weder als Namespace noch als Klasse
- NuGet Packages checkt man nicht mit ein, genauso wenig bin/dbg. Da können Informationen drin sein, die Du nicht teilen willst :-)

Du hast hier eine Mini-Anwendung; im Prinzip ist es sehr einfach, wenn Du Dich an die Grundlagen der Guidelines von C# hälst - dazu musst Du sie Dir aber mal durchlesen :-)
Leider machen das die wenigsten, weswegen strukturelle Probleme oft hier schon die Ursache haben.

Wenn man sich an die wichtigsten Puntke (zB .NET Namespaces und Projekte) hält, dann kommt zB sowas bei raus:
 https://github.com/BenjaminAbt/Sample.DotNetWPFStructure (einfach als Zip ziehen oder mit Hilfe der OctoTree Browserweriterung bequem anschauen).

Du hast eine Klassenbibliothek mit dem erfundenen Namen "Moritz.Mitarbeiterverwaltung", in der die gesamte Logik Deiner Anwendung untergebracht ist - ohne Abhängigkeiten an die Runtime (hier WPF) zu haben.
Hinzu kommt eben die WPF Anwendung hier als Name DesktopApp - die Runtime gehört schließlich nicht in den Projektnamen.
Die Klassenbibliothek enthält hierbei also die Logik und die Datenbankschicht (DAL) - ohne die Schichten namentlich zu nennen.

Die Namen der Klassen und Interfaces helfen Dir beim Faktor Modularisierung.
Die Logik interessiert nur, dass es ein Repository gibt, mit dem Mitarbeiter veraltet werden können (IEmployeeRepository) und ist hierbei so platziert, dass sie auch im Namespace neutral liegt.

Die konkrete Implementierung liegt nun in einem spezifischen Namespace (Sqlite) und trägt auch einen spezifischen Namen (EmployeeSqliteRepository).
Entscheidest Du Dich später statt Sqlite eben zB MSSQL zu verwenden, dann ist dies sehr einfach durch einen eigenen Namespace erreichbar inkl. den spezifischen Implementierungen (EmployeeMssqlRepository).

Die Namespaces haben dabei die Struktur, dass Du sie jederzeit in extra Projekte auslagern könntest, um eine höhere Unabhängigkeit von Dependencies (eben Mssql, Sqlite...) zu haben.
Lohnt sich dann aber erst wenn man es wirklich braucht.

Dieses Basis ermöglicht dann die Anwendung von Prinzipien wie Dependency Injection und erfüllt die 3- Schicht Architectur.
Du kannst nun nämlich die Logik aus einer WPF-App, Konsolen-App oder Web-App ansprechen.

Zur Update Methode: sofern es sich um eine einfache Anwendung handelt:
- Entität gegen die Datenbank aktualisieren
- Entität aus der Datenbank lesen und in der Methode zurück geben

Im Endeffekt müssten aber alle Deine Repositories komplett umgeschrieben werden.
Du hast ein Repository, in dem eigentlich die Datenbank-Operationen liegen sollte, dann aber noch eine DAL Klasse. Erschließt sich mir nicht.

C#-Code:
//Add a new employee
        public ObservableCollection<Employee> AddEmployee(ObservableCollection<Employee> OCEmployees)

Die Methode hat namentlich "Füge einen Employee hinzu" - Du gibst ihr aber eine ganze Liste.

Es reicht völlig (mit Dapper, anders macht es keinen Sinn - keiner will Sql Code schreiben)

C#-Code:
    class EmployeeSqliteRepository : IEmployeeRepository
    {
    IDbConnection _connection; // Sqlite implementiert IDbConnection. Siehe ADO.NET

    public EmployeeRepository (IDbConnection connection) // Die Connection wird übergeben und nicht im Repository erstellt! Eine Connection können sich mehrere Repository-Instanzen teilen.
    {
        _connection = connection;
    }

    public IList<EmployeeEntity> GetAll()
    {
        _connection.GetAll<EmployeeEntity>().ToList(); // GetAll = Dapper
    }


        public bool Update(EmployeeEntity e)
        {
        return connection.Update(e);
        }
    }

Und hier sieht man direkt, dass man das ganze generische Schreiben kann (OOP)

C#-Code:
    class SqlRepository<TEntity> : ISqlRepository<TEntity>
    {
    IDbConnection _connection;

    public SqlRepository (IDbConnection connection)
    {
        _connection = connection;
    }

    public IList<TEntity> GetAll()
    {
        _connection.GetAll<TEntity>().ToList(); // GetAll = Dapper
    }


        public bool Update(TEntity e)
        {
        return connection.Update(e);
        }
    }

public class EmployeeRepository : SqlRepository<EmployeeEntity>
{
    public EmployeeRepository (IDbConnection connection) : base(connection)
    {

    }
}
public class TeamRepository : SqlRepository<TeamEntity>
{
    public TeamRepository (IDbConnection connection) : base(connection)
    {

    }
}

Man also den strukturellen Code nur ein mal schreiben muss.

Im Endeffekt ist das also nur eine Mischung aus den C# bzw. .NET Guidelines und Grundlagen von Objekt-orientierter Programmierung :-)
Aber ja, man muss sich diese Dinge durchlesen, im Kopf sortieren und abstrahieren. Kommt alles mit der Zeit.
06.05.2019 23:13 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Vorab:

Vielen lieben Dank für das mehr als ausführliche Feedback!!
Die Quintessenz daraus ist für mich ganz klar: Nochmals über die Bücher und ganz von vorne anfangen. Irgendwie sind viele Sachen zwar in den Kopf rein aber leider auch wieder raus; hast du mit deinem letzten Satz auch wunderbar getroffen

Zitat:
Aber ja, man muss sich diese Dinge durchlesen, im Kopf sortieren und abstrahieren. Kommt alles mit der Zeit.

Nun zum detaillierten Feedback:

Das mit dem Projektnamen habe ich ehrlich gesagt ignoriert weil ich erst mit Dapper gespielt hatte. Wird natürlich so auch nicht mehr gemacht. Mit meiner Interpretation von DAL war ich wohl auf dem Holzweg, hatte das so in einem Lernvideo gesehen und dachte das es sich so gehört.

Mit dem Einchecken muss ich mich explizit nochmals befassen, hatte einfach den Hauptordner angeklickt und dann veröffentlicht.

(Zum Thema Guidelines durchlesen: Witzig ist das ich mein Accessprogramm erst auch kreuz und quer aufgebaut hatte und erst später eine "organisierte und aufgeräumte" Version erstellt habe .... anstatt direkt von Anfang an ... neige zur Chaostheorie)

Das mit der DAL Klasse ist simpel (wenngleich auch total falsch): Ich dachte ich müsse sämtliche Datenbankzugriffe (den SQL Code) in eine eigene Datei auslagern, deshalb dieser "Murks".

Ich muss mir nochmals ganz in Ruhe die Grundlagen durchlesen und dann deinen Code neu interpretieren.

Der Teil mit den Listen holen und die Entity updaten ist logisch (auch wenn ich den Codesyntax noch nicht 100% verstehe) aber bei "public EmployeeRepository (IDbConnection connection) : base(connection)" ist Schluss. Vermute mit ":base(Connection)" implementierst du etwas wo die Verbindungsdaten geregelt sind, liege ich damit richtig?


Auf alle Fälle, vielen vielen Dank für deine Bemühungen ... weiss es echt zu schätzen smile
07.05.2019 11:25 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
inflames2k inflames2k ist männlich
myCSharp.de-Poweruser/ Experte

avatar-3407.gif


Dabei seit: 03.01.2010
Beiträge: 2.205
Entwicklungsumgebung: Visual Studio 2010 Express


inflames2k ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von Moritz83:
aber bei "public EmployeeRepository (IDbConnection connection) : base(connection)" ist Schluss. Vermute mit ":base(Connection)" implementierst du etwas wo die Verbindungsdaten geregelt sind, liege ich damit richtig?

EmployeeRepository erbt von der Basisklasse SqlRepository<EmployeeEntity>. Der Aufruf am Ende des Konstruktors bewirkt, dass der Konstruktor der Basisklasse aufgerufen wird.

Auf diese weise wird ganz gut redundanter Code vermieden und viel wichtiger: Die abgeleitete Klasse muss die Datenzugriffsschicht außer im Konstruktor gar nicht kennen.
07.05.2019 12:07 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Danke für die Erklärung :)

@Abt
vielen Dank für den Github Link!
07.05.2019 14:14 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ich habe die letzten Tage versucht das Beispiel von Abt (OOP) in das von ihm netterweise zur Verfügung gestellte Rahmenprojekt einzubauen. Mir erschliessen sich noch einige Sachen nicht so wirklich, darum brauche ich (mal wieder) euren Rat.

Die Struktur hat sich leicht verändert im Gegensatz zum Originalprojekt (siehe Bild im Anhang)


Der Inhalt der einzelnen Dateien sieht nun wie folgt aus:
EmployeeEntity.cs

C#-Code:
namespace Toolbox.Employee.Database.Entities
{
    class EmployeeEntity
    {
    }
}

EmployeeRepository.cs

C#-Code:
using System.Data;
using Toolbox.Employee.Database.Entities;

namespace Toolbox.Employee.Database.Sqlite.Repositories
{
    class EmployeeRepository : SqlRepository<EmployeeEntity>
    {
        public EmployeeRepository(IDbConnection connection) : base(connection)
        {
            GetAll();  //BEISPIEL
        }
    }
}

IEmployeeRepository.cs

C#-Code:
namespace Toolbox.Employee.Database.Sqlite.Repositories
{
    class IEmployeeRepository
    {
    }
}

ISqlRepository.cs

C#-Code:
namespace Toolbox.Employee.Database.Sqlite.Repositories
{
    internal interface ISqlRepository<TEntity>
    {
    }
}

SqlRepository.cs

C#-Code:
using Dapper.Contrib.Extensions;
using System.Collections.Generic;
using System.Data;
using System.Linq;

namespace Toolbox.Employee.Database.Sqlite.Repositories
{
    class SqlRepository<TEntity> : ISqlRepository<TEntity> where TEntity : class
    {
        IDbConnection _connection;

        public SqlRepository(IDbConnection connection)
        {
            _connection = connection;
        }

        public IList<TEntity> GetAll()
        {
            return _connection.GetAll<TEntity>().ToList(); // GetAll = Dapper
        }

        public bool Update(TEntity e)
        {
            return _connection.Update(e);
        }
    }

}

DbSqliteContext.cs

C#-Code:
namespace Toolbox.Employee.Database.Sqlite
{
    class DbSqliteContext
    {
    }
}

IDbContext.cs

C#-Code:
namespace Toolbox.Employee.Database
{
    class IDbContext
    {
    }
}

Employee.cs

C#-Code:
using System.ComponentModel;

namespace Toolbox.Employee.Models
{
    public class Employee
    {
        //Employee ID -- Auto Increment (Database)
        public int Id { get; set; }
        private string _firstName;
        public string FirstName
        {
            get => _firstName;
            set
            {
                if (_firstName != null && _firstName != value)
                {
                    _firstName = value;
                }
            OnPropertyChanged("FirstName");
            }
        }
        private string _lastName;
        public string LastName
        {
            get => _lastName;
            set
            {
                if (_lastName != null && _lastName != value)
                {
                    _lastName = value;
                }
                OnPropertyChanged("LastName");
            }
        }
        //TeamId -- Foreign Key
        private int _teamId;
        public int TeamId
        {
            get => _teamId;
            set
            {
                if (_teamId != value)
                    {
                    _teamId = value;
                    }
                else
                {
                    return;
                }
                OnPropertyChanged("TeamId");
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;
        public void OnPropertyChanged(string propertyName)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Team.cs

C#-Code:
namespace Toolbox.Employee.Models
{
}

EmployeeService.cs

C#-Code:
using System;
using System.Collections.Generic;
using System.Text;

namespace Toolbox.Employee.Services
{
    class EmployeeService
    {
    }
}

Soweit die Theorie, nun habe ich mir dazu ein paar Gedanken gemacht, recherchiert und mir sind einige Dinge unklar und bräuchte weitere Hilfe:

- um eine generische Methode nutzen zu können muss ich ja eine Entity definieren. Ist es richtig das ich in der Datei "EmployeeEntity.cs" definiere aus was diese Entity besteht (beispielsweise ID, Vorname, Nachname) und dann in der Datei "Employee.cs" beschreibe das der Vorname ein String ist und wie get/set definiert sind?

- damit ich die IDbConnection "connection" nutzen kann muss ich ja irgendwie definieren wohin sie connecten soll. Wo wird das definiert (in welcher Datei) und vor Allem, die Verbindung sollte ja nach erfolgtem Update oder was auch immer wieder geschlossen werden.

- wenn ich in der Datei "EmployeeRepository.cs" "GetAll" (gekennzeichnet mit Beispiel) aufrufen will, warum ist dort keine Angabe der Entity notwendig? (oder ist es so wie ich vermute und ich starte von dort gar nicht den Aufruf?)

- ausgehend von der MVVM Idee, entspricht "EmployeeService.cs" dem ViewModel für die Employee Sachen?

Moritz83 hat dieses Bild (verkleinerte Version) angehängt:
Anmerkung.png
Volle Bildgröße

14.05.2019 14:23 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von Moritz83:
Ist es richtig das ich in der Datei "EmployeeEntity.cs" definiere aus was diese Entity besteht (beispielsweise ID, Vorname, Nachname) und dann in der Datei "Employee.cs" beschreibe das der Vorname ein String ist und wie get/set definiert sind?

Si.

C#-Code:
public class EmployeeEntity : Entity
{
    public int Id {get;set;}
    public string Vorname {get;set;}
    public string Nachname {get;set;}
    // ...
}

Zitat von Moritz83:
Wo wird das definiert (in welcher Datei) und vor Allem, die Verbindung sollte ja nach erfolgtem Update oder was auch immer wieder geschlossen werden.

Eigentlich übernimmt das eben die Dependency Injection für Dich.
services.AddTransient<IDbConnection>(_ => return new SqliteConnection("connection string..)".

Verwendest Du keine Dependency Injection aktuell, dann musst Du das selbst instantiieren und auch selbst verwalten (Disposen=>Closen).

Oft wrappt man die Verbindung selbst in einem "DbContext".
D.h. der DbContext kennt die Verbindung und das Repository der DbContext.
Der DbContext hat darüber hinaus die Kenntnis über alle Tabellen, die die Repositories nutzen können.
Das ist das, was Du da im Screenshot auch siehst.

Zitat von Moritz83:
warum ist dort keine Angabe der Entity notwendig

Generics =>  Generic Classes (C# Programming Guide)

Zitat von Moritz83:
- ausgehend von der MVVM Idee, entspricht "EmployeeService.cs" dem ViewModel für die Employee Sachen?

Nein. Ein Service ist Bestandteil der Logik, nicht der View.

Das ViewModel kann/darf aber den Service kennen und kann drauf zugreifen.
14.05.2019 14:47 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Woher kommt denn das ": Entity"?

C#-Code:
public class EmployeeEntity : Entity

Müsste ich das nicht auf das Employee.cs File im Model Ordner referenzieren?
14.05.2019 15:59 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Ah, ist hier gar nicht drin - sorry.

Man kann eben Vererbung nutzen, um generische Einschränkunge zu realisieren.

Wenn man folgendes schreibt...

C#-Code:
class SqlRepository<TEntity> : ISqlRepository<TEntity> where TEntity : class

.. dann kann man hier als TEntity alles einwerfen, was eine Klasse ist.

Wenn man schreibt

C#-Code:
class SqlRepository<TEntity> : ISqlRepository<TEntity> where TEntity : class, Entity

schreibt, dann man kann nur Klassen als Generic übergeben, die von Entity erben (bzw. kann man auch einfach als Interface deklarieren).

C#-Code:
public interface IEntity {}
public abstract class Entity : IEntity {}

public class EmployeeEntity: Entity {}

class SqlRepository<TEntity> : ISqlRepository<TEntity> where TEntity : class, IEntity // oder Entity wenn man kein Interface hat
14.05.2019 16:20 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

irgendwas geht sich bei mir net aus.

Versuche mal aufzuzeigen wo meine Denkblockade ist:

In der Datei "SqlRepository.cs" sind die Datenbankaugaben wie "alle Einträge holen" oder "Eintrag updaten" abgelegt. Der Code

C#-Code:
    class SqlRepository<TEntity> : ISqlRepository<TEntity> where TEntity : class, Entity
    {
        //BLIBLABLUB
    }

besagt das es sich um eine Klasse "SqlRepository" handelt die als Parameter eine TEntity erfordert (stellt in diesem Fall Mitarbeiter oder Team dar), die vom Interface "ISqlRepository" abhängig ist und der Parameter nur gültig ist wenn es eine Klasse ist die von Entity erbt -- in deinem Beispiel

C#-Code:
public class EmployeeEntity: Entity {}

Die Klasse "EmployeeEntity" erbt von Entity und sie wie folgt aus:

C#-Code:
public class EmployeeEntity : Entity
{
    public int Id {get;set;}
    public string Vorname {get;set;}
    public string Nachname {get;set;}
    // ...
}

Sprich hier ist definiert welche Eigenschaften vorhanden sind

Widerspruch 1 für mich: Wieso brauche ich denn ein Model "Employee.cs" in dem fast das Gleiche steht? Kann es ja net doppelt definieren --> Annahme: Employee.cs fällt weg

Widerspruch 2 für mich: Das mit dem " : Entity" Zusatz verstehe ich gar nicht. Was soll denn da drin stehen?--> Annahme: Gemäss folgendem  Link (abstrakte Klasse) vermute ich das diese Klasse Sachen beinhaltet die für beide Entities (Team oder Mitarbeiter) relevant sind, wobei sich mir hier net erschliesst was das sein soll



Die ganz ganz groben Züge erschliessen sich mir langsam aber irgendwie bleibt jedesmal ein Fragezeichen, daher vielen Dank das Ihr meine "blöden" Fragen so umfassend beantwortet :)
Hoffe das ich mir eurer Hilfe bald eine funktionierende Klassenbibliothek habe mit der ich das ganze nochmals durchgehen kann; sprich den kompletten Workflow innerhalb der Klasse abarbeiten kann.
14.05.2019 18:25 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von Moritz83:
Widerspruch 1 für mich: Wieso brauche ich denn ein Model "Employee.cs" in dem fast das Gleiche steht?

Das mag in Deinem sehr einfachen Beispiel so sein; die Regel ist, dass Entität und Modell nicht identisch sind.
Beispiel: Du hast in einer UserEntity zB. E-Mail und Password.
Das willst Du mit Sicherheit nicht in Deinem Model "User" haben.

Aber wie bereits gesagt: pragmatisch bleiben ist legitim.

Zitat von Moritz83:
Das mit dem " : Entity" Zusatz verstehe ich gar nicht. Was soll denn da drin stehen?

Das sind generische Einschränkungen.
Siehe Erklärung oben. Das ist ein wichtiges Element in OOP von/mit .NET.

Damit schränkst Du eben ein, dass eine Klasse nur mit den generischen Klassen funktioniert, die Du definierst.
Du musst Dich wirklich ein wenig mit den Themen beschäftigen...

Aber hier nochmal:

C#-Code:
class SqlRepository<TEntity> : ISqlRepository<TEntity> where TEntity : class, IEntity

class EmployeeRepository : SqlRepository<EmployeeEntity>, IEmployeeRepository

Damit definierst Du, dass TEntity eine Klasse sein muss, die das Interface IEntity implementiert.

C#-Code:
public class EmployeeEntity : Entity // Entiy implementiert IEntity

wird nun funktionieren, weil Du die Constrainst erfüllst.

C#-Code:
public class EmployeeEntity {}

wird ein Fehler verwerfen, weil Du nirgends IEntity implementierst.
In diesem Fall führen die Constraints eben dazu, dass Du garantiert in einem Repository eine Klasse verwenden musst, die zwangsweise eine Entität in Form der Implementierung von IEntity ist.

Das ganze kann man natürlich jetzt noch weiter Spinnen, dass Du IEntity gewisse Inhalte gibst.

C#-Code:
public interface IEntity
{
   int Id {get;}
}

Du gibst also zB Deiner Entität immer vor, dass sie eine Eigenschaft id hat, die immer ein int ist.
Das sorgt dafür, dass Du in Deinem SqlRepository bereits auf die Eigenschaft id zugreifen kannst ohne die konkrete Klasse zB Employee zu kennen.
Damit kannst Du allen ableitenden Repositories zB ein GetById() zur Verfügung stellen ohne das jedes Mal im spezifischen Repository schreiben zu müssen - was aber eben die Gemeinkeit jeder Entität voraussetzt, dass es immer eine Id-Spalte gibt.
14.05.2019 20:21 Beiträge des Benutzers | zu Buddylist hinzufügen
Moritz83
myCSharp.de-Mitglied

Dabei seit: 27.05.2013
Beiträge: 24

Themenstarter Thema begonnen von Moritz83

Moritz83 ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von Abt:
Aber wie bereits gesagt: pragmatisch bleiben ist legitim.

Ja ich muss irgendwo ne Grenze ziehen, ansonsten habe ich zu viel drin was ich nicht wirklich verstehe. Habe jetzt schon Schwierigkeiten den ganzen Sachen zu folgen.

Zitat von Abt:
Du musst Dich wirklich ein wenig mit den Themen beschäftigen...

und damit wären wir bei Problem Nummer 2: Ich für meinen Teil finde es extrem schwierig anhand von abstrakten Erklärungen daraus die richtigen Schlussfolgerungen zu ziehen, zumal mir hier im RL auch niemand wirklich helfen kann. Beispielsweise dein Link zu den generischen Klassen.
Klar steht dort sehr viel und vermutlich auch sehr gut erklärt aber für mich als Laien doch schwer zu interpretieren und dann in ein funktionierendes Beispiel einzusetzen.

Wenn ich ein konkretes Beispiel habe (wie jetzt in meinem Fall die aus deiner Sicht sicherlich sehr simple Anwendung) dann kann ich anhand meines Codes die Theorie nachvollziehen, resp. ich kann es vermutlich besser als andersrum.

Die Idee mit "GetByID" greife ich auf, zumal ich in meiner Anwendung in jeder Tabelle eine eindeutige ID habe und auch haben werde.

Vielen Dank für deine Geduld! :) Ich denke ich werde jetzt erstmal wieder über die Bücher und versuche deinen umfangreichen Input irgendwie zu interpretieren und dann umzusetzen bevor ich auch nur daran denken sollte weiter zu machen. Werde auch im Buch weiter machen und parallel die Grundlagen nochmals durchlesen und vertiefen.
14.05.2019 22:12 E-Mail | Beiträge des Benutzers | zu Buddylist hinzufügen
Abt
myCSharp.de-Team

avatar-4119.png


Dabei seit: 20.07.2008
Beiträge: 12.948
Herkunft: Stuttgart/Stockholm


Abt ist offline

Beitrag: beantworten | zitieren | editieren | melden/löschen       | Top

Zitat von Moritz83:
Die Idee mit "GetByID" greife ich auf, zumal ich in meiner Anwendung in jeder Tabelle eine eindeutige ID habe und auch haben werde.

Nicht nur eine eindeutige Id, sondern die Spalte muss auch überall identisch heißen (eben Id).

Am Ende vom Tag kann das so aussehen:

C#-Code:
    public interface IDbContext
    {

    }

    public class DbContext : IDbContext
    {

    }

    public interface IEntity
    {
        int Id { get; }
    }

    public abstract class Entity : IEntity
    {
        public int Id { get; set; }
    }

    public interface ISqlRepository<TEntity> where TEntity : class, IEntity
    {
        IList<TEntity> GetAll();
        int Count();
        TEntity GetById(int id); // Wir wissen ja, Id muss int sein
        bool Delete(TEntity entity);
        bool Delete(int id);
    }

    public abstract class SqlRepository<TEntity> : ISqlRepository<TEntity> where TEntity : class, IEntity
    {
        protected IDbContext DbContext { get; }
        protected abstract string TableName { get; }
        protected abstract string IdFIeld { get; }

        protected SqlRepository(IDbContext dbContext)
        {
            DbContext = dbContext;
        }

        public IList<TEntity> GetAll()
        {
            return DbContext.Connection.GetAll<TEntity>();
        }

        public TEntity GetById(int id)
        {
            return DbContext.Connection.Get<TEntity>(id);

            // wenn man kein Dapper.Contrib hat/will:
             string sql = $"SELECT * FROM {TableName} WHERE {IdFIeld} = @Id";
             TEntity entity = DbContext.Connection.Query<TEntity>(sql, new { Id = entity.Id }); // hier braucht man eben nun die definierte Id von IEntity.Id
             return entity;
        }

        public int Count()
        {
            string sql = $"SELECT COUNT(*) FROM [{TableName}];";
            return DbContext.Connection.ExecuteScalar<int>(sql)
        }

        public bool Delete(TEntity entity)
        {
            return DbContext.Connection.Delete(entity)

            // wenn man kein Dapper.Contrib hat/will:
             var sql = $"DELETE FROM {TableName} WHERE {IdFIeld} = @Id";
             var affectedrows = DbContext.Connection.Execute(sql, new { Id = entity.Id }); // hier braucht man eben nun die definierte Id von IEntity.Id
        }

        public bool Delete(int id)
        {
            var sql = $"DELETE FROM {TableName} WHERE {IdFIeld} = @Id";
            var affectedrows = DbContext.Connection.Execute(sql, new { Id = id });

            return affectedrows > 0;
        }
    }


    public class EmployeeEntity : Entity
    {
        public string Vorname { get; set; }
        public string Nachname { get; set; }
    }

    public interface IEmployeeRepository : ISqlRepository<EmployeeEntity>
    {

    }

    public class EmployeeRepository : SqlRepository<EmployeeEntity>, IEmployeeRepository
    {
        public EmployeeRepository(IDbContext dbContext) : base(dbContext)
        {
        }

        // Tabellenname für selbst geschriebene SQL Commands, die Dapper.Contrib nicht unterstützt
        // ansonsten muss eben in Dapper.Contrib via Attribute oder Mapper Configuration der Tabellenname hinterlegt werden
        protected override string TableName { get; } = "Employees";
        protected override string IdField { get; } = "Id";
    }

Ein mal die richtige OOP Basis und Du hast nur noch den Aufwand für das Definieren der Enitäten und das Anlegen des Repositories.
Die Standard SQL Methoden bekommst Du dann durch die Vererbung einfach geschenkt.
14.05.2019 22:53 Beiträge des Benutzers | zu Buddylist hinzufügen
Baumstruktur | Brettstruktur       | Top 
myCSharp.de | Forum Der Startbeitrag ist älter als 4 Monate.
Der letzte Beitrag ist älter als 4 Monate.
Antwort erstellen


© Copyright 2003-2019 myCSharp.de-Team | Impressum | Datenschutz | Alle Rechte vorbehalten. | Dieses Portal verwendet zum korrekten Betrieb Cookies. 19.09.2019 20:57