EF Core BusinessLogic Ohne Repository

  • VB.NET

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

    EF Core BusinessLogic Ohne Repository

    Hallo zusammen,

    ich versuche mich noch immer an EF Core in meiner (MVVM) Anwendung. Da ein GernericRepository noch mein Verständnis übersteigt dachte ich mir ich kann den Layer erstmal weglassen.
    Also erstmal einen BusinessLogic Layer hinzugefügt. Das sollte doch auch gehen. Allerdings versteh ich das ganze zusammenspiel irgendwie nicht.

    Wäre jemand so nett und würde mir erklären wie genau das zusammenspiel funktioniert? Also was alles würde in meinem Fall in die BL gehören und wo wird sie dann aufgerufen oder wo wird sie hinein gereicht? Im/Ins Viewmodel?
    :/
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    Hallo

    Ich dachte das haben wir bereits besprochen?

    Im Grunde ist ja die BL nur eine "Hilfe" um den Code vom ViewModel sauber zu halten bzw. um die dll von einer anderen App auch dann auch nutzen zu können. Also wenn du eine zweite App machst. Handy, Website oder einfach ein Admintool peralell zu deiner "normalen" App dann kannst du die dll einbinden und hast Zugriff auf die Logik und musst diese nicht neu schreiben. Denn im Admintool willst du ja sicher gänzlich andere Views haben und kannst sohin evtl. nicht das selbe ViewModel wie in deiner normalen App nutzen. Damit du nicht alles übertragen musst wandert einfach so viel als möglich in die BL.

    Was wäre das z.b.

    OK, gehen wir davon aus das deine Anwendung einen Login hat. So hast du sicher zumindest ein Command im Viewmodel. Ich nenne diesen Command nun mal DoLoginCommand.
    OK, dann wirdst du sicher noch eine Methode haben welche CheckLoginData benannt wäre.

    Gut. CheckLoginData könnte prüfen ob es den Benutzernamen gibt, ob das Konto auch nicht gesperrt ist, das Passwort nicht abgelaufen ist, das Passwort korrekt ist usw.
    Als rückgabe könnte es ja eine List(Of String) mit den aufgetretenen Fehlern zurückgeben. Ist die auflistung leer gab es keinen Fehler und der User kann eingeloggt werden.
    Anschliessen (sobald der User eingeloggt wird soll aber vieleicht ein Log in eine Datenbank geschrieben werden. Wenn das Passwort aber falsch war soll in der Datenbank der Zähler für "Failed Logins" um eines hochgezählt werden da nach 10 Versuchen das Konto gesperrt werden soll.

    Also haben wir mal geklärt was wir wollen.

    Ich gehe immer so vor das ich zuerst eine art pseudocode im ViewModel schreibe und mir dann einfach die Methoden automatisch von VS erstellen lasse.

    Also schreibe ich im ViewModel mal so was wie (jetzt rein aus der Hüfte):

    VB.NET-Quellcode

    1. Private ReadOnly _blLogin As BL.LoginBl (meine BL Instanz welche ich z.b. im Konstruktor erstelle)
    2. Private Function CheckLogin(user as String, password as String) As Boolean
    3. Dim loginErrors = _blLogin.CheckUserLogin(user,password)
    4. If loginErrors.Any() then
    5. 'Messagebox mit fehlern für User ausgeben
    6. End if
    7. Return Not loginErrors.Any()
    8. End Function
    9. Private Sub DoLoginCommand_Execute
    10. If CheckLogin() Then
    11. 'Login vollziehen. Fenster öffnen oder sonst was.
    12. End If
    13. End Sub


    Kurz und bündig sowie übersichtlich im ViewModel.

    Im Code der BL sieht das z.b. so aus (wieder extremer PsoidoCode):

    VB.NET-Quellcode

    1. Public Function CheckUserLogin(user as String, password as String) As List(Of String)
    2. Dim errorList as New List(Of String)
    3. Dim userInfo As Model.User = GetUserInfo(user).SingleOrDefault()
    4. If userInfo Is Nothing then loginErrors.Add("Unbekannter Benutzername") : WriteDbLog(loginErrors.First) : Return loginErrors
    5. If userInfo.AccountLocked Then loginErrors.Add("Benutzerkonto wurde gesperrt, verständigen Sie den Administrator.") : Return loginErrors
    6. If userInfo.PasswordRunnsOutAt <= DateTime.Now Then
    7. loginErrors.Add("Das Passwort ist abgelaufen, vor dem Login müssen Sie ein neues Passwort vergeben")
    8. End If
    9. If Not userInfo.Password = Helper.EncryptPassword(password) Then
    10. loginErrors.Add("Falsches Passwort")
    11. AddFailedLogins(userInfo.Id)
    12. End If
    13. Return loginErrors
    14. End Function
    15. Private Sub AddFailedLogins(userID as Integer)
    16. 'User von DB holen, Failed Logins beim User hochzählen und Wert in der DB zurückspeichern.
    17. End Sub


    Ich hoffe man versteht was ich meine.

    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. ##

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

    Hallo @Akanel,
    ich stand auch vor dem gleichen Problem, komme mit Generic Repository auch nicht klar...
    Ich hab es dann in verschiedene Layer aufgeteilt. Bei mir gibt es den Context (der sollte klar sein, oder?), dann gibt es den DataProvider und die Logic.
    Am Beispeil von @Nofear23m nehme ich hier auch mal den Login.
    Ich hab erst mal eine DataProviderBase Klasse, die quasi den Context erstellt und hält:
    Spoiler anzeigen

    C#-Quellcode

    1. using System;
    2. using System.Linq;
    3. using TimeTracker.Data.Context;
    4. namespace TimeTracker.Data.Provider
    5. {
    6. public class DataProviderBase : IDisposable
    7. {
    8. #region Private Fields
    9. private bool _disposeContext;
    10. internal TimeTrackerContext Context { get; private set; }
    11. #endregion
    12. #region Constructor
    13. public DataProviderBase()
    14. {
    15. CreateContext(new TimeTrackerContext());
    16. _disposeContext = true;
    17. }
    18. internal DataProviderBase(TimeTrackerContext context)
    19. {
    20. CreateContext(context);
    21. _disposeContext = false;
    22. }
    23. #endregion
    24. #region Helper Methods
    25. private void CreateContext(TimeTrackerContext context)
    26. {
    27. Context = context;
    28. Context.ChangeTracker.LazyLoadingEnabled = false;
    29. Context.ChangeTracker.AutoDetectChangesEnabled = false;
    30. Context.Database.EnsureCreated();
    31. }
    32. public bool HasDataBaseEntries()
    33. {
    34. return Context.People.Any();
    35. }
    36. #endregion
    37. #region IDisposable
    38. public void Dispose()
    39. {
    40. if (_disposeContext) Context.Dispose();
    41. }
    42. #endregion
    43. }
    44. }


    Dann habe ich mir ein Interface erstellt, wecles einen Typ T engegen nimmt und die üblichen Metjoden bereit stellt, also Insert, Update, Delete usw.
    Sieht dann so aus:
    Spoiler anzeigen

    C#-Quellcode

    1. using System.Collections.Generic;
    2. namespace TimeTracker.Data.Provider
    3. {
    4. public interface IGenericProvider<T>
    5. {
    6. bool Insert(T item);
    7. bool Update(T item);
    8. bool Delete(T item);
    9. T Get(int id);
    10. List<T> GetList();
    11. }
    12. }

    Nun kann ich für jede Modelklasse einen Provider erstellen, zum Beispiel für den Log in einen PeopleProvider (weil die Klasse Person eine AccessData hat, die die Login Daten hält)

    der PeopleProvider sieht dann so aus:
    Spoiler anzeigen

    C#-Quellcode

    1. using Microsoft.EntityFrameworkCore;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using TimeTracker.Model;
    5. namespace TimeTracker.Data.Provider
    6. {
    7. public class PeopleProvider : DataProviderBase, IGenericProvider<Person>
    8. {
    9. public bool Insert(Person item)
    10. {
    11. Context.People.Add(item);
    12. return Context.SaveChanges() > 0; ;
    13. }
    14. public bool Update(Person item)
    15. {
    16. Context.People.Update(item);
    17. return Context.SaveChanges() > 0;
    18. }
    19. public bool Delete(Person item)
    20. {
    21. item.DeletedFlag = true;
    22. Context.People.Update(item);
    23. return Context.SaveChanges() > 0;
    24. }
    25. public Person Get(int id)
    26. {
    27. return Context.People.Include(c => c.Company).Include(l => l.AcessData).Single(p => p.Id == id);
    28. }
    29. public Person Get()
    30. {
    31. return Context.People.Include(c => c.Company).Include(l => l.AcessData).FirstOrDefault(p => !p.DeletedFlag);
    32. }
    33. public List<Person> GetList()
    34. {
    35. return Context.People.ToList();
    36. }
    37. }
    38. }


    so, das sind die Vorarbeiten die ich einmal benötige.
    Im Logic Projekt hab ich nun eine Klasse für die Logic... kann man sicherlich auch in verschiedene Klassen aufteilen, zum Beispiel eine LoginLogic oder RegisterLogic oder so...

    In meiner Logic hab ich dann eine Methode um die ID des Users zurück zu geben wenn er eingeloggt werden kann, kann er nicht eingeloggt werden, wird -1 zurück gegeben was ich dann im ViewModel abfangen kann.
    Die Methode sieht dann so aus:

    C#-Quellcode

    1. /// <summary>
    2. /// gibt die ID der ersten Person aus der Datenbank zurück
    3. /// wenn keine Person in der Datenbank vorhanden ist
    4. /// wird -1 zurück gegeben
    5. /// </summary>
    6. /// <returns></returns>
    7. public static int GetLoggedInPersonId(string user, string pass)
    8. {
    9. using (var prov = new PeopleProvider())
    10. {
    11. var usr = prov.Get();
    12. if (usr.AcessData.UserName == user && usr.AcessData.EncryptedPassword == pass.GetHash())
    13. return usr.Id;
    14. return -1;
    15. }
    16. }


    So, nun zum ViewModel.
    Im Grunde ähnlich wie oben bei Sascha.
    Spoiler anzeigen

    C#-Quellcode

    1. using System.Threading.Tasks;
    2. using System.Windows.Input;
    3. using TimeTracker.Data.Logic;
    4. namespace TimeTracker.ViewModel
    5. {
    6. public class LoginViewModel : ViewModelBase
    7. {
    8. private string _userName;
    9. public string UserName { get => _userName; set => SetValue(ref _userName, value); }
    10. private bool _loginIsRunning;
    11. public bool LoginIsRunning { get => _loginIsRunning; set => SetValue(ref _loginIsRunning, value); }
    12. #region Commands
    13. public ICommand LogInCommand { get; private set; }
    14. public ICommand RegisterCommand { get; private set; }
    15. #endregion
    16. public LoginViewModel()
    17. {
    18. LogInCommand = new RelayCommand(async (parameter) => await LoginAsync(parameter));
    19. }
    20. #region Command Methods
    21. /// <summary>
    22. /// den User einloggen
    23. /// </ summary>
    24. /// <param name = "parameter"> der <see cref = "SecureString" />, der aus der View für das Benutzerkennwort übergeben wurde </ param>
    25. /// <returns> </ returns>
    26. public async Task LoginAsync(object parameter)
    27. {
    28. await RunCommandAsync(() => LoginIsRunning, async () =>
    29. {
    30. var user = UserName;
    31. var pass = (parameter as IHavePassword).SecurePassword.Unsecure();
    32. IoC.Application.CurrentPersonId = TimeTrackerLogic.GetLoggedInPersonId(user,pass);
    33. if (IoC.Application.CurrentPersonId > 0)
    34. {
    35. IoC.Application.GoToPage(ApplicationPageEnum.Main, new MainViewModel(IoC.Application.CurrentPersonId));
    36. }
    37. else
    38. {
    39. await IoC.UI.ShowMessage(new MessageBoxDialogViewModel
    40. {
    41. WindowTitle = "Falsche Zugangsdaten",
    42. Message = "Ihre Zugangsdaten waren nicht korrekt, bitte versuchen Sie es erneut",
    43. ButtonText = "Zurück"
    44. });
    45. }
    46. });
    47. }
    48. #endregion
    49. }
    50. }

    Ich arbeite mit dem nuget Paket ninject. Das stellt mir IoC bereit, womit ich halt Fenster öffnen kann, oder ein bestimmtes ViewModel statisch überall verfügbar habe. Das ist bei mir das ApplicationViewModel. Das AppliucationViewModel hält zum Beispiel die ID des angemeldeten Users. Das VM hat auch Boolean Properties um Menüs anzuzeigen oder auszublenden. Und das VM hat eine Methode GoToPage um die Pages in meiner WPF Anwendung hin und her zu schalten.

    In der LoginAsync Methode rufe ich einmal die Logic auf um an die ID zu kommen. Mehr benötigt es ja zu dem Zeitpunkt nicht.
    Alle anderen Daten rufe ich immer erst dann ab, wenn ich sie brauche, dann eben immer über die Logic.

    Mein Zeiterfassungstool ist noch nicht ganz fertig, paar Kleinigkeiten fehlen noch, vor allem Übersichten und Salden.
    Das Registrieren (wenn die Tabelle Person leer ist wird dies automatisch aufgerufen), das einloggen, das ändern des Kennwortes und der Personen Details, sowie das "Stechen" (Kommen, Gehen, Pause) und die Pausenberechnung funktioniert schon.
    Falls interesse besteht, kann ich es Dir schicken oder hier hoch laden.
    Ich nutze übrigens EFCore mit SQLite und alle Projekte sind .NetCore 3.1.
    "Hier könnte Ihre Werbung stehen..."

    MichaHo schrieb:

    komme mit Generic Repository auch nicht klar..

    Achso? Denn....

    MichaHo schrieb:

    Ich hab es dann in verschiedene Layer aufgeteilt. Bei mir gibt es den Context (der sollte klar sein, oder?), dann gibt es den DataProvider und die Logic.

    Genau das ist ein Generisches Repository und genau das selbe wie in meinem Notes Projekt. Deines ist auch generisch weil ja IGenericProvider<T>.

    Obs nun GenericRepository oder GenericPRovider benannt ist kann ja mala egal sein. Du abstrahierst die EF Methoden um dieses vor der Businesslogik zu verstecken um somit in der Businesslogik keinen Verweis auf EF Core haben zu müssen. Was ja auch der einzige Sinn eines Repositories ist. Obs nun generisch ist oder nicht.

    Im umkehrschluss ergibt dies aber @Akanel und @MichaHo das man auch nur diesen zusätzlichen Layer einführen sollte wenn dieser auch benötigt wird. Ist es ausgeschlossen das später auf eine andere Datenhaltung umgestellt wird oder die BL wo anders verwendet werden muss wo kein EF Core möglich ist (da mittlerweile Cross-Plattform fähig eher unwarscheinlich aber es könnte ja evtl. Konzernrichtlinien verletzen) würde ich den Layer auch schlicht weglassen.

    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 zusammen
    Ich habe mal ein paar versuche unternommen und habe gleich wieder eine Ohrfeige bekommen.
    Ich habe mir mal eine MainWorkspaceBl angelegt, mit folgendem Inhalt.

    VB.NET-Quellcode

    1. Imports HStatistik.Model
    2. Imports StatsContext
    3. Public Class MainWorkspaceBl
    4. Private ReadOnly _ctx As StatsDbContext
    5. Public Sub New()
    6. _ctx = New StatsDbContext
    7. _ctx.SalesStaffs.Add(New SalesStaff With {.Name = "Max Mustermann"})
    8. _ctx.SaveChanges()
    9. End Sub
    10. End Class


    Aufgerufen wird diese im MainWorkspace

    VB.NET-Quellcode

    1. Imports HStatistik.BusinessLogic
    2. Namespace Workspaces
    3. Public Class MainWorkspace
    4. Inherits ViewModelBase
    5. Private _statusWs As StatusWorkspace
    6. Public Property StatusWs() As StatusWorkspace
    7. Get
    8. Return _statusWs
    9. End Get
    10. Set(ByVal value As StatusWorkspace)
    11. _statusWs = value
    12. RaisePropertyChanged()
    13. End Set
    14. End Property
    15. Private _planWs As PlanWorkspace
    16. Public Property PlanWs() As PlanWorkspace
    17. Get
    18. Return _planWs
    19. End Get
    20. Set(ByVal value As PlanWorkspace)
    21. _planWs = value
    22. RaisePropertyChanged()
    23. End Set
    24. End Property
    25. Private ReadOnly mainBl As MainWorkspaceBl
    26. Public Sub New()
    27. Me.mainBl = New MainWorkspaceBl
    28. Me.PlanWs = New PlanWorkspace
    29. Me.StatusWs = New StatusWorkspace
    30. End Sub
    31. End Class
    32. End Namespace


    Wenn ich versuche dies zu kompilieren bekomme ich folgenden Fehler


    Eigentlich hatte ich gedacht mein Model wäre so gewählt das ich einen SalesStaff (Vertriebler) anlegen kann ohne das diese einen Plan haben MÜSSEN.
    Dem scheint aber nicht so wenn ich die Fehlermeldung richtig verstehe.
    Oder liegt der fehler hier ganz wo anders?

    Ziel ist folgendes:
    Ich kann einen Vertriebler anlegen, oder auch mehrere. Dies müssen nicht zwingend einen Plan haben.
    Dann Kann ich einen Plan anlegen und dabei via Combobox einen Vertriebler auswählen.

    Hier noch das Model dazu.

    Spoiler anzeigen

    VB.NET-Quellcode

    1. Imports System.ComponentModel.DataAnnotations
    2. Public Class Plan
    3. <Key>
    4. Public Property Id As Integer
    5. <Required>
    6. Public Property PlanNumber As Integer
    7. <Required(AllowEmptyStrings:=False, ErrorMessage:="Das Feld 'Firma' muss ausgefüllt sein.")>
    8. <MinLength(3, ErrorMessage:="Das Feld 'Firma' muss mindestens 3 Zeichen enthalten.")>
    9. <MaxLength(30, ErrorMessage:="Das Feld 'Firma' darf maximal 30 Zeichen enthalten.")>
    10. Public Property Company As String
    11. Public Property SetOn As Date
    12. Public Property UploadOn As Date
    13. <MaxLength(250, ErrorMessage:="Das Feld 'Kommentar' darf maximal 250 Zeichen enthalten.")>
    14. Public Property Comment As String
    15. Public Property Finished As Boolean
    16. End Class

    VB.NET-Quellcode

    1. Imports System.ComponentModel.DataAnnotations
    2. Public Class SalesStaff
    3. <Key>
    4. Public Property Id As Integer
    5. <Required(AllowEmptyStrings:=False, ErrorMessage:="Das Feld 'Name' muss ausgefüllt sein.")>
    6. <MinLength(3, ErrorMessage:="Das Feld 'Name' muss mindestens 3 Zeichen enthalten.")>
    7. <MaxLength(30, ErrorMessage:="Das Feld 'Name' darf maximal 30 Zeichen enthalten.")>
    8. Public Property Name As String
    9. Public Property PlanId As Integer
    10. Public Property Plans As Plan
    11. End Class
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    Hallo

    Nur zum verständnis:

    Akanel schrieb:

    Ich kann einen Vertriebler anlegen, oder auch mehrere. Dies müssen nicht zwingend einen Plan haben.
    Dann Kann ich einen Plan anlegen und dabei via Combobox einen Vertriebler auswählen.

    Moment. Geht (jetzt das ohne ViewModel irgendwie im Hinterkopf zu haben, rein um die Daten) es jetzt darum das du per "Default" keinen Plan für einen Vertriebler hast oder soll er nicht unbedingt einen bekommen müssen.
    Das ist ein großer Unterschied. Oder andersrum. DARF es im Endeffekt in der Datenbank einen Vertriebler ohne Plan geben.
    Danke daran: Was das Model betrifft muss dies so Designed werden das du KEINE falschen Daten haben/bekommen kannst. Also unabhängig von einem View oder sonstwas muss es so Designed werden das du beim Programmieren was die datenhaltung betrifft im Grunde keine Fehler in der Struktur machen kannst. so zumindest der Idealfall.

    Akanel schrieb:

    Eigentlich hatte ich gedacht mein Model wäre so gewählt das ich einen SalesStaff (Vertriebler) anlegen kann ohne das diese einen Plan haben MÜSSEN.

    Wenn du entweder die PlanId als Eigenschaft rausnimmst oder diese als Nullable definierst funktioniert das auch. Es ist klar. Plans ist Nullable weil Plans ja nothing sein kann. Da es aber der dazugehörige Fremdschlüssel (PlanId) nicht sein kann schlägt dies fehl. Aber was nun genau gewollt ist habe ich oben ja in den Raumgestellt.

    PS: Bitte benenne die Property Plans in Plan um. Plans impliziert das es mehrere geben kann, das führt später in der Anwendung des Property sicher zur verwirrung.

    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. ##

    Es geht darum das der Vertriebler per "Default" keinen Plan haben muss. Am ende darf es aber keinen Vertriebler ohne Plan geben.
    Vielleicht ist meine Denkweise auch falsch.
    Wenn ich einen Plan anlege muss ich diesen ja einem Vertriebler zuordnen können. Dies geht aber nur wenn der Vertriebler schon da ist. So meine Denkweise.

    Folgendes war der Plan.
    Ich habe eine UI mit 2 Listboxen. Links eine mit allen Vertrieblern und rechts eine mit den dazugehörigen Plänen zum jeweiligen Vertriebler.
    Wenn ich nun einen neuen Plan anlege, muss ich auswählen welcher Vertriebler den erstellt hat. Dieser Vertriebler kann schon x pläne erstellt haben, oder es ist sein erster.

    Kann man das irgendwie nachvollziehen?
    Rechtschreibfehler betonen den künstlerischen Charakter des Autors.
    Hallo

    Jaja, klar ist das nachvollziehbar und eben so wie ich es mir dachte.

    Akanel schrieb:

    Am ende darf es aber keinen Vertriebler ohne Plan geben.

    Siehste, wie ich dachte. Also wir das Model so erstellt das es einen Plan geben MUSS.

    Das ViewModel wird aber so gemacht das es keinen geben muss. Also das Property im ViewModel per Default Nothing ist.
    Aber... der Speichern Button darf eben erst gekickt werden wenn es einen Plan gibt.

    So kannst du dich selbst nicht "vertun" und unabsichtlich vieleicht mal irgendwo vergessen einen Plan für einen Vertriebler zu hinterlegen, weil dir die DB das schlicht nicht zulässt. Du hast dich also abgesichert.
    Merke, mit einer echten MVVM Struktur (im Gegensatz zu den Strukturen die ich oft sehe wo das Model Quasi als ViewModel verwendet wird weil ja so viel einfacher ;-)) kannst du dich auch vor deinen eigenen Fehler schützen und bist eben flexibler, wie ja in diesem Fall das dein ViewModel anders aufgebaut werden kann als es das Model vorgeben würde.

    Ich hoffe es wird nun etwas klarer.

    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. ##