Entity Framework - BusinessLogic - Aufbau

  • C#

Es gibt 10 Antworten in diesem Thema. Der letzte Beitrag () ist von Nofear23m.

    Entity Framework - BusinessLogic - Aufbau

    Hallo,

    vorweg ich gestalte ein Projekt in MVVM mit EF in der WPF. Da es aber hier hauptzächlich um die Verbindung von EntityFramework zu der BusinessLogic gehen soll, habe ich es in diese Themenkategorie gesteckt - hoffe das ist richtig.

    Also ich erkläre am besten mal kurz mein Projekt:

    Es geht grob darum, dass mehrere Benutzer Songs in eine Datenbank eingeben können. Diese Songs werden einem Genre und einer "OrderList" zugeordnet. Eine "OrderList" kann man sich wie eine Playlist vorstellen. Diese hat außerdem noch einen Status.
    Hier mal mein Datenmodell:


    Ich denke das ist soweit verständlich.

    Das Programm soll so funktionieren:
    Wenn man das Programm startet, soll ein LogIn Fenster kommen. Wenn man korekte LogIn Daten angegeben hat soll der Benutzer angemeldet werden und Zugriff auf die Songliste erhalten. Er kann dann neue Songs hinzufügen, bearbeiten und löschen (also ich brauche keine gesonderten Berechtigungen).
    Wenn eine Änderung gemacht wurde, soll diese protokolliert werden.

    In meinem Projekt habe ich jetzt schon die Layer "DBContext", "BusinessLogic (BI)", "View", "Model" und "ViewModel" angelegt.

    Gut zu meiner Frage:
    Ich weiß jetzt nicht wie ich mit dem Datenabruf und dem Aufbau der BI anfangen soll:
    Also wie aus einem vorherigen Thread herausgeht, brauche ich jetzt eine LoginBI. Aber was sollen da jetzt für Methoden rein. Ich denke mal ​LogInUser() oder so.
    Aber wie läuft das dann ab. Wie gebe ich den eingelogten User an die nächste BI weiter und wie soll ich die dann nennen. SongOverviewBI?

    Ich hoffe das ist jetzt nicht zu verwirrend :D

    Ach ja das Projekt könnt ihr euch unter dem folgenden Link auf GitHub anschauen: github.com/florian03-1/MusicManager

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hi,
    ich hab es bei mit so:
    die BL wird ja vom ViewModel aus aufgerufen, dazu hab ich ein LogInViewModel.
    die LogInUserBL gibt mir einen int zurück, dieser int ist die UserID, diese speichere ich in ein Property welches in meinem ApplicationViewModel steckt dieses ApplicationViewModel ist über DependencyInjection überall erreichbar.
    meine anderen BL Methoden benötigen alle als Paramater die UserID.
    sonit hab ich immer den passenden User nach dem LogIn.

    zum Beispiel könntest Du eine BL Methode names GetAllSongs haben, dieser übergibst du dann die UserId die dann als Filter für deine DB Abfrage gilt:
    ctx.Songs.Where(s => s.UserID == userId).ToList()
    "Hier könnte Ihre Werbung stehen..."
    Hallo,

    ok, vielen Dank für deinen Ideenanstoß.
    Ich müsste aber mal fragen was du mit DependencyInjection meinst. Kann man sich das wie eine Singleton Klasse vorstellen, wie bei mir im Projekt InstanceHolder?



    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    @florian03 Hey, da du gefragt hast, was Dependency Injection genau ist und ich gerade definitiv prokrastinieren muss, habe ich mir mal die Zeit genommen und versucht, das Thema so anschaulich wie möglich zu erklären. Hoffentlich hilft es dir weiter!

    Dependency Injection (DI) ist prinzipiell ein Pattern, bei dem jede Abhängigkeit (jede Dependency) einer Klasse bei der Initialisierung von einem anderen Objekt, bzw. von außen angegeben wird. Das steht im Gegensatz dazu, dass eine Klasse eine Abhängigkeit selbst per new erstellt, wenn die Klasse initialisiert wird.
    Im Prinzip kannst du dir das Konzept wie ein Plugin-System vorstellen: Anstatt direkt beim Compilen/Programmieren des Projekts alle Abhängigkeiten im Voraus hard-gecoded zu haben, gibt es Module, die dynamisch beim Start geladen und dann von anderen Modulen genutzt werden.

    Ich finde Code für diese Beispiele meist anschaulicher. Als Beispiel kannst du mal annehmen, dass du eine Klasse baust, die eine hoch komplexe Rechnung durchführt und das Ergebnis dann loggen möchte. Der nötige Code könnte z.B. so aussehen:

    C#-Quellcode

    1. interface ILogger {
    2. void Log(string? message);
    3. }
    4. class ConsoleLogger : ILogger {
    5. public void Log(string? message) => Console.WriteLine(message);
    6. }
    7. class Calculator {
    8. private ILogger _logger;
    9. public Calculator() {
    10. _logger = new ConsoleLogger();
    11. }
    12. public int DoHardCalculation() {
    13. var result = 42;
    14. _logger.Log($"The answer to life, the universe and everything: {result}");
    15. return result;
    16. }
    17. }

    Damit im Calculator alles korrekt funktioniert, ist unbedingt ein ILogger nötig. Der ILogger ist somit eine Abhängigkeit der Klasse Calculator.

    Ein wichtiges Detail im obigen Code ist, dass im Konstruktor der Klasse Calculator ein ConsoleLogger erstellt wird. Die Klasse kümmert sich also selbst um die Erstellung der Abhängigkeiten. Das hat einen besonders großen Nachteil: Es ist nicht möglich, diese Abhängigkeit von außen auszutauschen.

    Nehmen wir beispielsweise an, dass dein Programm sehr beliebt wird und du die Möglichkeit hast, es zu verkaufen. Dafür musst du allerdings sichergehen, dass das Programm zu 100% den Logeintrag schreibt - kurz gesagt, du willst testen. Da es bereits das ILogger interface gibt, wird die Sache sehr einfach - du schreibst einfach einen Logger, mit dem du validieren kannst, dass eine Nachricht geschrieben wurde:

    C#-Quellcode

    1. class TestLogger : ILogger {
    2. private List<string?> _messages = new List<string?>();
    3. public void Log(string? message) => _messages.Add(message);
    4. public bool WasMessageLogged(string? message) => _messages.Any(m => m == message);
    5. }

    Das Problem ist, dass du mit dem Calculator von oben keine Möglichkeit hast, diesen Logger dynamisch einzufügen. Um das zu erreichen, musst du den Code umschreiben - beispielsweise zu dieser Version:

    C#-Quellcode

    1. class Calculator {
    2. private ILogger _logger;
    3. public Calculator(ILogger logger) {
    4. _logger = logger;
    5. }
    6. public int DoHardCalculation() {
    7. var result = 42;
    8. _logger.Log($"The answer to life, the universe and everything: {result}");
    9. return result;
    10. }
    11. }

    Jetzt ist es möglich, von außen den Logger (also die Abhängigkeit/Dependency) einzufügen (injecten), der genutzt werden soll:

    C#-Quellcode

    1. // In the real app:
    2. void Main() {
    3. var logger = new ConsoleLogger();
    4. new Calculator(logger).DoHardCalculation();
    5. }
    6. // In the tests:
    7. [Test]
    8. void Calculator_Logs_Result() {
    9. var logger = new TestLogger();
    10. var result = new Calculator(logger).DoHardCalculation();
    11. Assert.True(logger.WasMessageLogged($"The answer to life, the universe and everything: ${result}"));
    12. }

    Das ist, vereinfacht, das gesamte Konzept hinter Dependency Injection. Abhängigkeiten einer Klasse können von außen angegeben werden, beispielsweise per Konstruktor, wenn die Instanz erstellt wird. Dadurch gewinnt man die Möglichkeit, verschiedene Implementierungen anzugeben und somit die Funktionalität einer Anwendung anzupassen, ohne eine einzelne Zeile Code in den verschiedenen Klassen selbst anzugeben.

    Ein Real-World Beispiel kann man z.B. oft bei Datenbanken sehen, wenn es ans Testen geht. In einer "echten" App muss natürlich immer eine Verbindung zu einer echten DB existieren. Für Tests reicht es hingegen oft, eine virtuelle, in-memory Datenbank zu nutzen. Man könnte eine Klasse beispielsweise so erstellen:

    C#-Quellcode

    1. class SongProvider {
    2. private IDbConnection _db;
    3. public ServiceWhichUsesDb(IDbConnection db) {
    4. _db = db;
    5. }
    6. public IList<Song> GetAllSongs() {
    7. return _db.RunSql("...");
    8. }
    9. }

    Der Klasse an sich ist es somit völlig egal, welche Art von DB genutzt wird. Alles was zählt ist, dass es eine DB gibt, die eine gewisse Schnittstelle erfüllt. Ob das nun die echte DB ist, oder eine in-memory mit gemockten Daten, ist für die Funktionalität völlig irrelevant.



    DI Container

    Das Konzept hinter DI ist somit relativ einfach, aber bei großen Anwendungen wird es dann schnell haarig, alle Abhängigkeiten richtig aufzusetzen.



    Das Bild hier zeigt beispielsweise ein paar Abhängigkeiten: Beispielsweise hängt Dep B nur von Dep A ab. Dep C wiederrum hängt von Dep B ab, was eben wieder von Dep A abhängt.
    Zusätzlich müssen einige der Abhängigkeiten Singletons sein, während andere immer, wenn sie benötigt werden, per new erstellt werden müssen. (Singleton in dem Kontext heißt nur, dass es in der ganzen Anwendung nur eine Instanz gibt, die verwendet wird - das Singleton Pattern muss dafür nicht implementiert werden!)

    In Code übersetzt würden die Klassen, mit DI im Kopf, so aussehen (unwichtige Stellen weggelassen):

    C#-Quellcode

    1. class DepA { public DepA() { } }
    2. class DepB { public DepB(DepA a) { } }
    3. class DepC { public DepC(DepB b) { } }
    4. class DepD { public DepD(DepB b, DepC c) { } }
    5. class MyBusinessLogicRunner {
    6. public MyBusinessLogicRunner(DepD d) { /* ... */ }
    7. public void Run() { /* ... */ }
    8. }

    Jetzt geht es aber darum, wie man die Business Logic über den runner richtig ausführen kann - und hier wird es eklig. Man muss nämlich jetzt dafür sorgen, dass jede einzelne Dependency im Graph resolved wird.

    C#-Quellcode

    1. // Singletons can be declared one time, globally.
    2. // This way, they can be used anywhere.
    3. public static DepA A { get; } = new DepA();
    4. public static DepB B { get; } = new DepB(A);
    5. void Main() {
    6. // Remember: Whenever we want to use a non-singleton dependency, we must new it up.
    7. // DepD requires an instance of DepB (which is a singleton) and DepC (which must be newly created).
    8. DepC c = new DepC();
    9. DepD d = new DepD(B, c);
    10. new MyBusinessLogicRunner(d).Run();
    11. }

    An sich ist es ein sehr leichtes Beispiel, weil es noch wenige Klassen besitzt. Bei jedem echten Projekt werden es aber garantiert mehr.

    Um dieses Problem zu lösen, gibt es Frameworks, die DI-Container genannt werden. Diese Container haben einen Zweck: Sie nehmen dir das Auflösen/Erstellen einer Dependency ab.

    Sobald die App startet, sagst du einem DI-Container, wie genau dein Abhängigkeitsgraph aussieht. Sobald das geschehen ist, kannst du den DI-Container bitten, dir eine Instanz einer Klasse zurückzugeben, die du am Anfang registriert hast. Der Container läuft den gesamten Abhängigkeitsgraph für dich durch, erstellt wenn nötig Klassen und gibt dir am Ende das fertige Objekt zurück.

    Pseudocode für so ein Setup könnte z.B. so aussehen:

    C#-Quellcode

    1. void Main() {
    2. IDiContainer container = new PseudoDiContainer();
    3. container
    4. .AddSingleton<DepA>()
    5. .AddSingleton<DepB>()
    6. .AddTransient<DepC>()
    7. .AddTransient<DepD>()
    8. .AddTransient<MyBusinessLogicContainer>();
    9. var businessLogicRunner = container.Create<MyBusinessLogicContainer>();
    10. businessLogicRunner.Run();
    11. }

    AddTransient bedeutet, dass jedes mal eine neue Instanz von dem jeweiligen Typ erstellt werden soll.
    Interessant ist auch, dass der DI-Container automatisch den Konstruktor eines Typen anschaut und dementsprechend merkt, welche Abhängigkeiten er hat. Wir müssen also nicht explizit sagen, dass DepB von DepA abhängt - all das passiert automatisch.

    Wie man sehen kann, nimmt so ein Framework einen Haufen Arbeit ab und hat nebenbei den Vorteil, dass der Code um einiges verständlicher wird. Wenn du mehr über solche Container lernen willst, kannst du mal nach folgenden suchen: "Microsoft.Extensions.DependencyInjection", "Autofac", "SimpleInjector", "Ninject". Es gibt noch mehr, aber das sind die mir geläufigen. Ersteres ist das Framework meiner Wahl.



    Service Location

    Im Kontext von DI gibt es noch ein ähnliches Konzept: Service Location. Im Prinzip ist es wie DI, nur andersrum. Als Beispiel nochmal der Code von oben:

    C#-Quellcode

    1. void Main() {
    2. IDiContainer container = new PseudoDiContainer();
    3. container
    4. .AddSingleton<DepA>()
    5. .AddSingleton<DepB>()
    6. .AddTransient<DepC>()
    7. .AddTransient<DepD>()
    8. .AddTransient<MyBusinessLogicContainer>();
    9. new ServiceLocationExample(container).DoStuff();
    10. }
    11. class ServiceLocationExample {
    12. private DepA _a;
    13. private DepD _d;
    14. public ServiceLocationExample(IDiContainer container) {
    15. _a = container.Create<DepA>();
    16. _d = container.Create<DepD>();
    17. }
    18. // ...
    19. }

    Hier gibt es wieder eine Klasse, die gewisse Abhängigkeiten hat: ServiceLocationExample braucht DepA und DepB. Im Gegensatz zu den Beispielen oben werden diese Abhängigkeiten aber nicht im Konstruktor verlangt. Stattdessen nutzen wir direkt den DI-Container, um uns die Abhängigkeiten selbstständig zu erstellen.

    Dieses Konzept nennt sich Service Location und ist, strenggenommen, DI von der anderen Seite. Der DI-Container ist hier ein sogenannter Service Locator (er erlaubt das Auffinden von bestimmten Services, oder auch einfach Klassen/Dependencies). Oft hört man auch Service Provider (Fun Fact: In .NET gibt es das System.IServiceProvider interface, das genau diese Aufgabe erfüllt).

    Auch per Service Location ist es möglich, Services auszutauschen (was ja das eigentliche Argument für DI war), da immernoch beim Start der App definiert wird, welche Typen in den Container gepackt werden.

    Online wird öfters davon abgeraten, Service Location zu nutzen (in favor of DI) - siehe z.B. blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern. Meiner Meinung nach ist es aber meist graues Gebiet. Ich finde, dass, wenn möglich, klassisches DI genutzt werden sollte. Es gibt allerdings Situationen, bei denen man nur Service Location nutzen kann. Und dann ist es selbstverständlich nötig.



    Ein paar Kommentare zu deinem Projekt

    Wichtig: Ich bin nicht in deinem Projekt drin. Ich hab mir nur den Post oben durchgelesen und einen kurzen Blick auf GitHub geworfen. Also sein ein bisschen kritisch beim Folgenden. ;)

    Im Projekt sind schon ein paar Ansätze, die in die Richtungen von oben gehen. Du hast zum Beispiel einen ServiceContainer in MusicManager.ViewModel, der Ähnlichkeiten zu einem DI-Container hat, allerdings ohne der ganzen Magie. Im Endeffekt muss man die Methode GetService<T> nutzen, was, je nach Anwendung, wieder zu Service Location führen kann. Es gibt außerdem noch den MusicManager.App.ServiceInjector, allerdings wird der auch nie genutzt, von daher ist es schwer zu sagen, welche Aufgabe er momentan erfüllen soll. Du könntest dir überlegen, die verschiedenen DI Container dieser Welt anzuschauen und in dein Projekt zu integrieren. Einen fertigen Container zu nutzen wird immer besser sein, als eine eigene Implementierung zu fahren.

    Oben hast du auch gefragt, ob DI das gleiche ist, wie Instance Holder bei dir im Projekt. Das hätte ich mir gerne angeschaut, aber das Projekt ist auf GitHub schlichtweg leer - von daher ist das schwer zu sagen.

    Allgemein noch zu GitHub: Es scheint so, als ob die .gitignore nicht wirklich greift. Wenn man sich die Git History anschauen möchte, wird es schwer, weil viele unleserliche Dateien enthalten sind. Teils kann man auch "private" Daten sehen, wie z.B. lokale Pfade: Link. Ist jetzt nichts wildes, aber es schadet nicht. Du kannst z.B. einfach die hier C&P'en: github.com/github/gitignore/bl…er/VisualStudio.gitignore



    Wenn dich das ganze Thema interessiert, kannst du dir mal folgende Artikel anschauen. Martin Fowler erklärt das Thema um einiges besser als ich. ;)
    martinfowler.com/articles/injection.html

    Interessant ist auch, allgemein, das Thema IoC: martinfowler.com/bliki/InversionOfControl.html

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „shad“ ()

    Hallo,

    erstmal vielen Dank für eine so ausführliche Antwort! Das ist echt immer wieder unglaublich wie viel Zeit sich hier die User nehmen.....

    Also ich muss gestehen, das ist schon sehr kompliziert. Bis zu dem Punkt "DJ Container" habe ich alles verstanden, danach wurde (bzw. wird) es schwierig.

    Bez. meines Projekts:

    shad schrieb:

    MusicManager.App.ServiceInjector, allerdings wird der auch nie genutzt

    Wird nun genutzt, die Klasse ist im Grunde dafür da, die Services zum Programmstart dem Service Container hinzuzufügen. Man könnte es auch direkt in der App.xaml.cs machen, so ist es aber aus meiner Sicht übersichtlicher.

    shad schrieb:

    Das hätte ich mir gerne angeschaut, aber das Projekt ist auf GitHub schlichtweg leer - von daher ist das schwer zu sagen.

    Oh ja ist tazächlich noch leer, habe ich noch gar nicht angelegt. Wird im Grunde eine Singleton Klasse mit ein paar Properties wie ConnectionString oder halt CurrentUserID.

    C#-Quellcode

    1. public class Instance
    2. {
    3. private Instance() {}
    4. public static Instance CurrentInstance { get; } = new Instance();
    5. public string CurrentConnectionstring { get; set; }
    6. public int CurrentUser { get; set; }
    7. }


    shad schrieb:

    Es scheint so, als ob die .gitignore nicht wirklich greift.

    Ist mir auch schon aufgefallen, habe jetzt mal die aktuelle hineinkopiert...

    Viele Grüße und nochmals vielen Dank :)
    Florian
    ----

    WebApps mit C#: Blazor

    florian03 schrieb:

    Also ich muss gestehen, das ist schon sehr kompliziert. Bis zu dem Punkt "DJ Container" habe ich alles verstanden, danach wurde (bzw. wird) es schwierig.

    Das ist gar kein Problem - die Container habe ich nur der Vollständigkeit halber ins Spiel gebracht. Im Endeffekt sind sie auch nur ein Mittel zum Zweck. Ob man sein Programm von einem Container aufsetzen lässt, oder es per Hand macht, ist im Endeffekt egal. Wichtig ist, die Klassen richtig zu designen, damit DI überhaupt erst möglich wird. Und das geht eben sowohl mit, als auch ohne Container.
    Zu Lernzwecken ist es meiner Meinung nach eh besser, erstmal ohne DI-Container zu starten und dann zu sehen, wie weit man kommt. Irgendwann wird man meist einen Punkt erreichen, an dem der Code schwierig wird - spätestens dann kann man anfangen, sich in Container einzulesen. Da man dann bereits im Thema drin ist, ist es meist leichter, die Vorteile davon zu verstehen.

    florian03 schrieb:

    Wird nun genutzt, die Klasse ist im Grunde dafür da, die Services zum Programmstart dem Service Container hinzuzufügen.

    Da war ich wohl einfach zu früh. Ich schaue, wenn ich Zeit habe, nochmal rein!

    florian03 schrieb:

    Oh ja ist tazächlich noch leer, habe ich noch gar nicht angelegt. Wird im Grunde eine Singleton Klasse mit ein paar Properties wie ConnectionString oder halt CurrentUserID.

    Vorweg: Dein Projekt wird mit so einer statischen Klasse sicher funktionieren. Im Endeffekt ist das auch Code, der sehr leicht zu verstehen sein wird, von daher ist es auch durchaus valide, es so zu machen.

    Da ich jetzt schon dabei bin, will ich dir dennoch zeigen, wie es möglich ist, das Ganze DI konform zu machen. Die Lösung wirkt eventuell ein wenig over-engineered und ist nur eine von vielen möglichen Varianten (man könnte das sicher noch vereinfachen).

    Das Problem ist allgemein, dass verschiedene Klassen wissen müssen, welcher User gerade angemeldet ist. Einige Klassen müssen außerdem die Fähigkeit haben, diesen User zu setzen (beim Login, beispielsweise). Diese Anforderung könnte man erstmal, ganz einfach, so definieren:

    C#-Quellcode

    1. class User {
    2. public int Id { get; }
    3. public string Name { get; }
    4. }
    5. interface IUserContext {
    6. User CurrentUser { get; set; }
    7. }

    IUserContext wäre im Endeffekt die Schnittstelle, die den angemeldeten User setzen oder zurückgeben kann. Alle Klassen, die einen angemeldeten User brauchen, erwarten im Konstruktor dieses Interface, um eben an den User ranzukommen.

    Implementieren könnte man das Interface nun beispielsweise so:

    C#-Quellcode

    1. class DefaultUserContext : IUserContext {
    2. private User? _currentUser;
    3. public static DefaultUserContext Instance { get; } = new DefaultUserContext();
    4. public User CurrentUser {
    5. get => _currentUser ?? throw new InvalidOperationException("The current user is null. No user has logged in yet.");
    6. set => _currentUser = value;
    7. }
    8. }

    An sich auch wieder sehr einfach - damit man immer von überall an den User kommt, wird der Context erstmal als Singleton erstellt. Ansonsten macht er nichts, außer den aktuellen User zu verwalten.
    Jetzt fehlen nur noch die Klassen, die das Interface auch benutzen:

    C#-Quellcode

    1. class LoginBL {
    2. private IUserContext _userContext;
    3. // ...
    4. public LoginBL(IUserContext userContext) {
    5. _userContext = userContext;
    6. }
    7. public void DoLogin() {
    8. _userContext.User = new User() {
    9. Name = TheName,
    10. Id = TheId,
    11. };
    12. }
    13. }
    14. class SongOverviewBL {
    15. private IUserContext _userContext;
    16. // ...
    17. public SongOverviewBL(IUserContext userContext) {
    18. _userContext = userContext;
    19. }
    20. public GetSongsForCurrentUser() {
    21. var currentUser = _userContext.User;
    22. // Do stuff.
    23. }
    24. }


    Jedesmal, wenn so eine Klasse erstellt wird, muss nun einfach der aktuelle User Context mitgegeben werden. Beispielsweise so für die login klasse:

    C#-Quellcode

    1. var loginBl = new LoginBL(DefaultUserContext.Instance);

    Im Endeffekt steckt immernoch ein Singleton hinter dem aktuellen User bzw. UserContext, genau wie vorher. Allerdings ist es sauber weg-abstrahiert worden - die beiden Klassen wissen nicht, wie genau das Interface implementiert ist. Ihnen ist es auch egal, ob es ein Singleton ist, oder nicht. Was zählt ist, dass sie einen User bekommen oder setzen können. Und das ist in beiden Fällen erfüllt.

    Durch das Einführen des Interfaces ist es nun möglich, es austauschen, beispielsweise eben für Test-Implementierungen. Nebenbei wird der Code so sauberer, da alle Abhängigkeiten klar in den jeweiligen Konstruktoren deklariert sind. Jeder, der SongOverviewBL nutzen möchte, weiß, dass er dafür einen IUserContext braucht.

    Nocheinmal, das ist nur eine von vielen Möglichkeiten, das ganze zu designen. Da ich auch nicht tief im Projekt bin, könnte es durchaus sein, dass andere Interfaces/Klassen besser sind. Aber es geht ja auch eher um den Gedanken dahinter.

    florian03 schrieb:

    Viele Grüße und nochmals vielen Dank

    Gerne! Wenn du Fragen zum Thema hast, melde dich einfach.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „shad“ ()

    Hallo,

    (ich schreibe sehr wenig da ich am Handy bin. Kann auch nicht zitieren)

    Ich weiß gar nicht was ich sagen soll...
    Vielen vielen Dank! Das Beispiel an meinem Code hat es mir nochmals verdeutlicht.
    Ich sehe zwar (bitte sei mir nicht böse :)) für mich keinen praktischen Nutzen, werde es aber trotzdem so machen!

    Tests wird immer so groß geschrieben ich weiß aber gar nicht wie diese funktionieren...
    Ist bei mir dann die nächste Baustelle.

    Dann noch einen schönen Abend und wenn noch einen Frage kommt werde ich die stellen.
    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor
    Hallo Florian

    @shad hat dir ja schon eine sehr gute und ausführliche Antwort zum Thema DependencyInjection gegeben welche auch sehr gut erklährt ist wie ich finde. Einfach Top.
    Hierzu muss ich nicht mehr viel sagen ausser das du in meinem Beispiel (WPFNotes 2) welches ja die Grundlage deines Projekts ist auch bereits DependencyInjection verwendest um Fenster, Dialog, Messageboxe usw. unter MVVM in den Griff zu bekommen. Das sind eben diese Services.

    Was dein abspeichern des aktuellen Users betrifft würde ich hier wirklich schlicht und einfach zu einer Singleton Klasse greifen welche du z.b. als Property in deine ViewModel-Basisklasse packst. Ist das effizienterste denke ich.

    So nun zur Businesslogik und deren Aufgabe bzw. was du dort hineinpacken kannst. Ich versuchs mal unter rücksichtnahme das du EF verwendest.

    Thema Login:

    Was brauchst du für einen Login? OK, Userdaten prüfen. Evtl. ob das Passwort des Users abgelaufen ist. Ob sein Konto vieleicht vom Admin gesperrt wurde oder weil er X mal das Kennwort falsch eingegeben hat.
    Vieleicht Passwortänderung unter rücksichtnahme das X Sonderzeichen vorhanden sein müssen oder die letzten X Passwörter nicht verwendet werden dürfen. Aber wieviele? Der Wert kommt z.b. wieder aus den Settings, also hast du eigendlich für einen Loginscreen viel zu tun. Auch wenn wir die Passwortänderung weglassen. Ist viel zu tun.

    OK. Der reine Login.
    User gibt Username und Passwort ein und klickt auf Login.

    Gibt es den Benutzer?
    Ist das Passwort korrekt?
    Das Passwort ist aber verschlüsselt in der Datenbank, also müssen wir das soeben eingegebene auch verschlüssel und erst dann vergleichen mit dem Wert in der Datenbank.
    Ist der Useraccount aktiv oder gesperrt?
    Und zu guter letzt geben wir zurück ob er sich nun Anmelden darf.

    Du merkst, da ist schon was zu tun. Damit hättest du ein paar Zeilen in deinem ViewModel die du dort nicht haben willst. Deshalb eben die Businesslogik.

    In der Businesslogik würde ich jetzt auf die schnelle zwei Methoden machen:

    * DbConnectionAviable As Boolean
    * LoginUser(userName As String, rawPassword As String) As Integer

    Beim Start den Login-Views prüfst du erstmal Asyncron ob eine Verbindung zur Datenbank besteht (DbConnectionAviable) und gibst je nach rückgabewert den LoginButton über dein Command-CanExecute frei. sonst macht es ja keinen Sinn.
    Bei LoginUser verschlüsselst du erstmal das Passwort auf die selbe Weise wie du es beim Eintragen gemacht hast (Seeding) und Fragst mittels EF die UserId (und nur diese mittels einem Select wegen der Performance) des Users mit dem Username und dem Passwort sowie mit einem Filter auf User wo der Account aktiv ist und gibst wenn ein User vorhanden ist die UserId zurück und wenn nicht (geht bei EF gut mit SingleOrDefault()) gibst du 0 zurück. Am besten stellst du hsierfür in der Linq Abfrage sogar noch ein AsNotracking() dahinter um nochmals Perfomance rauszuholen. Danach kannst du dann noch sofern du magst einen Protokolleintrag schreiben oder sonst was loggen oder Aufräumarbeiten in der BL machen und haltest damit dein ViewModel sauber. Denn dort interessiert sich im Moment einfach nur ob die Daten korrekt sind oder nicht um zu enscheiden ob du das nächste Fenster öffnen kannst. Also sollte dies auch nicht mehr als eine Zeile im ViewModel sein/werden.+

    Ich hoffe das war verständlich erklärt was nun (aus meiner Sicht) in die BL gehört.

    PS weil du es vorher angesprochen hast:
    Und die Methode LoginUser der lässt sich eben auch wunderbar per UnitTests abdecken da hier keinerlei Abhängigkeiten vorhanden sind. (vorausgesetzt das Design deiner BL lässt das übergeben einer DbKontext Instanz für das Testprojekt zu damit du für Tests den InMemory-Provider nutzen kannst). So kannst du dann einfach mit einem klick innerhal von vieleicht einer Sekunde wissen ob dein Code in Ordnung ist. Mit falschem Username oder falschem Passwort, richtigem Username aber falschem Passwor, mit richtem Passwort und richtigem Username, mit Kontosperrung usw.

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##

    Hallo,

    auch dir Sascha vielen Dank für deine Antwort.

    Nofear23m schrieb:

    sehr gut erklährt ist wie ich finde. Einfach Top.

    Finde ich auch :)

    Nofear23m schrieb:

    So nun zur Businesslogik und deren Aufgabe bzw. was du dort hineinpacken kannst. Ich versuchs mal unter rücksichtnahme das du EF verwendest.

    Ich hab da halt noch ein paar Denkschwierigkeiten. Am Anfang war mir auch nie klar was ins ViewModel muss. Das ist denke ich einfach Übungssache.

    So wie ich es jetzt verstanden habe kommt in die Businesslogic einfach Methoden, um Aufgaben zu erledigen, die vom ViewModel ausgelöst werden. Man will so das ViewModel von unötigen Code befreien.

    Nofear23m schrieb:

    Ich hoffe das war verständlich erklärt was nun (aus meiner Sicht) in die BL gehört.

    Ja das kann man sagen, was in die LoginBI kommt ist mir jetzt klar.

    ---

    Ich hoffe ich komme jetzt nicht allzu blöd und nervig herüber aber ich muss nochmal fragen:
    Für mein neues View (was so eine Übersicht über alle Songs wird) sollte ich eine eigene BI anlegen (zb. SongOverviewBI).
    In diese schreibe ich dann Methoden wie GetAllSongs(), GetrSongByID(), etc. rein.

    Da ich aber mit einer ICollectionView arbeite ist mir jetzt eines nicht klar:
    Soll ich ALLE Daten von der Datenbank laden und dann mit der Filter Funktion der ICollectionView filtern so wie ich es bis jetzt immer gemacht habe.
    ODER
    Soll ich mit einem LINQ Ausdruck (wenn es da einen passenden gibt - ich denke da an .Where() ) nur die Daten abrufen die einem bestimmten Text in der Filtertextbox entsprechen

    Viele Grüße
    Florian
    ----

    WebApps mit C#: Blazor

    florian03 schrieb:

    In diese schreibe ich dann Methoden wie GetAllSongs(), GetrSongByID()

    Ne, das wäre etwas hart. Dann hättest du X Methoden die einfach nur eine Zeile (die Anfrage über EF durchreichen) beinhalten, ich komme unten dazu.

    florian03 schrieb:

    Soll ich mit einem LINQ Ausdruck (wenn es da einen passenden gibt - ich denke da an .Where() ) nur die Daten abrufen die einem bestimmten Text in der Filtertextbox entsprechen

    Das musst du von Fall zu Fall entscheiden. Wenn ich weis das ich bei einer Abfrage mit maximal X Einträgen rechnen kann und ich kann es verkraften das ich diese Abrufe kann ich mit der ICollectionView Filtern.

    Kacke wird es wenn die Daten (wie bei dir glaube ich) über die WAN Leitung kommen. Da will man nicht zu viel abrufen. Die Lösung hier (meiner meinung nach) Filtern auf DB Ebene also mittels WHERE in der Abfrage in kombination sogar mit Paging. Denn .... und das ist wichtig .... was ist wenn der User in der Suche "a" eingibt. Da kannste dann warten bis alle Lieder da sind die "a" im Titel beinhalten. Mit Paging ladest du immer nur so viele wie du anzeigen willst. So bist du dann z.b. auf der ganz sicheren Seite. Ist aber eben auch eine Sache wie du das ganze im Endeffekt anzeigen willst.

    Paging mit EF geht mfit Skip() und Take().

    Aber wieder zum Thema: Ich würde hier z.b. eine Methode SearchSongs(searchString As String, SearchAlsoAlbum As Boolean , Optional includeDeleted As Boolean = False) As List(Of Song).
    In der BL kannst du nun entscheiden nach was du suchst und die anderen zwei Parameter in die suche mit einbeziehen. (oder eben noch viel mehr wie z.b. nur in Gewissen Kategorien zu suchen oder sonstwas)

    Grüße
    Sascha
    If _work = worktype.hard Then Me.Drink(Coffee)
    Seht euch auch meine Tutorialreihe <WPF Lernen/> an oder abonniert meinen YouTube Kanal.

    ## Bitte markiere einen Thread als "Erledigt" wenn deine Frage beantwortet wurde. ##