datenbank / objekt kapselung - friend-alternative



  • hallo,

    ich suche derzeit einen weg, ein objekt ähnlich diesem:

    class item_t
    {
      public item_t(int number) { m_number = number; }
    
      public int number { get { return m_number; } }
    
      public void process_some_fct( parameter_type param )
      {
        if( nicht_gesetzt() )
          db.fill_item( this );
        //mach was mit daten
      }
    
      private int m_number;
      private int m_foo;
      private string m_bar;
    //...
    };
    

    über eine datenbank zu füllen.
    sehr stark vereinfacht:

    class db
    {
      static public void fill_item( ref item_t in_out )
      {
         var[] row = query( "select * from FOO_TABLE where NUMBER = " + in_out.number.ToString());
    
         in_out.m_bar = row[0];
         in_out.m_foo = row[1];
      }
    };
    

    in c++ gibt es hierfür ja friend; für c# habe ich noch nichts vergleichbares gefunden.

    hintergrund war, dass ich gerne eine fkt hätte, die nichts davon weiß, wo die daten herkommen.

    void foo()
    {
    
      for( ;; )
      {
        item_t my_item( some_special_nr );
    
        var other_db_input = //db....;
        while ( var x = other_db_input.read_next() )
        {
          my_item.process_some_fct( x );
        }
    
        Sleep(10000);
      }
    }
    

    jetzt möchte ich nicht zwangsläufig jedes mal mein gesamtes objekt neu laden, obwohl ich noch die korrekten zustände habe...

    gibt es in c# irgend einen weg, das ganze hübsch zu machen ohne die kapselung von item_t komplett aufzuheben?

    falls die vorgehensweise in euren augen keinen sinn macht, bin ich für rückfragen oder andere vorschläge offen.

    bb 🙂



  • Ich kann dir noch nicht ganz folgen. Was haben denn Friend-Klassen mit Funktionen zu tun, die von der Datenherkunft nichts wissen sollen?

    Irgendwo fehlen mir Details für das Verständnis was du da genau vorhast.

    // EDIT:
    Sofern ich dich richtig verstanden habe, reicht es unter Umständen eine Property anzulegen statts einem Feld und den Setter internal zu setzen.



  • Was haben denn Friend-Klassen mit Funktionen zu tun, die von der Datenherkunft nichts wissen sollen

    das meine db-fkt die member setzen darf und ich trotzdem keine öffentlichen setter schreiben muss.

    das einzige, was mir einfällt ist, ein temporäres objekt von der db erstellen zu lassenmit sämtlichen daten. und das dann in item_t für die zuweisungen zu benutzen. gefällt mir aber auch nicht.

    werds jetzt erst mal komplett public machen (die item_t-member) und hoffen, dass mir iwann noch was besseres einfällt/vorgeschlagen wird 😉



  • public void process_some_fct( parameter_type param )
      {
        if( nicht_gesetzt() )
          db.fill_item( this );
        //mach was mit daten
      }
    

    nicht mal das geht -.-'
    es fehlt ref:
    db.fill_item( ref this );
    -> Fehler CS1605: "<this>" kann nicht als ref- oder out-Argument übergeben werden, da das Element schreibgeschützt ist.

    so ne kacke -.-'



  • Das "ref" bei der Methode ist ja auch kompletter Blödsinn - das Objekt wird ja per se als Referenz übergeben (da "item_t" als Klasse deklariert ist).
    "ref" bzw. "out" wird nur benötigt, wenn die Methode ein neues Objekt zurückgeben soll!

    Wenn die beiden Klassen in einer Assembly sind, würde ich auch hier mit "internal" arbeiten.

    Du könntest aber auch mit Schnittstellen arbeiten, z.B.

    interface IReadOnlyItem
    {
      int Number { get; }
      // ...
    }
    
    interface IItem : IReadOnlyItem
    {
      int Number { get; set; }
      // ...
    }
    
    class Item : IItem
    {
      //
    }
    

    Und nach außen gibst du nur IReadOnlyItem zurück (oder vereinfacht: intern nutzt du die Klasse, aber nach außen nur die Schnittstelle).



  • Was spricht denn gegen den Ansatz "internal" zu verwenden?

    class item_t
    {
      public item_t(int number) { m_number = number; }
    
      public int number { get { return m_number; } }
    
      public void process_some_fct( parameter_type param )
      {
        if( nicht_gesetzt() )
          db.fill_item( this );
        //mach was mit daten
      }
    
      private int m_number;
      internal int m_foo;
      internal string m_bar;
    //...
    };
    

    Und in deiner Klasse db kannst du anschließend darauf zugreifen:

    class db
    {
      static public void fill_item( ref item_t in_out )
      {
         var[] row = query( "select * from FOO_TABLE where NUMBER = " + in_out.number.ToString());
    
         in_out.m_bar = row[0];
         in_out.m_foo = row[1];
      }
    };
    

    Der Zugriff auf "m_bar" und "m_foo" ist dann solang möglich, wie sich die der Code in der Assembly von "MyItem" befindet. - Außerhalb von der Assembly sind keine Zugriffe möglich und die Eigenschaft wird auch nicht angezeigt.



  • habs jetzt in etwa so gemacht:

    public class entity_helper<T>
    {
      private T me;
    #if DEBUG
      private bool wrote = false;
    #endif
    
      public void set(T value)
      {
    #   if DEBUG
          wrote = true;
    #   endif
        me = value;
      }
      public T get()
      {
    #   if DEBUG
          if(!wrote)
            throw new Exception("...");
    #   endif
        return me;
      }
    }
    
    public class base_state
    {
      public base_state()
      {
        m_foo = new entity_helper<String>();
        m_bar = new entity_helper<Int64>();
      }
      protected entity_helper<String> m_foo;
      protected entity_helper<Int64> m_bar;
    }
    
    public class data_state_update : base_state
    {
      public String foo { get { return m_foo.get(); } set { m_foo.set(value); } }
    }
    public class data_state_get : base_state
    {
      public String foo { get { return m_foo.get(); } set { m_foo.set(value); } }
      public Int64 bar { get { return m_bar.get(); } set { m_bar.set(value); } }
    }
    
    public void update_state(data_state_update param)
    {
    ...
    }
    public data_state_get get_state()
    {
    ...
    }
    
    //-------
    
    void foo_bar()
    {
      var data = new data_state_update();
      {
        data.foo = ...
        data.bar = ...
      }
    
      update_state( data );
    }
    

    ob andere compiler die präprozessordirektiven auch unterstützen, weiß ich nicht - aber msvc tuts jedenfalls.

    spricht da was dagegen abgesehen vom hohen schreibaufwand?
    deutlich einfacher wäre es natürlich nur nen ctor zu haben und die member protected zu lassen, aber ich bin kein fan von so vielen werten im ctor setzen.

    wäre übrigens auch an ner möglichkeit interessiert, zu erkennen, ob nen member von data_... gesetzt wurde und nicht gelesen wurde.
    aber da habe ich bisher noch nichts sinnvolles hinbekommen, weil ich das nur einmal in entity_helper schreiben möchte und dann gerne angaben hätte, die ich dort nicht habe(in welcher klasse ist der wert und wie heißt die membervariable).

    danke, bb 🙂



  • EDIT: Unfug. Dachte es ginge um C++.

    #if DEBUG in C# ist OK.
    Wie man dem Compiler beibringt dass in einer bestimmten Config DEBUG definiert sein soll ist von der IDE und/oder dem Compiler anhängig. #if SYMBOLNAME ist aber auf jeden Fall OK in C# und sollte von jedem Compiler akzeptiert werden.



  • also (mal copy&paste):

    public class entity_helper<T>
        {
    #if DEBUG
            private bool m_set = false;
            private bool m_get = false;
    #endif
            private T m_value;
    
            public void set(T value)
            {
    #if DEBUG
                m_set = true;
                m_get = false;
    #endif
                m_value = value;
            }
    
            public T get()
            {
    #if DEBUG
                if( !m_set )
                    throw new Exception( "DB value not set" );
                m_get = true;
    #endif
                return m_value;
            }
    
            public entity_helper()
            {}
            public entity_helper(T value)
            {
                set( value );
            }
    #if DEBUG
            public entity_helper(entity_helper<T> old)
            {
                m_set = old.m_set;
                m_get = old.m_get;
                m_value = get();
            }
    
            ~entity_helper()
            {
                if( m_set && !m_get )
                    Console.WriteLine( "unused DB variable: '" + get().ToString() + "'" );
            }
    #endif
        }
    

    dazu so:

    public class base_xyz
        {
            protected entity_helper<String> m_a = new entity_helper<String>();
            protected entity_helper<Int64> m_b = new entity_helper<Int64>();
            protected entity_helper<Int64> m_c = new entity_helper<Int64>();
            protected entity_helper<Int64> m_d = new entity_helper<Int64>();
            protected entity_helper<bool> m_e = new entity_helper<bool>();
            protected entity_helper<Int64> m_f = new entity_helper<Int64>();
            protected entity_helper<String> m_g = new entity_helper<String>();
            protected entity_helper<DateTimeOffset> m_h = new entity_helper<DateTimeOffset>();
            protected entity_helper<DateTimeOffset> m_i = new entity_helper<DateTimeOffset>();
            protected entity_helper<String> m_j = new entity_helper<String>();
            protected entity_helper<Int64> m_k = new entity_helper<Int64>();
        }
    

    und

    public class xyz_close_data : base_xyz
        {
            public Int64 A { get { return m_a.get(); } set { m_a.set( value ); } }
            public String B { get { return m_b.get(); } set { m_b.set( value ); } }
            public DateTimeOffset C { get { return m_c.get(); } set { m_c.set( value ); } }
            public String D { get { return m_d.get(); } set { m_d.set( value ); } }
        }
    

    findet ihr auch ok? oder spricht da irgendwas dagegen?

    danke, bb 🙂



  • Jetzt ist wieder alles public nur halt in Wrappern und auf die protected Member kann die Datenbank auch nicht zugreifen.

    Mach einen Constructor der den Datenbanktyp (zB eine DataRow) entgegegen nimmt und sich daraus initialisiert. Das könntest du dann noch per internal verstecken.



  • @unskilled
    Lies mal das da
    http://www.mehdi-khalili.com/orm-anti-patterns-part-4-persistence-domain-model/

    ----

    KN4CK3R schrieb:

    und auf die protected Member kann die Datenbank auch nicht zugreifen.

    Doch, schon, per Reflection. Was auch einige ORM Frameworks machen. Nicht dass ich das besonders toll finden würde (ganz im Gegenteil), aber gehen tut es.



  • KN4CK3R schrieb:

    Jetzt ist wieder alles public nur halt in Wrappern

    klar ist es public. aber eben nur als parameter-ersatz.

    class xyz
    {
      void update()
      {
        var param = new data_update_xyz();
        {
          param.a = m_foo;
          param.b = 3;
          //...
        }
    
        database.update_xyz( param );
      }
    }
    

    Mach einen Constructor der den Datenbanktyp (zB eine DataRow) entgegegen nimmt und sich daraus initialisiert. Das könntest du dann noch per internal verstecken.

    1. versteckt internal nicht
    2. geht das beim update/set aber nicht beim select/get
    oder?

    KN4CK3R schrieb:

    und auf die protected Member kann die Datenbank auch nicht zugreifen.

    soll (und darf!) sie ja auch gar nicht; deshalb ists ja protected und nicht public...
    die basis-klasse hat im prinzip nur eine aufgabe:
    (relevante) änderungen an der tabelle an sich kann ich sofort im code übertragen und der compiler sagt mir dann, ob alles gut ist oder ich was anpassen muss...

    @hustbaer:
    ich habs bisher zugegebenermaßen nicht komplett gelesen sondern meist nur überflogen; sehe aber nicht, wie das zu meiner frage passt weil da nur die grundsätzliche design-entscheidung diskutiert wird (objekt- oder relationen-bezogen). evtl wurde auch nicht so ganz deutlich, dass ich das ganze nur zwischen objekt und datenbank verwende; mich beim design also schon recht deutlich entschieden habe
    ich werds die tage mal komplett lesen; aber heut abend nicht mehr 😉


Anmelden zum Antworten