Willkommen auf myCSharp.de! Anmelden | kostenlos registrieren
 | Suche | FAQ

Hauptmenü
myCSharp.de
» Startseite
» Forum
» Suche
» Regeln
» Wie poste ich richtig?

Mitglieder
» Liste / Suche
» Wer ist online?

Ressourcen
» FAQ
» Artikel
» C#-Snippets
» Jobbörse
» Microsoft Docs

Team
» Kontakt
» Cookies
» Spenden
» Datenschutz
» Impressum

  • »
  • Community
  • |
  • Diskussionsforum
Erstes C#, MVVM und SQLite Projekt
Moritz83
myCSharp.de - Member



Dabei seit:
Beiträge: 50

Themenstarter:

beantworten | zitieren | melden

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.
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15618
Herkunft: BW

beantworten | zitieren | melden

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:

    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.
private Nachricht | Beiträge des Benutzers
Moritz83
myCSharp.de - Member



Dabei seit:
Beiträge: 50

Themenstarter:

beantworten | zitieren | melden

Entschuldige die verspätete Antwort, krankheitsbedingt ist dies leider völlig in Vergessenheit geraten

Ich habe die letzten Wochen versucht mittels des gekauften Buches die Grundlagen zu erlangen und mittlerweile leuchtet mir einiges ein was vorher unklar war. Habe mir jetzt dein OOP Beispiel vorgenommen und versuche im Moment jeden Schritt zu dokumentieren (warum und wieso) sowie nachzuvollziehen, was da genau abläuft, sprich wer gibt wem die Anweisung oder wer erbt von wem und was erbt er (und so weiter)

Mein jetziger Stand ist folgender:
Die Grundsatz Idee hinter deinem Ansatz ist die Definition einer generischen Struktur, sprich ich definiere die Funktionen (Löschen, Auslesen, etc) und je nachdem ob es ein Team oder ein Mitarbeiter ist wird immer dieselbe Funktion aufgerufen, nur die "Parameter" (wie zum Beispiel TableName) werden entsprechend geändert.

Um zu Prüfen ob alles funktioniert wollte ich im CodeBehind der XAML Datei eine Messagebox einbinden welche den Vor- und Nachnamen des Mitarbeiters mit der Id 1 ausgibt. Wäre jetzt über das EmployeeRepository eingestiegen aber ich kriege den Befehl net hin. (Vermute der Schluss heisst dann irgendwas a la GetById(1).FirstName


PS: der Kram im CodeBehind dient nur zum Testen, habe miich noch nicht mit XAML beschäftigt

PSS:
Hab das ganze Projekt neu hochgeladen, ist einfacher da was nachzugucken
--> https://moritzjuergensen.visualstudio.com/_git/Calendar?path=%2FCalendar&version=GBmaster
Den Connection String habe ich unter "Projekteigenschaften - Ressourcen" definiert. Die DbContext Datei habe ich erstellt, aber das "Set" fehlt mir noch, vielleicht kannste mir hier auch noch auf die Sprünge helfen damit ich anhand eines funktionierenden Modells das ganze mittels Buch nochmals rekapitulieren kann...Falls du mal in der Gegend um Lindau oder der Ostschweiz bist lade ich dich für die Hilfe mal auf ein Bier ein
Dieser Beitrag wurde 2 mal editiert, zum letzten Mal von Moritz83 am .
private Nachricht | Beiträge des Benutzers
Moritz83
myCSharp.de - Member



Dabei seit:
Beiträge: 50

Themenstarter:

beantworten | zitieren | melden

hab versucht anhand von einem Tutorial die "Dependency Injection" einzubauen, allerdings stimmt da etwas noch nicht.

Wenn ich es start wirft die Datei "SqlRepository" einen "System.NullReferenceException: "Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt." Fehler aus. Hab da wohl etwas falsch eingebaut ;(
Dieser Beitrag wurde 1 mal editiert, zum letzten Mal von Moritz83 am .
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15618
Herkunft: BW

beantworten | zitieren | melden

Null Reference kann aber nich vom SqlRepository kommen.
Von SqlRepository kann auch gar keine Instanz erzeugt werden, weil es eine abstrakte Klasse ist.

Wo wird die NullReference genau geworfen?
private Nachricht | Beiträge des Benutzers
Moritz83
myCSharp.de - Member



Dabei seit:
Beiträge: 50

Themenstarter:

beantworten | zitieren | melden

Die Return Zeile des folgenden Codes wirft die Exception aus (in der Dateil SqlRepository.cs):

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

Ich habe die DbContext um folgenden Code ergänzt:

using System.Data.SQLite;

namespace Calendar.Database.DbContext
{
    public class DbContext : IDbContext
    {
        public SQLiteConnection Connection
        {
            get
            {
                return null;
            }
            set
            {
                using (var connection = new SQLiteConnection("Data Source=C:\\Database\\Database.db;Version=3;"))
                {
                    connection.Open();
                }
            }
        }
    }
}

Eine Datei namens ContainerConfig.cs beinhaltet das NuGet Paket Autofac für die Dependency Injection und der Code sieht so aus:

using Autofac;
using Calendar.Database.DbContext;
using Calendar.Database.Repositories;

namespace Calendar
{
    public static class ContainerConfig
    {
        public static IContainer Configure()
        {
            var builder = new ContainerBuilder();
            builder.RegisterType<DbContext>().As<IDbContext>();
            builder.RegisterType<EmployeeRepository>().As<IEmployeeRepository>();
            return builder.Build();
        }
    }
}


Ich war nach dem Tutorial der Meinung das der Code (vor Allem der Teil in der Klammer)

        public EmployeeRepository(IDbContext dbContext) : base(dbContext)
        {
        }
automatisch die Injection triggert aber ich bin da wohl auf dem Holzweg und geh da heute Abend nochmal über die Bücher.


Der genaue Fehlercode lautet übrigends:
System.NullReferenceException
  HResult=0x80004003
  Nachricht = Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt.
  Quelle = Dapper
  Stapelüberwachung:
   bei Dapper.SqlMapper.<QueryImpl>d__140`1.MoveNext() in C:\projects\dapper\Dapper\SqlMapper.cs: Zeile1066
   bei System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
   bei System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)
   bei Dapper.SqlMapper.Query[T](IDbConnection cnn, String sql, Object param, IDbTransaction transaction, Boolean buffered, Nullable`1 commandTimeout, Nullable`1 commandType) in C:\projects\dapper\Dapper\SqlMapper.cs: Zeile721
   bei DapperExtensions.DapperImplementor.GetList[T](IDbConnection connection, IClassMapper classMap, IPredicate predicate, IList`1 sort, IDbTransaction transaction, Nullable`1 commandTimeout, Boolean buffered)
   bei DapperExtensions.DapperImplementor.Get[T](IDbConnection connection, Object id, IDbTransaction transaction, Nullable`1 commandTimeout)
   bei System.Dynamic.UpdateDelegates.UpdateAndExecute5[T0,T1,T2,T3,T4,TRet](CallSite site, T0 arg0, T1 arg1, T2 arg2, T3 arg3, T4 arg4)
   bei DapperExtensions.DapperExtensions.Get[T](IDbConnection connection, Object id, IDbTransaction transaction, Nullable`1 commandTimeout)
   bei Calendar.Database.Repositories.SqlRepository`1.GetById(Int32 id) in C:\Users\mj\Source\Repos\Calendar_Azure\Calendar\Calendar\Database\Repositories\SqlRepository.cs: Zeile27
   bei Calendar.MainWindow..ctor() in C:\Users\mj\Source\Repos\Calendar_Azure\Calendar\Calendar\ViewModels\MainWindow.xaml.cs: Zeile24
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15618
Herkunft: BW

beantworten | zitieren | melden

     public SQLiteConnection Connection
        {
            get
            {
                return null;
            }
            set
            {
                using (var connection = new SQLiteConnection("Data Source=C:\\Database\\Database.db;Version=3;"))
                {
                    connection.Open();
                }
            }
        }

Das kann nicht klappen; das macht auch null sinn und im gleichen Zug kann der Zugriff hier nur null ergeben und eine Exception werfen.
Siehe auch [FAQ] NullReferenceException: Der Objektverweis wurde nicht auf eine Objektinstanz festgelegt - kann man auch super debuggen ( [Artikel] Debugger: Wie verwende ich den von Visual Studio? )

public SQLiteConnection Connection
        {
            get
            {
                var connection = new SQLiteConnection("Data Source=C:\\Database\\Database.db;Version=3;");
                connection.Open();
                return connection;
            }
        }

Hier darf auch kein using() verwendet werden, sonst ist die Verbindung natürlich sofort wieder verworfen.
usings() verwendet man so, dass sie um die eigentliche Operation liegen.

Noch besser natürlich

public class SqliteContext : IDbContext
{

   private IDbConnection _connection = null;
   private string _connectionString;
   public SqliteContext(string connectionString)
   {    
         _connectionString = connectionString;
   }
   public IDbConnection Connection
   {
      get
      {
         if (_connection is null)
         {
            _connection = new SQLiteConnection(_connectionString);
            _connection.Open();
         }
         return _connection;
      }
   }
}
private Nachricht | Beiträge des Benutzers
Moritz83
myCSharp.de - Member



Dabei seit:
Beiträge: 50

Themenstarter:

beantworten | zitieren | melden

Das mit dem Debugging hatte ich mittels Einzelschritt versucht (und eben auch um heraus zu finden, wann er wohin springt) aber wenn natürlich der Code an sich zwar ok aber trotzdem Blödsinn ist dann bringt dir das nicht viel.

Deine 2. Variante erinnert mich stark an mein Dependency Injection Beispiel von gestern, guck es mir daheim mal an.

A propos, den Teil mit "GetAll()" in SqlRepository.cs musste ich deaktivieren weil GetAll ein IEnumerable zurück gibt und daher

        public IList<TEntity> GetAll()
        {
            return DbContext.Connection.GetAll<TEntity>();
        }
nicht funktionieren kann (wenn ich mich nicht komplett täusche).

Nochmals vielen Dank für die tatkräftige Unterstützung!

Eine kleine Randnotiz noch:
Der Code funktioniert soweit, allerdings habe ich die lustige Eigenschaft dass die Tabelle im db File auf Entity enden muss, sprich "EmployeeEntity". Das finde ich aber raus warum das so ist.
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15618
Herkunft: BW

beantworten | zitieren | melden

Zitat von Moritz83
A propos, den Teil mit "GetAll()" in SqlRepository.cs musste ich deaktivieren weil GetAll ein IEnumerable zurück gibt und daher..

Das eine hat mit dem anderen nichts zutun.
Schau Dir an wie Listen funktionieren, dann siehst Du, dass IList von IEnumerable erbt.
Dann verstehst Du auch, wie hier der korrekte Weg wäre.
Intermediate Materialization (C#)

Darüber hinaus ist es aber nur sehr selten eine gute Idee wirklich alles aus der Datenbank zu laden.
Daher sollte man GetAll eh vermeiden, wenn möglich.
Zitat von Moritz83
Das finde ich aber raus warum das so ist. ;)
Steht in der Doku.
private Nachricht | Beiträge des Benutzers
Moritz83
myCSharp.de - Member



Dabei seit:
Beiträge: 50

Themenstarter:

beantworten | zitieren | melden

Sodele, mittlerweile habe ich die "Add" und die "Update" Variante auch eingebaut und es klappt wunderbar!

Add (hier musste ich allerdings "long" anstatt "int" nehmen - int wurde in der Dapper Doku genannt - warum kann ich nicht sagen, aber long funktioniert)

        public long Add(TEntity entity)
        {
            return DbContext.Connection.Insert(entity);
        }

Update

        public bool Update(TEntity entity)
        {
            return DbContext.Connection.Update<TEntity>(entity);
        }


Das 2. Beispiel für den Inhalt der DbContext habe ich mal angeguckt:
"noch besser weil die Connection zuerst geprüft wird (sprich ist bereits eine offene Verbindung vorhanden?) und weil es nun als SqLite Verbindungsdatei erkennbar ist?


Mir ist noch aufgefallen das nun die Datei "Employee.cs" (mein ursprüngliches Model) gar nicht mehr eingebunden ist. Gehe ich richtig in der Annahme das nun die Definition (als Beispiel)

        public string FirstName
        {
            get => _firstName;
            set
            {
                if (_firstName != null && _firstName != value)
                {
                    _firstName = value;
                }
                OnPropertyChanged("FirstName");
            }
        }
direkt in der EmployeeEntity gemacht wird und ich die "normale" Model Datei nicht mehr brauche?
private Nachricht | Beiträge des Benutzers
Abt
myCSharp.de - Team

Avatar #avatar-4119.png


Dabei seit:
Beiträge: 15618
Herkunft: BW

beantworten | zitieren | melden

Zitat von Moritz83
"noch besser weil die Connection zuerst geprüft wird (sprich ist bereits eine offene Verbindung vorhanden?) und weil es nun als SqLite Verbindungsdatei erkennbar ist?
- Weil der ConnectionString nicht hard im Code ist
Weil Du den Context so an vielen Stellen re-usen kannst aber keine neue Verbindung geöffnet wird (kann wichtig sein, wenn Du mehrere Operationen hast, die gemeinsam abgeschickt werden sollen - daher zB auch Standardverhalten vom Entity Framework).
Zitat von Moritz83
Gehe ich richtig in der Annahme..

Das eine hat mir dem anderen nicht viel zutun.
Das sind zwei getrennte Schichten.
[Artikel] Drei-Schichten-Architektur
private Nachricht | Beiträge des Benutzers
Moritz83
myCSharp.de - Member



Dabei seit:
Beiträge: 50

Themenstarter:

beantworten | zitieren | melden

ach so, ne den Connection String habe ich auch im 2. Beispiel bereits nicht mehr hard im Code. Hab den in der App.config verstaut (kopiere im Moment bei jedem Debug eine Datei in den bin Ordner zum ausprobieren)
 <connectionStrings>
    <add name="SQLite" connectionString="Data Source=Database\\DatabaseFile\\Database.db;Version=3;" />
  </connectionStrings>



Also gehört eine Entity gemäss Link zu Datenzugriffsschicht da damit ja eigentlich nur verwaltet werden, richtig? (Es wird ja nix verarbeitet sondern nur "verwaltet", resp. aus der DB ausgelesen - das SqlRepository gehört demnach auch dazu, oder?)

Was sich mir nicht ganz ausgeht:
Wenn ich nun in der Entity Definition

public string FirstName { get; set; }

schreibe und gleichzeitig in der Employee Model Datei

        public string FirstName
        {
            get => _firstName;
            set
            {
                //blub blub
            }
        }

steht so ist das doch doppelt gemoppelt, oder nicht? Ich mein ich definiere 2x das es einen String namens "FirstName" gibt.

Viel wichtiger ist, wo wird denn die Model Datei eingeklinkt? In der Entity steht ja

public class EmployeeEntity : Entity
sprich "FirstName" und "LastName" hole ich aus der EmployeeEntity und die "Id" aus der "normalen" Entity. Eigentlich müsste sich doch das Employee Model auf die Entität stützen, oder?

--> Habe nur folgendes Beispiel gefunden, hab aber keine Ahnung ob das so korrekt ist:https://stackoverflow.com/questions/30319584/how-to-map-entity-framework-model-classes-with-business-layer-class-in-n-tier-ar
private Nachricht | Beiträge des Benutzers
MrSparkle
myCSharp.de - Team

Avatar #avatar-2159.gif


Dabei seit:
Beiträge: 5960
Herkunft: Leipzig

beantworten | zitieren | melden

Damit dieses Thema nicht endlos ausufert, mache ich jetzt hier zu. Das hat alles nichts mehr mit einem Code-Rieview zu tun. Bitte beachte die Regeln für dieses Forum: Code-Review Regeln

Bei konkreten Fragen bitte im passenden Unterforum posten, und auch da bitte [Hinweis] Wie poste ich richtig? beachten, besonders 1.2 Nur ein Thema pro Thread.
Weeks of programming can save you hours of planning
private Nachricht | Beiträge des Benutzers