Radical 1.0.1 (Vacuum): Memento –> “the complex basics”
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 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 :
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 Style” ;
- Propagare il Memento ove necessario;
Scrivere le proprietà è banale:
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 String FirstName
{
get { return this.GetPropertyValue( () => this.FirstName ); }
set { this.SetPropertyValue( () => this.FirstName, value ); }
}
Per le liste non dobbiamo fare nulla di più, facilissimo!public Person()
{
this.Addresses = new MementoEntityCollection<Address>();
}
public IList<Address> Addresses { get; private set; }
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:
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.protected override void OnMementoChanged( IChangeTrackingService newMemento, IChangeTrackingService oldMemmento )
{
base.OnMementoChanged( newMemento, oldMemmento );
var m = this.Addresses as IMemento;
if( m != null )
{
m.Memento = newMemento;
}
}
Una volta sbrigate le formalità vediamo di capire come si usa
Il setup
Il processo di setup è “trivialissimo”, iniziamo con il comporre il nostro grafo esattamente come faremmo con un qualsiasi grafo:
Sono normalissime classi e come tali le usiamo, a questo punto possiamo cominciare a giocare con le cose interessanti:var person = new Person()
{
FirstName = "Mauro",
LastName = "Servienti"
};
var address = new Address( person )
{
Street = "Foo street",
City= "Bar City",
};
person.Addresses.Add( address );
Tutto li? ebbene si tutto li , fatto quello succede che il ChangeTrackingService si è insinuato con un virus all’interno del grafo e osserva qualsivoglia cosa succede.var service = new ChangeTrackingService();
service.Attach( person );
La prima cosa che potete fare è questa:
per scoprire lo stato di una entità, EntityTrackingStates è un’enumerazione così definita:var addressState = service.GetEntityState( address );
var isTransient = ( addressState & EntityTrackingStates.IsTransient ) == EntityTrackingStates.IsTransient;
Che vi da tutte le informazioni necessarie per scoprire lo stato di una singola entità, ma se fosse tutto li che gusto ci sarebbe?[Flags]
public enum EntityTrackingStates
{
None =0,
IsTransient = 1,
AutoRemove = 2,
HasBackwardChanges = 4,
HasForwardChanges = 8,
}
Proviamo a fare qualche modifica al nostro grafo:
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:person.LastName = "another last name";
person.Addresses.Add( new Address( person ) );
person.Addresses[ 0 ].City = "my city";
e scoprire che i primi due valori sono true mentre, giustamente CanRedo è false, se quindi facciamo semplicemente un mero “Undo”:var isChanged = service.IsChanged;
var canUndo = service.CanUndo;
var canRedo = service.CanRedo;
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.service.Undo();
Non male
Autocelebrazione a parte… il bello inizia adesso:
Un ChangeSet è una IEnumerablevar changeSet = service.GetChangeSet();
var advisory = service.GetAdvisory();
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.var advisedAction = advisory.Where( a => a.Target == person ).SingleOrDefault();
var shouldCreate = advisedAction.Action == ProposedActions.Create;
.m