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.