[OO/O-R] Dirty state van een object bepalen

Pagina: 1
Acties:
  • 118 views sinds 30-01-2008
  • Reageer

  • Michali
  • Registratie: Juli 2002
  • Laatst online: 09-12-2025
Ik ben erg benieuwd naar de verschillende methoden waarmee bepaald kan worden of een bepaald object, dat uit de database getrokken is en eventueel weer opgeslagen moet worden, dirty of niet is.

Enkele methoden die ik zelf wel kan bedenken of eens heb gezien:

-Een clone van een object maken en middels een unieke ID later terug zoeken voor vergelijking.
Een voordeel van deze methode is dat je het class zelf met rust laat en dat je later niets in de DB zelf hoeft te controleren. Nadeel lijkt mij is dat het clonen misschien niet altijd goed gaat ivm. met references en deep cloning etc.

-De gegevens opnieuw selecteren uit de DB bij het opslaan en dan de waarden vergelijken.
Ook hier laat je de class met rust. Het voordeel boven clonen is dat je daar geen problemen mee kan krijgen. Verder weet ik niet of dit nou zo veel sneller is dan gewoon de update uitvoeren zonder die controle.

-Het bewaren van de geselecteerde gegevens bij het initialiseren en later ophalen middels een unieke ID.
Ongeveer hetzelfde als de vorige. Nadeel lijkt me alleen dat je bij iedere save weer opnieuw een select moet doen, omdat je met de volgende save rekening moet houden (dan is de nieuwe row data immers nodig), dus geeft dat eigenlijk geen voordeel boven de vorige.

-Het bijhouden van een flag in de class zelf.
Dit lijkt mij de snelste methode (in performance dan). Een nadeel is dat je de class zelf er mee moet gaan 'vervuilen'. Ook moet je er aan denken bij iedere set de flag te zetten. En wil je dit vanuit een automatische mapper laten uitlezen, dan moet je de class bijna wel van een vaste superclass laten erven of een interface laten implementeren.

Wat is de beste methode en waarom?


Dan nog een vraag mbt. collecties binnen een object. Hoe bepaal je daarbij of er een object is toegevoegd of is verwijderd?

Hoe ik het nu vaak doe is het verzorgen van een speciale decorator collectie welke de wijzigingen doorvoert in de DB. Een groot nadeel is daarvan dat de save/store whatever actie daar dan geen effect meer op heeft. Ik wil dus eigenlijk dat de associatie pas gelegd wordt bij het expliciet aanroepen van zo'n actie en niet dat de collectie dat 'stiekem' voor me regelt.

Hierbij kan ik zelf ook wel wat bedenken en dat is:

-Het ophalen van de ID's van alle associaties en daarmee vergelijken.
Dit lijkt me een hele nette methode, alleen is dit wel performant genoeg?

Zijn er hier nog meer methoden?

Noushka's Magnificent Dream | Unity


  • kingmuze
  • Registratie: Februari 2003
  • Laatst online: 05-11-2025

kingmuze

so don't fear

Bedoel je met "dirty" of het object gewijzigd is na het ophalen vanuit de database?

[gvr]muze[nl] says: fear is the mind killer


  • Michali
  • Registratie: Juli 2002
  • Laatst online: 09-12-2025
kingmuze schreef op maandag 01 mei 2006 @ 14:46:
Bedoel je met "dirty" of het object gewijzigd is na het ophalen vanuit de database?
Ja, dat bedoel ik ermee.

Noushka's Magnificent Dream | Unity


  • kingmuze
  • Registratie: Februari 2003
  • Laatst online: 05-11-2025

kingmuze

so don't fear

De optie van de flag is opzich niet mooi, aan de andere kant kan wanneer van het object de destructor wordt aangeroepen automatisch voor zichzelf controlleren of deze in de database geupdate moet worden.

Zou dat een mogelijkheid zijn?

BTW wat betekent O-R?

[ Voor 5% gewijzigd door kingmuze op 01-05-2006 15:23 ]

[gvr]muze[nl] says: fear is the mind killer


  • JHS
  • Registratie: Augustus 2003
  • Laatst online: 04-01 15:49

JHS

Splitting the thaum.

Ik laat zelf alle sets lopen via een overloading generieke set method, waardoor het bijhouden van die dirty flag geen probleem is. Ik gebruik daar inderdaad een vaste superclass voor. Mocht het nodig zijn om set methods te veranderen in de child, dan laat ik dat over een gedeeltelijke alias zijn van de generieke set method.

Een andere optie zou misschien het aanmaken van een __init_vars() variabele waarmee bij het saven / destroyen vergeleken zou kunnen worden. Zo heb je geen flag en overloading set method nodig, maar het nadeel is dat er als eerste alsnog moet gaan vergelijken, en ten tweede alsnog het object vervuild. Het voordeel van het vergelijken is overigens wel dat als er gewijzigd zou worden naar de initiële waarde er niet zal worden opgeslagen.

De derde methode lijkt me database vervuiling. Je kan dan net zo goed de tweede methode gebruiken volgens mij. Of de tweede methode sneller is dan gewoon een update doen ligt denk ik aan de gegevens en de indices, maar vergeet niet dat je er ook nog het vergelijken mee op moet tellen. Het lijkt me qua OO een goede / de beste methode, maar ik betwijfel of het (altijd) performance-technisch gezien verstandig is.

De eerste methode lijkt me an sich goed, maar zoals je zelf al zei haal je je potentieel onnodige problemen op de hals. Of dat voldoende reden is om dit niet toe te passen kan ik niet voldoende inschatten.

Alhoewel de flag methode misschien niet het netst is en problemen oplevert m.b.t. consistent aanroepen kies ik er dus wel voor deze te gebruiken. Of dat verstandig is of niet en of ik verder een hoop over het hoofd zie weet ik niet, maar zo heb je iniedergeval inzicht in mijn keuze :) .

edit:
kingmuze: Het gaat om de overweging óf die flag methode toepasbaar zou zijn. Dát het toepasbaar is was * Michali vermoedelijk al achter :P . O/R doelt overigens Object/Relational mapping, waar een boel over is te vinden.

[ Voor 9% gewijzigd door JHS op 01-05-2006 15:28 ]

DM!


  • Michali
  • Registratie: Juli 2002
  • Laatst online: 09-12-2025
@kingmuze: Ik zou een object niet zichzelf in de DB willen laten opslaan of hem daar zelf de verantwoordelijkheid over geven. Zeker niet vanuit de destructor.

@JHS: Bij de derde methode bedoel ik niet dat ik de gegevens in de DB bewaar, maar gewoon in het geheugen. Ik sla row data dan gewoon tijdelijk op. Dat is eigenlijk als een soort besparing bij de save, als die er komt, zodat ik niet opnieuw hoef te selecteren. Maar die besparing is er eigenlijk helemaal niet, omdat je ook weer met de volgende save rekening moet houden. Je moet dan de row data gaan aanpassen met de gegevens in het object zelf of de rij opnieuw selecteren. Een beetje onzinnig dus eigenlijk.

Ik wil eigenlijk toch niet kiezen voor een vaste superclass, en met zo'n flag wil ik toch niet werken.

Ik denk dat ik een beetje ga experimenteren met de 2de methode en ook degene met die collecties. Als iemand nog toevoegingen heeft, dan graag.

Noushka's Magnificent Dream | Unity


Verwijderd

#2 heeft in een multi-user omgeving als nadeel dat je object ook dirty is wanneer iemand anders ondertussen het record gewijzigd heeft. Klopt op zich wel (de gegevens wijken af van de op dat moment geldende "werkelijkheid"), maar 't is maar de vraag of je vervolgens de wijzigingen van die ander zomaar ongedaan wilt maken...

Ik zou uitgaan van een dirty-flag in de class zelf (of zelfs per member), in combinatie met een check, liefst op veld-niveau, of er ondertussen iets gewijzigd is. Hoe je vervolgens omgaat met verschillen (niet opslaan, de gebruiker vragen, verschillen loggen, of gewoon opslaan) is een keuze die je zelf moet maken.
Mijn ervaring is dat meestal een "de laatste wint" strategie, evt. in combinatie met logging, in de praktijk goed voldoet.

  • whoami
  • Registratie: December 2000
  • Laatst online: 21:39
Michali schreef op maandag 01 mei 2006 @ 14:39:
Ik ben erg benieuwd naar de verschillende methoden waarmee bepaald kan worden of een bepaald object, dat uit de database getrokken is en eventueel weer opgeslagen moet worden, dirty of niet is.

Enkele methoden die ik zelf wel kan bedenken of eens heb gezien:

-Een clone van een object maken en middels een unieke ID later terug zoeken voor vergelijking.
Een voordeel van deze methode is dat je het class zelf met rust laat en dat je later niets in de DB zelf hoeft te controleren. Nadeel lijkt mij is dat het clonen misschien niet altijd goed gaat ivm. met references en deep cloning etc.

-De gegevens opnieuw selecteren uit de DB bij het opslaan en dan de waarden vergelijken.
Ook hier laat je de class met rust. Het voordeel boven clonen is dat je daar geen problemen mee kan krijgen. Verder weet ik niet of dit nou zo veel sneller is dan gewoon de update uitvoeren zonder die controle.

-Het bewaren van de geselecteerde gegevens bij het initialiseren en later ophalen middels een unieke ID.
Ongeveer hetzelfde als de vorige. Nadeel lijkt me alleen dat je bij iedere save weer opnieuw een select moet doen, omdat je met de volgende save rekening moet houden (dan is de nieuwe row data immers nodig), dus geeft dat eigenlijk geen voordeel boven de vorige.

-Het bijhouden van een flag in de class zelf.
Dit lijkt mij de snelste methode (in performance dan). Een nadeel is dat je de class zelf er mee moet gaan 'vervuilen'. Ook moet je er aan denken bij iedere set de flag te zetten. En wil je dit vanuit een automatische mapper laten uitlezen, dan moet je de class bijna wel van een vaste superclass laten erven of een interface laten implementeren.
Ikzelf zou de laatste methode verkiezen.
Ik vraag me af wat je precies bedoelt met 'vervuilen'. Ok, die state-tracking is iets wat eigenlijk niets te maken heeft met de functionaliteit van de class zelf, maar het is imo wel de meest pragmatische manier om te werken.
De manier ivm clonen kan wel een probleem opleveren; je kan natuurlijk ook de class serializen en deserializen en dan gaan vergelijken, maar dan moeten je classes wel serializable zijn. Nadeel hier is wel dat je nogal wat objecten in je geheugen zult hebben, en hoe / wanneer bepaal je dat een bepaalde clone gediscarded mag worden ? Wat ga je doen als je object A uit de DB laadt, deze niet updated, en even later opnieuw object A uit de DB laadt -die ondertussen kan veranderd zijn- en dan wel een update wil doen ?
Het opnieuw ophalen van de gegevens uit de DB is imo slecht voor de performantie.
Dan nog een vraag mbt. collecties binnen een object. Hoe bepaal je daarbij of er een object is toegevoegd of is verwijderd?
Bij de .NET collection classes heb je een event / method die uitgevoerd wordt, iedere keer dat een object aan de collectie toegevoegd of verwijderd wordt.
Ik heb op m'n weblog een reeks artikels waarin ik wijzigingen aanbreng in het CSLA framework van Rockford Lhotka, zie hier, waar ik die methods / events v/d collections ga gebruiken.
Zijn er hier nog meer methoden?
Een O/R mapper zoals (N)Hibernate gebruiken ?

https://fgheysels.github.io/


  • Genoil
  • Registratie: Maart 2000
  • Laatst online: 12-11-2023
Ik gebruik Propel, en daarbij heeft elk businessobject een member $modifiedColumns. Deze wordt gezet vanuit elke setter, en weer geinitialiseerd na elke save() operatie.

PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
<?php

require_once 'propel/om/Persistent.php';

/**
 * This class contains attributes and methods that are used by all
 * business objects within the system.
 *
 * @author Hans Lellelid <hans@xmpl.org> (Propel)
 * @author Frank Y. Kim <frank.kim@clearink.com> (Torque)
 * @author John D. McNally <jmcnally@collab.net> (Torque)
 * @version $Revision: 316 $
 * @package propel.om
 */
abstract class BaseObject {

    /**
     * attribute to determine if this object has previously been saved.
     * @var boolean
     */
    private $_new = true;
    
    /**
     * attribute to determine whether this object has been deleted.
     * @var boolean
     */
    private $_deleted = false;
    
    /**
     * The columns that have been modified in current object.
     * Tracking modified columns allows us to only update modified columns.
     * @var array
     */
    protected $modifiedColumns = array();
    
    /**
     * Returns whether the object has been modified.
     *
     * @return boolean True if the object has been modified.
     */
    public function isModified()
    {
        return !empty($this->modifiedColumns);
    }
    
    /**
     * Has specified column been modified?
     * 
     * @param string $col
     * @return boolean True if $col has been modified.
     */
    public function isColumnModified($col)
    {        
        return in_array($col, $this->modifiedColumns);
    }        
    
    /**
     * Returns whether the object has ever been saved.  This will
     * be false, if the object was retrieved from storage or was created
     * and then saved.
     *
     * @return true, if the object has never been persisted.
     */
    public function isNew()
    {
        return $this->_new;
    }

    /**
     * Setter for the isNew attribute.  This method will be called
     * by Propel-generated children and Peers.
     *
     * @param boolean $b the state of the object.
     */
    public function setNew($b)
    {
        $this->_new = (boolean) $b;
    }    
    
    /**
     * Whether this object has been deleted.
     * @return boolean The deleted state of this object.
     */
    public function isDeleted()
    {
        return $this->_deleted;
    }
    
    /**
     * Specify whether this object has been deleted.
     * @param boolean $b The deleted state of this object.
     * @return void
     */
    public function setDeleted($b)
    {
        $this->_deleted = (boolean) $b;
    }
    
    /**
     * Sets the modified state for the object to be false.
     * @param string $col If supplied, only the specified column is reset.
     * @return void
     */
    public function resetModified($col = null)
    {
        if ($col !== null)
        {
            while (($offset = array_search($col, $this->modifiedColumns)) !== false)
                array_splice($this->modifiedColumns, $offset, 1);
        }
        else
        {
            $this->modifiedColumns = array();
        }
    }
   
    /**
     * Compares this with another <code>BaseObject</code> instance.  If
     * <code>obj</code> is an instance of <code>BaseObject</code>, delegates to
     * <code>equals(BaseObject)</code>.  Otherwise, returns <code>false</code>.
     *
     * @param obj The object to compare to.
     * @return    Whether equal to the object specified.
     */
    public function equals($obj)
    {
        if (is_object($obj)) {
            if ($this === $obj) {
                return true;
            } elseif ($this->getPrimaryKey() === null || $obj->getPrimaryKey() === null)  {
                return false;
            } else {
                return ($this->getPrimaryKey() === $obj->getPrimaryKey());
            }
        } else {
            return false;
        }
    }    

    /**
     * If the primary key is not <code>null</code>, return the hashcode of the
     * primary key.  Otherwise calls <code>Object.hashCode()</code>.
     *
     * @return int Hashcode
     */
    public function hashCode()
    {
        $ok = $this->getPrimaryKey();
        if ($ok === null) {
            return crc32(serialize($this));
        }
        return crc32(serialize($ok)); // serialize because it could be an array ("ComboKey")
    }

    /**
     * Logs a message using Propel::log().
     *
     * @param string $msg
     * @param int $priority One of the Propel::LOG_* logging levels
     * @return boolean
     */
    protected function log($msg, $priority = Propel::LOG_INFO)
    {
        return Propel::log(get_class($this) . ': ' . $msg, $priority);
    }
    
}
}


Tis misschien niet optimaal, maar het is zo doorzichtig dat iedereen het snapt.

[ Voor 6% gewijzigd door Genoil op 01-05-2006 18:53 ]


  • EfBe
  • Registratie: Januari 2000
  • Niet online
De methodiek van het bijhouden van de originele values en die later weer vergelijken is de methodiek die in hibernate en een paar anderen wordt gebruikt. Het is traag en heeft als nadeel dat je een centraal object moet hebben dat de originele state heeft. Als je een server farm hebt, en je laadt het object van server A en saved het middels server B, weet B niet of het object nieuw is of niet.

Een methodiek die erg veel wordt toegepast is inderdaad change tracking in het object zelf. Ikzelf gebruik ook deze benadering, omdat je de state in het object zelf hebt, en dus geen centrale opslag nodig hebt. Het is ook erg snel en dat is ook altijd meegenomen.

Over 'vervuiling' van je class: ... ik vind dat altijd zo'n non-discussie. Men wil functionaliteit hebben zodat ze verder kunnen met hun werk. Maar daarvoor wil men kennelijk geen consessies doen. Het rare is dat wanneer men het zelf zou schrijven de voor de handliggende central base class een oplossing is die bijna altijd gekozen zou zijn. En daarin dus de change tracking.

Overigens zijn er technieken in gebruik bij sommige O/R mappers die bytecode / IL emitten in entity classes at runtime, of na het compileren. Versant open access gebruikt dat geloof ik. Je krijgt dus change tracking in je objects maar je schrijft verder je code met POCO/POJO classes.

Het nadeel is dat wanneer er een bug in je code zit, die te herleiden is aan de ge-modificeerde IL/Bytecode, je erg ver van huis bent, want debuggen is niet mogelijk.

Creator of: LLBLGen Pro | Camera mods for games
Photography portfolio: https://fransbouma.com


  • Michali
  • Registratie: Juli 2002
  • Laatst online: 09-12-2025
Ok, wel overtuigend. Ik ga dan maar gewoon makkelijk doen en de state in de class zelf bijhouden. Dat is ook geen probleem eigenlijk.

En dat met die event in collection classes is idd ook wel een goed idee. Ik werkte nu eigenlijk vooral met decorators, maar puur met events kun je dat vaak ook anders oplossen. Daar ga ik zeker even mee spelen. Ik moet alleen even kijken wanneer ik wat laat registreren als geinteresseerd in een event.

Noushka's Magnificent Dream | Unity


  • CaptBiele
  • Registratie: Juni 2002
  • Laatst online: 27-08-2021

CaptBiele

No Worries!

Michali schreef op dinsdag 02 mei 2006 @ 10:19:
Ok, wel overtuigend. Ik ga dan maar gewoon makkelijk doen en de state in de class zelf bijhouden. Dat is ook geen probleem eigenlijk.

En dat met die event in collection classes is idd ook wel een goed idee. Ik werkte nu eigenlijk vooral met decorators, maar puur met events kun je dat vaak ook anders oplossen. Daar ga ik zeker even mee spelen. Ik moet alleen even kijken wanneer ik wat laat registreren als geinteresseerd in een event.
Ik gebruik vaak een Dirty property in een base class voor de controllers (mvc pattern). Hierin heb ik BindingBase_ColumnChanged gedefinieerd. Als deze wordt afgevuurd, zet ik Dirty op true.
Deze kan ik vervolgens uitlezen in alle views (ui's)
Pagina: 1