posts - 644, comments - 1982, trackbacks - 137

My Links

News

Raffaele Rialdi website

Su questo sito si trovano i miei articoli, esempi, snippet, tools, etc.

Archives

Post Categories

Image Galleries

Blogs

Links

EF 4.1 tracking di entity disconnesse (scenario 'POCO')

In uno scenario POCO che utilizza Entity Framework 4.1 (code-first) di default abbiamo in regalo il tracking dei cambiamenti apportati alle entities.

Caso 1.

Category cat; System.Data.EntityState state; using (BizContext ctx = new BizContext()) { cat = ctx.Categories.First(); cat.Name += "X"; state = ctx.Entry(cat).State; Console.WriteLine(state); }

Il risultato di questo codice è "Modified". Dietro le quinte infatti EF chiama ctx.ChangeTracker.DetectChanges(); che esegue il confronto delle proprietà delle entity dichiarate nel context BizContext che deriva da DbContext.

Nessun timore per le performance, l'automatismo si può disabilitare ed eseguire il controllo, se richiesto, on-demand:

using (BizContext ctx = new BizContext()) { ctx.Configuration.AutoDetectChangesEnabled = false; cat = ctx.Categories.First(); cat.Name += "X"; ctx.ChangeTracker.DetectChanges(); state = ctx.Entry(cat).State; Console.WriteLine(state); }

Naturalmente il controllo verifica che i valori siano effettivamente differenti, per cui se si scrive una cosa di questo tipo:

cat.Name = string.Copy(cat.Name);

l'entity non verrà rilevata come modificata.

CASO 2.

Fin qui tutto ok. Lo scenario però è troppo semplice perché abbiamo imparato che gli scenari reali sono n-tier o comunque disconnessi (come nel caso delle web application) di conseguenza serve fare qualcosa di più.

using (BizContext ctx = new BizContext()) { cat = ctx.Categories.First(); } cat.Name += "X"; using (BizContext ctx = new BizContext()) { ctx.Categories.Attach(cat); state = ctx.Entry(cat).State; Console.WriteLine(state); ctx.MarkAsModified(cat); // manuale! state = ctx.Entry(cat).State; Console.WriteLine(state); }

Il metodo MarkAsModified l'ho aggiunto alla mia classe BizContext ed è definito in questo modo:

public void MarkAsModified(object entity) { var ctx = ((System.Data.Entity.Infrastructure.IObjectContextAdapter)this).ObjectContext; ctx.ObjectStateManager.ChangeObjectState(entity, System.Data.EntityState.Modified); }

In questa seconda casistica:

  • dopo aver chiuso il context, modifichiamo l'entity che perciò è disconnessa
  • riapriamo il context ed eseguiamo l'attach, dopo il quale sfortunatamente l'entity viene segnata come 'unchanged'
  • dobbiamo quindi essere noi a marcarla come modificata (MarkAsModifid) e finalmente entity framework potrà lanciare le update sull'invocazione della SaveChanges (non presente nello snippet)

CASO 3.

Il problema di questa soluzione è che abbiamo perso del tutto il tracking che dovrà esser fatto perciò manualmente. Lo scenario è comunque valido per certe applicazioni e l'unica accortezza sarà quella di impostare AutoDetectChanges a false in modo da evitare inutili perdite di tempo da parte di EF.

Volendo recuperare il meccanismo di tracking, ecco il terzo caso:

using (BizContext ctx = new BizContext()) { cat = ctx.Categories.First(); } cat.Name += "X"; using (BizContext ctx = new BizContext()) { IObjectContextAdapter adapter = ctx as IObjectContextAdapter; var objctx = adapter.ObjectContext; var current = ctx.Categories.Where(c => c.Id == cat.Id).FirstOrDefault(); if (current == null) { ctx.Categories.Add(cat); // era stato cancellato da un altro utente } else { // ObjectStateEntry entry = objctx.ObjectStateManager.GetObjectStateEntry(current); // var s1 = entry.State; // entry.ApplyCurrentValues(cat); // var s2 = entry.State; objctx.ApplyCurrentValues<Category>("Categories", cat); state = ctx.Entry(current).State; Console.WriteLine(state); } }

Il nuovo flusso dopo la solita modifica è il seguente:

  • recupero l'ObjectContext a partire dal DbContext
  • carico in memoria la versione attualmente su DB (current)
  • se non la trovo significa che un altro utente l'ha cancellata. Nell'esempio la re-inserisco ma potrei ovviamente decidere di lanciare un'eccezione
  • se la trovo chiedo a EF di impostare i valori 'attuali' con quelli che ho modificato (cat) quando ero disconnesso
  • leggo lo stato della Entity e ottengo finalmente 'Modified'.

Le righe commentate sono un modo alternativo di eseguire la ApplyCurrentValues. Entrambe arrivano allo stesso risultato.

Tutto questo prescinde dall'uso di WCF Data Services (ancora in CTP) o di RIA che sono stati aggiornati per lavorare in tandem con EF 4.1 ed il tracking.

Una precisazione: perché uno scenario POCO? Perché sempre di più sarà utile lavorare serializzando in json e reidratare le entity sul lato server. Perciò POCO e disconnesso diventeranno sempre più strumenti ordinari. Se poi abbiamo i nuovi RIA con il supporto a json tanto meglio ma ovviamente dipende dal contesto lato server.

Print | posted on Thursday, September 1, 2011 3:38 PM |

Feedback

Gravatar

# re: EF 4.1 tracking di entity disconnesse (scenario 'POCO')

@Marco. Non ho trovato shortcut, devi ripetere l'operazione su tutto il grafo degli oggetti conosciuti dal Context.
Se tu hai altre entities sotto la Category queste sono 'detached' per il context e quindi le ignora.
Il fatto di poter scegliere quali entities prendano parte alle update e quali no è prezioso. Ho uno scenario che spero di semplificare abbastanza da renderlo bloggabile che dimostra quanto sia utile.
D'altra parte sarebbe comodo poter propagare l'operazione di ApplyCurrentValues a tutto il grafo ma per farlo avresti certamente bisogno di caricare le entity (come da esempio) e questo non è facilmente automatizzabile.
9/1/2011 7:47 PM | Raffaele Rialdi
Gravatar

# re: EF 4.1 tracking di entity disconnesse (scenario 'POCO')

Non sono un fan delle self-tracking. Ho fatto dei test e poi quando ho guardato il mostro che è venuto fuori ho scelto altre strade.
Il team di EF suggerisce di lavorare con i data services per scenari di tracking multi-tier. Appena avrò tempo farò un confronto tra il tracking con i data services e con i nuovi RIA (entrambi sono in CTP e spero che a Build facciano l'annuncio del rilascio).
9/2/2011 9:15 AM | Raffaele Rialdi
Gravatar

# re: EF 4.1 tracking di entity disconnesse (scenario 'POCO')

Marco aggiungo un punto importante.
Se prima o poi le tue entities finiranno su un client jQuery via json, avere self-tracking è un delirio perché ti costringe a ricostruirti il self-tracking anche in js.
Se hai bisogno di optimistic concurrency, può aver senso usare un 'economico' timestamp che è facile da gestire su qualsiasi tipo di client.
Resta il fatto che il grosso te lo devi smazzare sul lato server (meglio così che impazzire sul lato client dove, tra l'altro, può essere più problematico dal punto di vista della security).
9/2/2011 2:11 PM | Raffaele Rialdi
Gravatar

# re: EF 4.1 tracking di entity disconnesse (scenario 'POCO')

Sono d'accordo... Infatti il mio è uno scenario diverso, dato che non sono su multi tier, ma su una web app in cui comunque le entità sopravvivono a più postback e, ad un certo punto, ho bisogno di effettuare un re-attach al context. Purtroppo dopo quasi 3 anni, EF è ancora piuttosto carente su questo scenario e, ad oggi, non credo ci sia ancora una soluzione più Smart del "faccio tutto a mano".

In quest'ottica, ad esempio, le STEs mi sembrano abbastanza più semplici da utilizzare, sebbene, come ho già detto, le magagne non manchino.

Paradossalmente, le reputo molto più adeguate per questo scopo, ossia scenari web disconnessi, in cui al termine di una serie di opzioni si vuole effettuare il reattach, piuttosto che l'N-tier, per il quale erano originariamente pensate, ma in cui evidenziano i problemi che indichi tu!

Ciao!!
9/2/2011 9:48 PM | Marco De Sanctis

Post Comment

Title  
Name  
Email
Url
Comment   
Please add 6 and 5 and type the answer here:

Powered by:
Powered By Subtext Powered By ASP.NET