IdentityMap: come gestisco le query?

rated by 0 users
This post has 20 Replies | 4 Followers

Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907
petrux Posted: 04-15-2009 16.48

Ciao a tutti,

visto che oggi se ne è parlato tanto, butto giù un altro post sull'argomento... In pratica mi sto trovando a fronteggiare questo problema: seguendo l'esempio "costruisci il tuo DAL" nel capitolo 6 del libro di Andrea Saltarello, mi sono imbattuto nel problema dell'unicità degli oggetti.

Il testo fa giustamente notare che, se io chiedeo al mio DataContext un GetByKey(object key), il DataContext cerca l'oggetto nell'identity map e se non lo trova lo fa fetchare al DataMapper.

La questione ora è: cosa succede nel GetAll<T>() o nel GetByCriteria(Query)? Onestamente non saprei proprio come implementare la cosa...

grazie,

Giulio

 

  • | Punteggio Post: 35
Top 10 Partecipanti
Post 143
Punteggio 2.560
Ciao Giulio,

You wrote on 15/04/2009 :
> La questione ora è: cosa succede nel GetAll() o nel GetByCriteria(Query)?
> Onestamente non saprei proprio come implementare la cosa...

bella domanda.
La soluzione pi� elegante secondo me � che venga prima fatta una query
sulla identit� map alla ricerca di elementi che abbiamo gi� in memoria
e che soddisfino i criteri di ricerca, dopodich� vado dal data mapper
con la query e una lista di PK che ho gi� in memoria e che non voglio
che il data mapper "fetchi", quello che mi arriva di nuovo lo metto
nell'identity map, faccio una Union con quello che ho gi� e lo ritorno.

..m

--
Mauro Servienti
{C67C0157-5D98-4733-A75E-93CAEE4BADC8}
Microsoft MVP - Visual C# / MCP
http://mvp.support.microsoft.com
http://blogs.ugidotnet.org/topics
whynot [ at ] topics [ dot ] it
  • | Punteggio Post: 50
Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

Una soluzione è questa, prima fai la query al db per tornare tutti i dati che soddisfano la query, poi per ogni dato quando vai a cercare di reidratare l'entità, ovvero ricreare l'oggetto in memeoria, controlli nell'identity map se è già presente, se si usi quello altrimenti reidtrati l'entità e la metti nell'identity map.

nhibernate segue questo approccio, infatti prima delle query effettua un flush per essere sicuro che il db sia allineato.

Se nel tuo querymodel usi linq, potrebbe essere più interessante usare la query come linq to object sugli oggetti in memoria, poi eseguirla nel db, cosi non devi fare flush per evitare problemi con le entità dirty.

alk.

  • | Punteggio Post: 35
Top 25 Partecipanti
Maschio
Post 67
Punteggio 1.440

>Una soluzione è questa, prima fai la query al db per tornare tutti i dati che soddisfano la query,
>poi per ogni dato quando vai a cercare di reidratare l'entità, ovvero ricreare l'oggetto in memeoria,
>controlli nell'identity map se è già presente, se si usi quello altrimenti reidtrati l'entità e la metti nell'identity map.

>nhibernate segue questo approccio, infatti prima delle query effettua un flush per essere sicuro che il db sia allineato.

praticamente tutti gli O/RM (es: EF) fanno così, perchè il "costo" di produrre una query filtrata è considerato superiore al "risparmio" ottenuto: tipicamente, infatti, il "costo vero" non è costituito dalla esecuzione della query/fetch dei dati, bensì dalla materializzazione degli oggetti e, da questo punto di vista, le due strategie hanno un comportamento analogo

.A

Top 25 Partecipanti
Maschio
Post 67
Punteggio 1.440

'giorno Giulio, giusto una piccola nota: da un punto di vista concettuale, la risposta di Mauro non fa una piega. Sempre da un punto di vista concettuale, allora, il life cycle della identity map non dovrebbe coincidere con quello della unit of work, bensì con le sue transazioni. Ciò premesso, però, come già ebbi modo di affermare qui: http://blogs.ugidotnet.org/pape/archive/0001/01/01/identity-map-non-e-una-cache.aspx

L'implementazione di una identity map è proprio uno dei casi definibili: "vorrei ma non posso", nei quali implementare un comportamento concettualmente irreprensibile ha un costo (effort, performance, ...) tipicamente ingiustificato. Il buon Francesco Salvi negli anni '80 avrebbe cantato "Pragmatismo senza limitismo, esagerare!" :-)

.A

Top 10 Partecipanti
Post 143
Punteggio 2.560
Ciao "Capo",

You wrote on 16/04/2009 :
> 'giorno Giulio, giusto una piccola nota: da un punto di vista concettuale, la
> risposta di Mauro non fa una piega. Sempre da un punto di vista concettuale,
> allora, il life cycle della identity map non dovrebbe coincidere con quello
> della unit of work, bensì con le sue transazioni. Ciò premesso, però, come
> già ebbi modo di affermare qui:
> http://blogs.ugidotnet.org/pape/archive/0001/01/01/identity-map-non-e-una-cache.aspx
>

Non posso che essere d'accordo, infatti sarebbe la soluzione pi�
elegante, ma di certo non la pi� praticabile.

..m

--
Mauro Servienti
{C67C0157-5D98-4733-A75E-93CAEE4BADC8}
Microsoft MVP - Visual C# / MCP
http://mvp.support.microsoft.com
http://blogs.ugidotnet.org/topics
whynot [ at ] topics [ dot ] it
  • | Punteggio Post: 5
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907
Ciao Mauro,

mauroservienti ha scritto:
> bella domanda.

Grazie.

> La soluzione pi� elegante secondo me � che venga prima fatta una query
> sulla identit� map alla ricerca di elementi che abbiamo gi� in memoria
> e che soddisfino i criteri di ricerca, dopodich� vado dal data mapper
> con la query e una lista di PK che ho gi� in memoria e che non voglio
> che il data mapper "fetchi", quello che mi arriva di nuovo lo metto
> nell'identity map, faccio una Union con quello che ho gi� e lo ritorno.

Uhm... fammi riflettere...  * petrux riflette...
Allora, innanzitutto estendiamo l'insieme dei vocaboli italiani con
"fetchare", così non lo dobbiamo più mettere tra virgolette. Fa schifo,
ma ci viene comodo. :-) (Per la cronaca, l'unico che non riesco ancora a
tollerare è "deployare"... brrrr....).

Andiamo con ordine. Nell'assembly X.Data ho definito l'interfaccia per
il data context e l'interfaccia generica per il data mapper:
//{{{
public interface IDataContext
{
IList GetByCriteria(Query q) where T : class, new();
}
public interface IDataMapper
{
IList GetByCriteria(Query q);
}
//}}}

In un altro assembly X.Data.Base definisco un base data context e
l'identity map:
//{{{
public class IdentityMap
{
public Object GetObject(Type t, object key) { ... }
}
public class BaseDataContext : IDataContext
{
public IList GetByCriteria(Query q)
{
//???
}
}
//}}}

In poche parole: tu dici di esporre anche un metodo GetKeyList(Type t)
che torni una lista con tutte le chiavi degli oggetti contenuti per il
particolare tipo 't' e che poi venga ripassata come "black list" al data
mapper (che ora avrà un metodo GetByCriteria(Query q, object[]
excludeKeys))?

A questo punto però te la sei cercata... :-)
In teoria il mio IDataContext (e quindi anche BaseDataContext) espone un
metodo GetByCriteria(Query q, int index, int size). Questo caso può
andare trattato in maniera anloga? A occhio direi di no, perché non
tutti gli oggetti memorizzati nella IdentityMap saranno potenzialmente
inclusi nel risultato del metodo, quindi dovrei comunque fetcharli,
controllare le chiavi e se queste sono presenti nell'identity map ed
eventualmente rimpiazzare gli elementi "duplicati". In questo modo avrei
solo l'overhead del fetch di elementi già contenuti nell'identity map
(ovviamente non potrei passare l'intera black list al data mapper
perchpé altrimenti non avrei idea di quali oggetti mappati siano da
inserire nel risultato della query e quali no).

Da questa, sorge un'altra domanda: non verrebbe un po' più "facile"
imporre che tutte le entità del dominio implementino una interfaccia
comune IEntity che esponga una property "Key" di tipo stringa (o object,
o quello che è) così da ridurre un po' il codice che mi devo smazzare?
:-)

Ciao, grazie e buona giornata,
Giulio
--
http://www.giuliopetrucci.it
http://www.myspace.com/fujikomonamourtheband
  • | Punteggio Post: 5
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907
Ciao alkampfer,

alkampfer ha scritto:
> Una soluzione è questa, prima fai la query al db per tornare tutti i
> dati che soddisfano la query, poi per ogni dato quando vai a cercare di
> reidratare l'entità, ovvero ricreare l'oggetto in memeoria, controlli
> nell'identity map se è già presente, se si usi quello altrimenti
> reidtrati l'entità e la metti nell'identity map.

Uhm... potresti definirmi al volo cosa intendi per reidratare?

> nhibernate segue questo approccio, infatti prima delle query effettua un
> flush per essere sicuro che il db sia allineato.

flush? In che senso?

> Se nel tuo querymodel usi linq, potrebbe essere più interessante usare
> la query come linq to object sugli oggetti in memoria, poi eseguirla nel
> db, cosi non devi fare flush per evitare problemi con le entità dirty.

Sono convinto che anche questo mi sarà chiaro non appena mi saranno
chiariti i punti precedenti. :-p

Ciao e scusate la niubbagine,
Giulio
--
http://www.giuliopetrucci.it
http://www.myspace.com/fujikomonamourtheband
  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907
andysal ha scritto:
> 'giorno Giulio,

'giorno Andrea,

> giusto una piccola nota: da un punto di vista
> concettuale, la risposta di Mauro non fa una piega. Sempre da un punto
> di vista concettuale, allora, il life cycle della identity map non
> dovrebbe coincidere con quello della unit of work, bensì con le sue
> transazioni.

Uhm... ogni bambino che so rispetti chiederebbe: "e perché"? :-)
Io ho n-mila oggetti sparsi nel mio universo. Uno di questi (che
chiameremo oggetto X la cui chiave è "x") subisce una modifica in una
serie di modifiche. Le modifiche vengono "committate" e viene quindi
committata anche la modifica fatta su "x". A questo punto, se non ho
capito male, la IM dovrebbe venire buttata via (o almeno svuotata). Ma
perché? Supponiamo che nella transazione successiva (e quindi sempre
nello stesso ciclo di vita della UoW) un client richieda l'oggetto X, ha
senso che gli venga ritornato quello contenuto nella IM, no? (Ok,
probabilmente ho delirato... non mi insultate! :-p).

Ciao,
Giulio
--
http://www.giuliopetrucci.it
http://www.myspace.com/fujikomonamourtheband
  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907

Ah, dimenticavo... l'avatar con Gene Simmons è *impagabile* :-)

  • | Punteggio Post: 5
Top 25 Partecipanti
Maschio
Post 67
Punteggio 1.440

>Uhm... ogni bambino che so rispetti chiederebbe: "e perché"? :-)

Giusto per capire, così so "tarare" la risposta: hai letto il post che ho referenziato (o almeno la descrizione di identity map sul P of EAA) prima di fare questa domanda? :-)

.A

  • | Punteggio Post: 20
Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907
Ciao Andrea,

andysal ha scritto:
> Giusto per capire, così so "tarare" la risposta: hai letto il post che
> ho referenziato (o almeno la descrizione di identity map sul P of EAA)
> prima di fare questa domanda? :-)

Si. Peraltro ho già previsto la risposta. :-)
Il problema che mi pongo però è sempre il solito: se qualcuno usa ancora
l'entità X (intesa come oggetto fisico) ha senso "svuotare" l'IM?

Ciao e grazie,
Giulio
--
http://www.giuliopetrucci.it
http://www.myspace.com/fujikomonamourtheband
  • | Punteggio Post: 20
Top 25 Partecipanti
Maschio
Post 67
Punteggio 1.440

>se qualcuno usa ancora
>l'entità X (intesa come oggetto fisico) ha senso "svuotare" l'IM?

La risposta è "si": poichè non sei più all'interno di una transazione, è giusto che tu ottenga i dati "veri" in quel momento. O meglio... "Sarebbe", giacchè tipicamente (e soprattutto *comprensibilmente*) nessun DAL o O/RM lo fa :-)

.A

Top 10 Partecipanti
Maschio
Post 268
Punteggio 4.907
Ciao Andrea,

andysal ha scritto:
> >se qualcuno usa ancora
> >l'entità X (intesa come oggetto fisico) ha senso "svuotare" l'IM?
>
> La risposta è "si": poichè non sei più all'interno di una transazione,

In che senso?

> è giusto che tu ottenga i dati "veri" in quel momento. O meglio...
> "Sarebbe", giacchè tipicamente (e soprattutto *comprensibilmente*)
> nessun DAL o O/RM lo fa :-)

Ma a quel punto, se il client C fa una modifica dell'entità X e richiama
un Update, che succede? Ok, l'identity map fa un check dell'id e...? E
se il client cambia l'ID dell'entità e richiama una delete?

Insomma, tutto questo per dire che sto pensando ad una cosa del genere:
le entità che vanno in giro per l'universo sono in realtà dei proxy in
cui le modifiche vengono immediatamente mappate alla fine di ogni
transazione. Ovviamente con un ID che è read-only. Può andare? ;-)

Ciao,
Giulio
--
http://www.giuliopetrucci.it
http://www.myspace.com/fujikomonamourtheband
  • | Punteggio Post: 5
Top 10 Partecipanti
Maschio
Post 243
Punteggio 3.383

Ciao, vediamo di dare qualche spiegazione.

Per reidratare si intende l'operazione di ricostruire l'oggetto in memoria dai dati tornati dal database. Tipicamente l'ORM esegue la query, ottiene un datareader e per ogni riga ricostruisce gli oggetti prendendo i dati dal db. E' in questo momento che un orm controlla la chiave primaria nel datareader, controlla nella IM se è presente un oggetto con quella chiave, se si prende quello e non reidtrata nulla.

Il flush consiste in questo, l'orm controlla tutti gli oggetti in stato persistente, verifica se qualcuno ne ha cambiato qualche proprietà (controllando nel suo contesto di persistenza), se gli oggetti sono cambiati in memoria vengono propagati i cambiamenti al db con istruzioni UPDATE. NHibernate fa un flush prima di ogni queri per evitare che

Carico l'0oggetto A, cambio una sua proprietà, diciamo nome da "Paolo" a "Gian Paolo". Poi faccio una query per tutte le entità il cui nome isizia con "Pao". In questo caso il record nel db che corrisponde all'oggetto A ancora ha il valore "paolo" ma in memoria è diventato "Gian Paolo". Se non si fa il flush, l'orm esegue la query, l'oggetto viene tornato nel datareader (perchè nel db ancora vale paolo), poi viene controllata l'identitymap, e non viene reidtratato l'oggetto, in questo modo ti viene tornata una entità il cui nome è "Gian Paolo" .... cosa che non è concettualmente corretta. Un flush prima delle query risolve il tutto.

Per entità dirty si intendono tutte le entità persistenti che hanno proprietà cambiate, e che quindi non corrispondono più al contenuto del db.

cmq ti consiglio anche "java persistence with hibernate" o NHibernate in action. Parlano di hibernate e NHibernat eripsettivamente, ma contengono molti concetti applicabili a qualsiasi ORM. Naturalmente prima va letto il fowler che contiene tutti i pattern di base .

ciaooooo.

Alk.

  • | Punteggio Post: 20
Pagina 1 di 2 (21 elementi) 1 2 Avanti > | RSS
Powered by Community Server (Commercial Edition), by Telligent Systems