Radical_thumb17zitti… che forse torno a bloggare con un minimo di regolarità… forse Smile with tongue out.
Perché siamo nuovamente qui? perché un’interessante domanda via mail mi ha fatto capire che gli esempi presenti in Radical non sono sempre semplici ed accessibili, soprattutto quando si parla di Memento e ChangeTracking; non lo sono perché oltre alla complessità dell’argomento introducono anche la complessità derivante dal fatto che sono pensati per Wpf/Silverlight e sono basati su Model View ViewModel.
E’ però difficile fare un esempio funzionante e fruibile su Memento senza introdurre altre complessità, ergo non lo faccio Smile ma lo spiego qui.
Lo scenario
Al solito partiamo dal requisito: vogliamo tracciare le modifiche ad un grafo complesso (dove per complesso ci basta una relazione 1:n) al fine di sapere cosa è stato modificato ed eventualmente quali sono le modifiche.
Il modello
Sempre il solito triviale e inutile Person/Address Smile:
image
L’unica cosa degna di nota è che entrambe le classi derivano da MementoEntity, non è però l’unico requisito io non sono un amante degli automatismi spintissimi preferisco scrivere un minimo di codice e avere il totale controllo su quello che succede.
Le cose da fare perché il tutto funzioni sono fondamentalmente due:
  • Scrivere le proprietà in “Radical StyleSmile;
  • Propagare il Memento ove necessario;
Le proprietà
Scrivere le proprietà è banale:
public String FirstName
{
get { return this.GetPropertyValue( () => this.FirstName ); }
set { this.SetPropertyValue( () => this.FirstName, value ); }
}
Utilizziamo una sintassi molto simile alle Dependency Property di Wpf al fine di consentire alla MementoEntity di intrufolarsi e fare il suo lavoro, alla stessa stregua esponiamo la lista di indirizzi utilizzando una collection ad hoc, o derivando da essa:
public Person()

{
this.Addresses = new MementoEntityCollection<Address>();
}

public IList<Address> Addresses { get; private set; }
Per le liste non dobbiamo fare nulla di più, facilissimo! Smile with tongue out
La propagazione
La seconda cosa che dobbiamo fare è propagare il servizio di ChangeTracking all’intero grafo: abbiamo un aggregato che il servizio non conosce è quindi nostra responsabilità, dalla root del nostro grafo, propagare le informazioni necessarie:
protected override void OnMementoChanged( IChangeTrackingService newMemento, IChangeTrackingService oldMemmento )
{
base.OnMementoChanged( newMemento, oldMemmento );

var m = this.Addresses as IMemento;
if( m != null )
{
m.Memento = newMemento;
}
}
La MementoEntity offre un metodo ad hoc per fare questa operazione, nell’esempio ci limitiamo a propagare da Person verso la lista di Address, la collection è in grado di propagare autonomamente ad ogni singolo Address il nuovo memento.
Una volta sbrigate le formalità vediamo di capire come si usa Smile
Il setup
Il processo di setup è “trivialissimo”, iniziamo con il comporre il nostro grafo esattamente come faremmo con un qualsiasi grafo:
var person = new Person() 
{
FirstName = "Mauro",
LastName = "Servienti"
};

var address = new Address( person )
{
Street = "Foo street",
City= "Bar City",
};

person.Addresses.Add( address );
Sono normalissime classi e come tali le usiamo, a questo punto possiamo cominciare a giocare con le cose interessanti:
var service = new ChangeTrackingService();
service.Attach( person );
Tutto li? ebbene si tutto li Smile, fatto quello succede che il ChangeTrackingService si è insinuato con un virus all’interno del grafo e osserva qualsivoglia cosa succede.
La prima cosa che potete fare è questa:
var addressState = service.GetEntityState( address );
var isTransient = ( addressState & EntityTrackingStates.IsTransient ) == EntityTrackingStates.IsTransient;
per scoprire lo stato di una entità, EntityTrackingStates è un’enumerazione così definita:
[Flags]
public enum EntityTrackingStates
{
None =0,
IsTransient = 1,
AutoRemove = 2,
HasBackwardChanges = 4,
HasForwardChanges = 8,
}
Che vi da tutte le informazioni necessarie per scoprire lo stato di una singola entità, ma se fosse tutto li che gusto ci sarebbe?
Proviamo a fare qualche modifica al nostro grafo:

person.LastName = "another last name";
person.Addresses.Add( new Address( person ) );
person.Addresses[ 0 ].City = "my city";
Facciamo un po’ di modifiche “sparse” sul grafo, esattamente come succederebbe in uno scenario reale di editing. Quello che succede dietro le quinte è che il servizio di ChangeTracking ha creato uno stack con la lista delle modifiche che abbiamo apportato e ha un “cursore” che gli dice dove siamo posizionati attualmente con lo stato del nostro grafo, ergo possiamo fare questo:
var isChanged = service.IsChanged;
var canUndo = service.CanUndo;
var canRedo = service.CanRedo;
e scoprire che i primi due valori sono true mentre, giustamente CanRedo è false, se quindi facciamo semplicemente un mero “Undo”:
service.Undo();
scopriamo che il valore della proprietà City dell’indirizzo che abbiamo modificato è tornato al valore precedente, qualunque esso fosse e che a questo punto CanRedo è diventato anch’esso true. Ovviamente se facciamo tre volte Undo, o più semplicemente un RejectChanges, abbiamo il grafo automaticamente riportato allo stato iniziale. La differenza in questo caso tra un set di Undo e un singolo RejectChanges è nel supporto al Redo, utilizzando gli Undo possiamo anche scorrere lo stack delle modifiche in avanti e quindi riportare il grafo in stadi diversi del suo ciclo di vita.
Non male Smile
Autocelebrazione a parte… il bello inizia adesso:
var changeSet = service.GetChangeSet();
var advisory = service.GetAdvisory();
Un ChangeSet è una IEnumerable che sono tutte le modifiche presenti nello stack in quel momento, ergo è la differenza tra lo stato originale e lo stato attuale, mentre un IAdvisory è una “proposta”:
var advisedAction = advisory.Where( a => a.Target == person ).SingleOrDefault();
var shouldCreate = advisedAction.Action == ProposedActions.Create;
possiamo quindi chiedere al ChangeTrackingService cosa farebbe con il grafo che sta tracciando nel momento in cui dovesse rivolgersi ad un ipotetico motore di persistenza, nell’esempio ci sta dicendo che secondo lui la Person che stiamo tracciando andrebbe creata.
.m