Laden...

Variablenzugriff auf vererbte Klassen beim Speichern und Lesen in einer NoSQL DB

Erstellt von martl vor 9 Jahren Letzter Beitrag vor 9 Jahren 3.924 Views
M
martl Themenstarter:in
23 Beiträge seit 2009
vor 9 Jahren
Variablenzugriff auf vererbte Klassen beim Speichern und Lesen in einer NoSQL DB

Hallo,

ich habe eine Master-Klasse, in welcher ich je eine Load() und Save() Methode bereitstellen will. Diese sollten die Daten aus einer MongoDB lesen bzw. in eine schreiben. Basierend auf dieser Klasse vererbe ich mir Klassen wie zb. Company und User, mit jeweils eigenen Variablen.

Das Speichern stellt theoretisch kein Problem dar, hier kann man einfach ein collection.Insert(this); durchführen.

Beim Laden gibt es jedoch das Problem, das mir der letztendliche Klassentyp in der Masterklasse nicht bekannt ist und ich somit die Variablen nicht füllen kann.


namespace ConsoleApplication1
{
    class Master
    {
        public ObjectId Id { get; set; }

        public virtual void Load(string id)
        {
            MongoCollection collection = ConsoleApplication1._mongoDatabase.GetCollection(this.GetType(), this.GetType().Name);
            MongoCursor data = collection.FindAs(this.GetType(), Query.EQ("id", id));

            // missing code
        }

        public virtual void Save()
        {
            MongoCollection collection = ConsoleApplication1._mongoDatabase.GetCollection(this.GetType(), this.GetType().Name);
            collection.Insert(this);
        }
    }

    class Company : Master
    {
        public string name { get; set; }
        public string city { get; set; }
        public string phone { get; set; }

        public void individualMethod1()
        {
        }

        public void individualMethod2()
        {
        }
    }

    class User : Master
    {
        public string forename { get; set; }
        public string lastname { get; set; }
        public string skill { get; set; }

        public void individualMethod3()
        {
        }
    }
}

(exemplarischer Beispielcode)

Ziel ist es, das ich von den zahlreichen vererbten Klassen eine Laden, Daten ändern und diese wieder speichern kann:


User demo = new User();
demo.Load(12345)
demo.skill = 10;
demo.individualMethod3();
demo.Save();

Wie bekomme ich nach dem Laden der Daten in der Load() Methode der Master-Klasse die entsprechenden Variablen der vererbten Klassen gefüllt?

Vielen Dank für die Hilfe!

Viele Grüße,

martin

H
523 Beiträge seit 2008
vor 9 Jahren

Kannst Du nicht einfach eine weitere Methode einfügen, die Du dann in den abgeleiteten Klassen überschreibst?


public virtual void FillFields(......)

Diese Methode könntest in der Load-Methode aufrufen und die Daten, die Du in der Load-Methode in der Basisklasse aus der MongoDB gelesen hast, einfach übergeben:


        public virtual void Load(string id)
        {
            MongoCollection collection = ConsoleApplication1._mongoDatabase.GetCollection(this.GetType(), this.GetType().Name);
            MongoCursor data = collection.FindAs(this.GetType(), Query.EQ("id", id));

            this.FillFields(....);
        }


    class User : Master
    {
        public string forename { get; set; }
        public string lastname { get; set; }
        public string skill { get; set; }

        public void individualMethod3()
        {
        }

        public override void FillFields(....)
        {
                this.forename = ...;
                this.lastname = ...;
                this.skill = ...;
        }
    }


PS: Ich habe keine Ahnung von MongoDB, weshalb ich die Parameter der Methode nicht eingefügt habe.

M
martl Themenstarter:in
23 Beiträge seit 2009
vor 9 Jahren

Genau diese Idee hatte ich auch schon, hab aber gehofft es gibt etwas "automatischeres" 😉 Nichts desto trotz - vielen Dank!

S
322 Beiträge seit 2007
vor 9 Jahren

Hallo,

hast Du Dir mal Reflection angeschaut?
http://www.csharp-examples.net/reflection-property-names/

Gruß

2.078 Beiträge seit 2012
vor 9 Jahren

Mit Reflection würde ist sicherlich gehen, allerdings auch mit einigen Nachteilen und einem Mehraufwand, der meiner Meinung nach den Nutzen nicht rechtfertigt.
Außerdem denke ich, dass die Basis-Klasse sich nicht um die Member der erbenden Klasse kümmern sollte.

Der Vorschlag, eine Methode, die das tut, zu überschreiben, ist die vermutlich beste Lösung für so einen Fall.

49.485 Beiträge seit 2005
vor 9 Jahren

Hallo Palladin007,

grundsätzlich sollte ein Oberklasse nichts von ihren konkreten Unterklassen wissen, das ist richtig. Und Reflection sollte man immer nur gut überlegt einsetzen. Aber gerade beim Laden und Speichern spricht nicht das geringste gegen und viel für den Einsatz von Reflection, um alle Felder (auch die der Unterklasse) zu lesen und zu schreiben.

Methoden in der Unterklasse so zu überschreiben, dass sie die spezifischen Eigenheiten der Unterklasse zu berücksichtigen, ist zwar ein übliches und anerkanntes Vorgehen, aber beim Laden und Speichern wäre das meistens nur fehleranfällige und überflüssige Fleißarbeit. Eine Methode, die per Reflection beliebige Objekte speichern kann, ist aus meiner Sicht hier klar vorzuziehen.

Sollte es dann Spezialitäten geben, mit der so ein allgemeiner Mechanismus ausnahmsweise doch nicht klar kommt, kann man die allgemeine Methode immer noch in der Unterklasse überschreiben, aber auch nur dann.

herbivore

P
1.090 Beiträge seit 2011
vor 9 Jahren

Ich wurde ja einen Generischen Ansatz vorschlagen.

C# Developer Blog:MongoDB Repository Implementation

Die Implementierung hab ich mir jetzt nicht im Detail angeschaut, ich denke aber, das der Ansatz sich auf die gestellte Frage übertragen lässt.

Sollte man mal gelesen haben:

Clean Code Developer
Entwurfsmuster
Anti-Pattern

16.807 Beiträge seit 2008
vor 9 Jahren

Bei MongoDB profitierst Du von der enormen Geschwindigkeit bei Schreiben und Lesen - und das erreichst Du nicht mit Reflection, sondern nur mit manueller Serialisierung.

Mach Dir Repositories, die mit Documents (und optional mit Enitäten) umgehen kann.
Ich selbst habe dafür zwei Repositories erstellt und in NuGet veröffentlicht: https://www.nuget.org/packages/SchwabenCode.MongoDBInfrastructure/ und ganz neu https://www.nuget.org/packages/MongoDBRepository/ (Quellcode ab dem Wochenende auf Codeplex).
Ich hab auch die Fehler gemacht das alles zu sehr OOP-mäßig zu sehen; aber dafür sind eben NoSQL DBs nicht unbedingt gedacht gewesen. Hier lebt man Flexibilität und Redundanz.
Deswegen hab ich auch eine neue Linie begonnen, da einige Änderungen inkompatibel sind.

Und mach Dir POCOs! Die Klassen der Entitäten sollten nichts von Load oder Fill wissen!
Dafür sind eben Repositories verantwortlich.

Und ganz wichtig, wenn ich das Repository von Palin seh: Finger weg von LINQ mit MongoDB!
Nutzt die Queries, die MongoDB liefert. Alles andere ist unfassbar langsam oder wird teilweise gar nicht unterstützt.

PS: das Key-Feld sollte _id und nicht id heißen.
Und vergleich kein ObjectId mit nem String...

742 Beiträge seit 2005
vor 9 Jahren

@martl
Ich finde deinen Ansatz sehr gut, du bekommst hier richtige, schöne Business-Objekte. Viele Ansätze in letzter Zeit tendieren sehr Richtung Anemic Domain Model.

Ich würde wie folgt vorgehen:

  1. Abstrake Domain Models

abstract class User : Master
{
   // Mach das hier ruhig protected, damit du wirklich im Domain Model Änderungen machst und nicht von außen.
    public string Username { get; protected set; }

    public abstract void Save();
}

  1. Repository-Interface

interface IUserRepository
{
    User CreateNew();
    User Find(string id);
    // Kein Save, das funktioniert ja über das Domain Object
}

  1. Mongo User

class MongoUser : User
{
   private BsonDocument document;
   private MongoUserRepository repository;
 
   MongoUser(MongoRepository repository)
   {
      this.document = new BsonDocument();
      this.repository = repository;
   }

   MongoUser(BsonDocument document, MongoRepository repository)
   {
      this.document = document;
      this.repository = repository;

      // Hier auch gerne Reflection
      Username = document.GetAsString("Username");
   }

   public override void Save()
   {
        // Hier kann auch Reflection verwendet werden.
        document.Set("Username", Username);
        repository.Save(document);
   }
}

  1. MongoRepository

public class MongoUserRepository : IUserRepository
{
   User CreateNew() { return new MongoUser(this); }

   User Find(string id) { return new MongoUser(collection.Find(id), this); }
}

Die Vorwendung ist dann bspw. wie folgt:


User user = UserRepository.FindOne(id);
user.SetAndEncryptPassword(oldPassword, newPassword);
user.Save();

Der Vorteil ist ein starkes Domain Model, Unabhängigkeit von der DB-Implementierung und du kannst dem Zwang entkommen, deine Domaine Datenbank-Strukturen zu orientieren. Du hast hier wegen der Einfachvererbung aber Probleme, weil du die Felder der Basis-Klasse u.U. nochmal selber in das Document ablegen muss.

Ich würde dir zu Reflection raten, es gibt hier aber ein paar Varianten, die aus einem lahmen Reflection eine Lösung machen, die im Nanosekunden Bereich dein Objekt umwandeln. Du darfst aber auf keinen Fall deine Struktur an der DB orientieren.

M
martl Themenstarter:in
23 Beiträge seit 2009
vor 9 Jahren

Vielen Dank für die zahlreichen Antworten. Das sind ja einige, komplett Gegensätzliche Lösungsansätze.

Ich versuche mal auf die einzelnen Lösungen einzugehen.

hast Du Dir mal Reflection angeschaut?

>

Gruß

So wie ich Reflection verstanden habe, kann ich dies nicht auf meine Master Classe anwenden, da mir an dieser Stelle der eigentliche, spätere Typ nicht bekannt ist.

Im Link wird die Reflection auf die Klasse MyClass angewendet:

propertyInfos = typeof(MyClass).GetProperties(BindingFlags.Public | BindingFlags.Static);

Bei mir müsste es also wahrscheinlich etwas in Richtung...


propertyInfos = typeof( this ).GetProperties(BindingFlags.Public | BindingFlags.Static);

... was aber, egal wie ich es probiere ( this.getType(), getType(), ... ) in Compilerfehlern endet.

Ich wurde ja einen Generischen Ansatz vorschlagen.


>

Die Implementierung hab ich mir jetzt nicht im Detail angeschaut, ich denke aber, das der Ansatz sich auf die gestellte Frage übertragen lässt.

Funktioniert sehr gut, verwendet allerdings Linq, wovon mir nicht nur Abt abgeraten hat:

Und ganz wichtig, wenn ich das Repository von Palin seh: Finger weg von LINQ mit MongoDB!
Nutzt die Queries, die MongoDB liefert. Alles andere ist unfassbar langsam oder wird teilweise gar nicht unterstützt.

Linq hatte ich schon zu Beginn des Projektes, als ich die diversen C# Implementierungen von Mongo vergleichen habe, ausgeschlossen.

Die beiden NuGet Pakete von Abt sowie den Ansatz von malignate muss ich mir noch anschauen. Letzterer erscheint mir aber recht kompliziert und aufwendig.

Außerdem werde ich schauen ob ich den (generischen) Ansatz von Palin auf MongoQuery statt Linq umschreiben kann.

16.807 Beiträge seit 2008
vor 9 Jahren

Klar kannste alles Generisch umsetzen, ohne auch nur ein bisschen Linq nutzen zu müssen.
Reflection nutze ich nur, um die Felder eines Typs zu ermitteln. Das passiert aber nur ein einziges mal und befindet sich danach im Cache.

So kann ich die komplette Entität User, wie auch nur einzelne Felder mit dem Typ User_HeadInformation laden, das zB nur ID und Benutzername enthält. Alles komplett generisch mit entsprechenden Interfaces und Teil-Klassen.


        private MongoCursor<T> MapFieldsByInheritance<T>( MongoCursor<T> cursor ) where T : class
        {
            if ( MongoDBReflection.IsPartialEntity( typeof( T ) ) )
            {
                string[ ] fieldNames = MongoDBReflection.GetFieldNames<T>( ).Distinct( ).ToArray( );
                cursor = cursor.SetFields( fieldNames );
            }

            return cursor;
        }

Anschließend is eben zB userRepository.GetAll(), GetAll<User>, GetAll<User_HeadInformation> möglich.
Absolut minimaler Codeaufwand bei wirklich sehr guter Geschwindigkeit.

49.485 Beiträge seit 2005
vor 9 Jahren

Hallo martl,

da mir an dieser Stelle der eigentliche, spätere Typ nicht bekannt ist.

klar kommst du an den Typ, einfach per this.GetType(). Du solltest schon in die Doku schauen, wenn du nicht genau weißt, wie die Methode heißt. Allerdings ist nach den Namenskonventionen sowieso klar, dass sie mit einem Großbuchstaben beginnt.

herbivore