Il presente articolo è stato tradotto automaticamente.
Accesso ai dati
Creazione dell'applicazione Da fare desktop con NHibernate
Oren Eini
Scaricare il codice di esempio
NHibernate è un mapping relazionale a oggetti (OR / M), tasked con rendendo come semplici da utilizzare un database consiste nel collaborare con gli oggetti in memoria. Si tratta di uno dei più diffusi OR / M Framework per lo sviluppo di Microsoft .NET Framework. Ma la maggior parte degli utenti di NHibernate stiano eseguendo tali operazioni nel contesto di applicazioni Web, in modo che non vi è relativamente poche informazioni sulla creazione di applicazioni NHibernate sul desktop.
Quando si utilizza NHibernate in un'applicazione Web, tendono a utilizzare lo stile di sessione per ogni richiesta, ha molte implicazioni sono facili da perdere. La sessione di gestione di un riferimento a entità caricato, non preoccuparsi perché mi aspetto che la sessione verrà inserito lo stoccaggio a breve. Non è necessario preoccuparsi (molte) di gestione degli errori come semplicemente è possibile interrompere la richiesta corrente e la sessione di associato se si verifica un errore.
La durata della sessione è ben definita. Non è necessario aggiornare altre sessioni sulle eventuali modifiche apportate. Non è necessario preoccuparsi che svolge una transazione lunga nel database o persino si tiene una connessione aprire per un lungo periodo di tempo, perché solo sono attivi per la durata di una singola richiesta.
Come è facile immaginare, queste sono preoccupazioni in un'applicazione desktop. Solo per chiarire su di esso, sta parlando di un'applicazione che comunica con un database direttamente. Un'applicazione che utilizza una sorta di servizi remoti utilizza NHibernate sul server remoto, nello scenario per ogni richiesta e non è lo stato attivo di questo articolo. In questo articolo non copre occasionalmente scenari, sebbene gran parte la discussione qui si applica a tali scenari.
Creazione di un'applicazione desktop basata su NHibernate non è molto diversa rispetto alla creazione di un'applicazione desktop utilizzando qualsiasi altra tecnologia di persistenza. Molte delle sfide che intende creare una struttura in questo articolo vengono condivisi tra tutte le tecnologie di accesso ai dati:
- Gestione di ambito dell'unità di lavoro.
- Riducendo la durata delle connessioni di database aperto.
- Propagazione di entità viene modificato in tutte le parti dell'applicazione.
- Supporto dell'associazione dati bidirezionale.
- Riduzione di tempi di avvio.
- Evitare di bloccare il thread dell'interfaccia utente durante l'accesso al database.
- La gestione e risoluzione dei conflitti di concorrenza.
Le soluzioni che dare utilizzano esclusivamente con NHibernate, almeno la maggior parte dei loro sono anche applicabili alle altre tecnologie di accesso ai dati. Uno di questi problemi, condiviso da tutte le tecnologie di accesso ai dati che si sono a conoscenza, è come gestire l'ambito di unità dell'applicazione di lavoro, ovvero o in termini di NHibernate, la durata delle sessioni.
Gestione delle sessioni
Una singola sessione globale per l'intera applicazione è una pessima abitudine comune con le applicazioni desktop NHibernate. Si tratta di un problema per molte ragioni, ma tre di essi sono più importanti. Perché una sessione conserva un riferimento a tutto ciò che caricato, l'utente che lavora nell'applicazione sta per caricare un'entità, utilizzare un po' e quindi dimenticare. Ma poiché la singola sessione globale è mantenere un riferimento a esso, l'entità non è mai rilasciata. In sostanza, si dispone di una perdita di memoria dell'applicazione.
Quindi si è verificato il problema di gestione degli errori. Se si riceve un'eccezione (ad esempio StaleObjectStateException causa del conflitto di concorrenza), la sessione e la relativa entità caricata sono toast, perché con NHibernate, un'eccezione generata da una sessione di tale sessione viene spostata in uno stato non definito. È non possibile utilizzare non è più tale sessione o qualsiasi entità caricato. Se si dispone solo una singola sessione globale, significa che è probabile che sia necessario riavviare l'applicazione, probabilmente non è una buona idea.
Infine, è il problema della transazione e la gestione connessione. Durante l'apertura di una sessione non è simile all'apertura della connessione a un database, utilizzando una singola sessione significa che si è molto più probabile che contengano le transazioni e connessione per più di è necessario.
Un altro altrettanto errato e, Sfortunatamente, quasi come comune esercitarsi con NHibernate è micromanaging la sessione. Un esempio tipico è il seguente codice:
public void Save(ToDoAction action) {
using(var session = sessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
session.SaveOrUpdate(action);
tx.Commit();
}
}
Il problema con questo tipo di codice è che rimuove molti dei vantaggi si ottengono utilizzando NHibernate. NHibernate sta effettuando parecchio lavoro per gestire la gestione delle modifiche e persistenza trasparente. Micromanaging la sessione tagliata capacità della NHibernate per eseguire questa operazione e lo sposta onus di ciò che funzionano per l'utente. E che è anche senza menzionare i problemi che causa con caricamento lazy la riga verso il basso. In qualsiasi sistema in cui ho visto un approccio di questo tipo tentata, gli sviluppatori dovevano lavorare più difficile per risolvere tali problemi.
Una sessione non deve essere tenuta aperta per troppo tempo, ma si dovrebbe anche non essere tenuto aperto per troppo breve tempo rendere utilizzare funzionalità di NHibernate. In genere, tenterà di associare la durata delle sessioni per l'effettiva azione eseguita dal sistema.
La procedura consigliata per le applicazioni desktop consiste nell'utilizzare una sessione per ogni modulo, in modo che ciascun form nell'applicazione ha una propria sessione. Ogni form rappresenta in genere un di lavoro distinto pezzo che l'utente desidera eseguire, in modo che la durata della sessione corrispondente alla durata modulo funziona piuttosto bene nella pratica. L'ulteriore vantaggio è che non hai più un problema con perdite di memoria, poiché quando si chiude un form nell'applicazione, cessione anche della sessione. Questo renderebbe tutte le entità sono state caricate dalla sessione idonea per liberare sono andate dal garbage collector (GC).
Esistono altri motivi per preferenza per una singola sessione per ogni modulo. È possibile sfruttare revisione della NHibernate, in modo essa verrà svuotato tutte le modifiche apportate al database quando si esegue il commit della transazione. Crea anche una barriera di isolamento tra form diversi, in modo è possibile applicare modifiche a una singola entità senza preoccuparsi di eventuali modifiche ad altre entità sono illustrati in altri moduli.
Mentre questo stile di gestione del ciclo di vita di sessione viene descritto come una sessione per ogni modulo, in pratica è in genere gestire la sessione per ogni relatore. Il codice in di Figura 1 è stato prelevato dalla classe base del relatore.
Figura 1 di Gestione delle sessioni relatore
protected ISession Session {
get {
if (session == null)
session = sessionFactory.OpenSession();
return session;
}
}
protected IStatelessSession StatelessSession {
get {
if (statelessSession == null)
statelessSession = sessionFactory.OpenStatelessSession();
return statelessSession;
}
}
public virtual void Dispose() {
if (session != null)
session.Dispose();
if (statelessSession != null)
statelessSession.Dispose();
}
Come si può vedere, lentamente aprire una sessione (o una sessione senza informazioni sullo stato) e mantenere aperto fino a quando non è possibile eliminare del relatore. Questo stile corrisponde a piuttosto bene per il ciclo di vita del modulo stesso e consente di associare una sessione separata a ogni relatore.
Gestione connessioni
Nei Presenter non necessario preoccuparsi di apertura o chiusura della sessione. La prima volta che si accede a una sessione viene aperto per l'utente e verrà dispose stesso correttamente anche. Ma per quanto riguarda la connessione al database associato alla sessione? È si tenendo una connessione al database aperto per, purché l'utente sta visualizzando il modulo?
La maggior parte dei database gradite dover tenere aperta una transazione per lunghi periodi di tempo. Esso determina in genere la causa di errori o blocchi critici (deadlock) verso il basso la riga. Connessioni aperte possono causare problemi simili, poiché un database può accettare solo connessioni così tante prima dell'esecuzione di esaurimento delle risorse per gestire la connessione.
Per ottimizzare le prestazioni del server di database, è consigliabile mantenere transazione ciclo di vita a un minimo e chiusura connessioni appena possibile fare affidamento sul pooling di connessione per garantire tempi di risposta veloci quando si apre una nuova connessione.
NHibernate, la situazione è sostanzialmente uguale, tranne per il fatto che NHibernate contiene numerose funzionalità che ci sono per esplicitamente semplificare le cose per l'utente. La sessione di NHibernate non dispone di un'associazione uno-a-uno con una connessione di database. Invece, NHibernate gestisce la connessione al database internamente, apertura e chiusura, secondo le necessità. Ciò significa che non è necessario mantenere una sorta di stato nell'applicazione per disconnettersi e riconnettersi al database, se necessario. Per impostazione predefinita, NHibernate consentirà di ridurre al minimo nella misura massima durata in cui viene aperta una connessione.
È necessario preoccuparsi di transazioni che effettua piccole possibili. In particolare, una delle operazioni che non si desidera è tenere aperta una transazione per la durata del form. In questo modo verrà imposto NHibernate tenere aperta la connessione per la durata della transazione. E poiché la durata di un form viene misurata in tempi di risposta umana, è più probabile che si sarebbe finisce di compromettere una transazione e la connessione per periodi più lunghi rispetto a quella realmente integro.
Verrà in genere operazioni è aperte transazioni separate per ogni operazione che rendono. Let’s osservare un modello di modulo viene visualizzato un elenco di cose da fare semplice applicazione di esempio di scelta (vedere di Figura 2). Il codice per la gestione di questo modulo è piuttosto semplice, come si può vedere in di Figura 3.
Nella figura 2 di un'applicazione di elenco da fare
Nella figura 3 Creazione di form da fare
public void OnLoaded() {
LoadPage(0);
}
public void OnMoveNext() {
LoadPage(CurrentPage + 1);
}
public void OnMovePrev() {
LoadPage(CurrentPage - 1);
}
private void LoadPage(int page) {
using (var tx = StatelessSession.BeginTransaction()) {
var actions = StatelessSession.CreateCriteria<ToDoAction>()
.SetFirstResult(page * PageSize)
.SetMaxResults(PageSize)
.List<ToDoAction>();
var total = StatelessSession.CreateCriteria<ToDoAction>()
.SetProjection(Projections.RowCount())
.UniqueResult<int>();
this.NumberOfPages.Value = total / PageSize +
(total % PageSize == 0 ? 0 : 1);
this.Model = new Model {
Actions = new ObservableCollection<ToDoAction>(actions),
NumberOfPages = NumberOfPages,
CurrentPage = CurrentPage + 1
};
this.CurrentPage.Value = page;
tx.Commit();
}
}
Dispone di tre operazioni: durante il caricamento del modulo per la prima volta, che mostra la prima pagina e il paging e indietro tra i record.
Su ogni operazione, è possibile iniziare ed eseguire il commit di una transazione separata. In questo modo è possibile non utilizza le risorse sul database e non deve preoccuparsi di transazioni lunghe. NHibernate verrà aperto automaticamente una connessione al database quando si inizia una nuova transazione e chiuderla una volta completata la transazione.
Sessioni senza informazioni sullo stato
È disponibile un altro piccolo subtlety che è consigliabile prendere nota: Non si utilizza un ISession. Piuttosto, Sto utilizzando IStatelessSession al suo posto per caricare i dati. Le sessioni senza informazioni sullo stato vengono in genere utilizzate nella modifica dei dati di massa, ma in questo caso sto che effettua l'utilizzo di una sessione senza informazioni sullo stato per risolvere problemi di consumo della memoria.
È una sessione senza informazioni sullo stato, Beh, senza informazioni sullo stato. A differenza una normale sessione, esso non gestisce un riferimento alle entità carica. Di conseguenza, è adatta perfettamente al caricamento di entità per scopi di sola visualizzazione. Per il tipo di attività, in genere caricare le entità dal database, li generare nel form e dimentica su di essi. Una sessione senza informazioni sullo stato è esattamente ciò che è necessario in questo caso.
Ma senza informazioni sullo stato sessioni dotate di un insieme di limitazioni. Vyriausiasis tra di essi in questo caso è che le sessioni senza informazioni sullo stato non supportano il caricamento lazy, non coinvolgono stessi nella normale modalità evento NHibernate e non rendere l'utilizzo di NHibernate di memorizzazione nella cache le funzionalità.
Per tali motivi, è possibile in genere utilizzarli per le query semplice in cui si desidera semplicemente mostrare all'utente le informazioni senza eseguire qualsiasi operazione complicata. Nei casi in cui si desidera mostrare un'entità per la modifica, potuto effettuare ancora utilizzare di una sessione senza informazioni sullo stato, ma tendono a evitare che in favore di una normale sessione.
Nel modulo principale dell'applicazione si impegna a rendere i dati di sola visualizzazione e provare a rendere utilizzare sessioni senza informazioni sullo stato da solo. Il form principale vive per, purché l'applicazione è aperta e una sessione con informazioni sullo stato sta per essere un problema, non solo perché mantiene un riferimento a entità che caricato, ma poiché è probabile che potrebbe causare problemi se la sessione genera un'eccezione.
NHibernate considera una sessione che ha generato un'eccezione per essere in uno stato indefinito (solo Dispose ha un comportamento definito in questo caso). Occorre sostituire la sessione e perché le entità caricato da una sessione con informazioni sullo stato di mantenere un riferimento a esso, sarà necessario deselezionare tutte le entità sono state caricate dalla sessione inattiva adesso. È molto più semplice utilizzare una sessione senza informazioni sullo stato in questa circostanza.
Entità caricata da sessioni senza informazioni sullo stato non esistono esigenze particolari per lo stato della sessione e ripristino da un errore in una sessione senza informazioni sullo stato è semplice come la chiusura della sessione senza informazioni sullo stato corrente e l'apertura di uno nuovo.
Modifica dei dati
Nella figura 4 viene illustrato il modello di schermata di modifica. Quali problematiche è necessario affrontare quando è necessario modificare le entità?
Nella figura 4 Modifica Entities
Bene, è effettivamente necessario due problematiche distinte qui. Innanzitutto, si desidera essere in grado di utilizzare NHibernate del rilevamento delle modifiche, in modo da poter visualizzare un'entità (o un grafico di un oggetto entità) e disporre solo di NHibernate archiviarlo al termine. In secondo luogo, una volta che si salva un'entità, si desidera assicurarsi che ogni modulo Visualizza inoltre questa entità viene aggiornata con i nuovi valori.
Il primo elemento di business è effettivamente abbastanza facile da gestire. È sufficiente è rendere l'utilizzo della sessione associato al modulo e che è. Nella figura 5 viene visualizzato il codice driving questa schermata.
Nella figura 5 un'entità nella sessione di modifica
public void Initialize(long id) {
ToDoAction action;
using (var tx = Session.BeginTransaction()) {
action = Session.Get<ToDoAction>(id);
tx.Commit();
}
if(action == null)
throw new InvalidOperationException(
"Action " + id + " does not exists");
this.Model = new Model {
Action = action
};
}
public void OnSave() {
using (var tx = Session.BeginTransaction()) {
// this isn't strictly necessary, NHibernate will
// automatically do it for us, but it make things
// more explicit
Session.Update(Model.Action);
tx.Commit();
}
EventPublisher.Publish(new ActionUpdated {
Id = Model.Action.Id
}, this);
View.Close();
}
Si ottiene l'entità dal database nel metodo Initialize(id) e aggiornarla nel metodo OnSave. Si noti farlo in due transazioni separate, anziché mantenere attivo una transazione per un lungo periodo di tempo. È inoltre questa chiamata EventPublisher strano. Che cos'è che tutte le informazioni?
EventPublisher è qui per affrontare un'altra sfida: Quando la sessione di stato ogni maschera, ogni modulo contiene diversi instances di entità che è necessario intervenire. Su di esso, che è simile a uno spreco. Perché dovrebbe si carica la stessa azione più volte?
In realtà, la presenza di questa separazione tra i form verrà semplificare notevolmente l'applicazione. Si consideri cosa accadrebbe se le istanze di entità condiviso fra il consiglio. In tale situazione, si troverà manualmente con un problema in qualsiasi scenario di modifica possibili. Si consideri cosa accadrebbe se fosse necessario visualizzare un'entità in due formati per consentono la modifica di tale entità. Che potrebbe essere ad esempio una griglia modificabile e un modulo di modifica dettagliate. Se si apporta una modifica a un'entità nella griglia, aprire il modulo di modifica dettagliate e quindi salvarlo, ciò che accade per la modifica apportata nella griglia modificabile?
Se si utilizza un'istanza singola entità in tutta l'applicazione, è probabile che salvare la maschera dettagli causerebbe inoltre di salvare le modifiche apportate utilizzando la griglia. Che probabilmente non è un'operazione da eseguire. Condivisione di un'istanza di entità inoltre rende molto più difficile eseguire operazioni come annullare un modulo di modifica e dispone di tutte le modifiche non salvate passare lo stoccaggio.
Tali problemi non esistono semplicemente quando si utilizza un'istanza di entità per ogni modulo che è una cosa positiva come questo è più o meno obbligatorio quando si utilizza una sessione per ogni modulo approccio.
Eventi di pubblicazione
Ma ho completamente non spiegato lo scopo del EventPublisher ancora. È piuttosto semplice. È possibile che si dispone di molti anziché una singola istanza dell'entità nell'applicazione, ma l'utente desidera comunque vedere dell'entità (correttamente una volta salvato) aggiornati in tutti i moduli che mostrano tale entità.
Nel mio esempio è possibile farlo in modo esplicito. Ogni volta che si salva un'entità, è possibile pubblicare un evento che informa che ho fatto così e su quali entità. Questa operazione non è un evento .NET standard. Un evento .NET richiede una classe per la sottoscrizione direttamente. Che non funziona effettivamente per questo tipo di notifica, poiché richiede ogni modulo per registrare gli eventi in tutti gli altri moduli. Appena si tenta di gestire che sarebbe un vero incubo.
Il EventPublisher è una pubblicazione - sottoscrizione meccanismo che consente di separare un editore da proprio server di sottoscrizione. L'unico analogia tra di esse è la classe EventPublisher. È possibile utilizzare il tipo di evento (ActionUpdated in di Figura 5) per decidere chi informare l'evento.
Let’s osservare l'altro lato di quell'ora. Quando l'aggiornamento di un'azione di cose da fare I would like to mostrare i valori aggiornati nella maschera principale è visualizzata una griglia delle azioni di cose da fare. Di seguito è riportato il codice rilevante dal relatore tale modulo:
public Presenter() {
EventPublisher.Register<ActionUpdated>(
RefreshCurrentPage);
}
private void RefreshCurrentPage(
ActionUpdated actionUpdated) {
LoadPage(CurrentPage);
}
All'avvio, è possibile registrare il metodo RefreshCurrentPage all'evento ActionUpdated. A questo punto, ogni volta che tale evento viene generato, verrà semplicemente aggiornare la pagina corrente da LoadPage chiamante, cui si ha già una sufficiente dimestichezza con.
Si tratta effettivamente un'implementazione piuttosto lenta. È possibile non prestare attenzione se la pagina corrente è visualizzato l'entità modificata; solo aggiornamento comunque. Un'implementazione più complessa (ed efficiente) sarebbe aggiornare solo i dati della griglia se l'entità aggiornata viene visualizzato in tale pagina.
Il principale vantaggio dell'utilizzo della pubblicazione-sottoscrizione meccanismo in questo modo è separazione il server di pubblicazione e sottoscrittori. È possibile non prestare attenzione nella maschera principale che il modulo di modifica pubblica evento ActionUpdated. L'idea di pubblicazione su eventi e pubblicazione sottoscrizione è un elemento fondamentale nella generazione non fortemente accoppiati con interfacce utente e verrà ampiamente trattato in Composite Application Guida per la (msdn.microsoft.com/library/cc707819 ) dal team Microsoft patterns & practices.
È disponibile un altro caso prendere in considerazione: Che cosa succede se si dispone di due moduli di modifica alla stessa entità aperti nello stesso momento? Come possibile ottenere i nuovi valori dal database e visualizzarle all'utente?
Il codice riportato di seguito è tratto dal relatore di maschera di modifica:
public Presenter() {
EventPublisher.Register<ActionUpdated>(RefreshAction);
}
private void RefreshAction(ActionUpdated actionUpdated) {
if(actionUpdated.Id != Model.Action.Id)
return;
Session.Refresh(Model.Action);
}
Questo codice viene registrato per l'evento ActionUpdated e se è l'entità che si sta modificando, chiedere di NHibernate aggiornare dal database.
Questo modello esplicito dell'aggiornamento l'entità dal database fornisce inoltre la possibilità per prendere decisioni sull'azione da intraprendere a questo punto. Dovrebbe si aggiorna automaticamente, cancellazione di tutte le modifiche utente? È opportuno chiedere all'utente? Tenta di unire automaticamente le modifiche? Queste sono tutte le decisioni che hanno ora la possibilità di gestire in modo semplice.
Nella maggior parte dei casi, tuttavia, ritengo che semplicemente aggiornando l'entità è abbastanza sufficiente, perché in genere non consentono l'aggiornamento di una singola entità in parallelo (almeno non da un singolo utente).
Mentre questo codice di aggiornamento entità aggiornerà effettivamente i valori dell'istanza di entità, come verranno affinché la risposta dell'interfaccia utente per questa modifica? Si dispone di dati associati i valori di entità ai campi modulo, ma è necessario qualche modo indicare l'interfaccia utente che tali valori sono stati modificati.
Microsoft .NET Framework fornisce l'interfaccia INotifyPropertyChanged, comprendere quali la maggior parte dei Framework dell'interfaccia utente e saper intervenire. Ecco la definizione INotifyPropertyChanged:
public delegate void PropertyChangedEventHandler(
object sender, PropertyChangedEventArgs e);
public class PropertyChangedEventArgs : EventArgs {
public PropertyChangedEventArgs(string propertyName);
public virtual string PropertyName { get; }
}
public interface INotifyPropertyChanged {
event PropertyChangedEventHandler PropertyChanged;
}
Un oggetto che implementa questa interfaccia deve generare l'evento PropertyChanged con il nome della proprietà che è stato modificato. L'interfaccia utente verrà sottoscrivere l'evento PropertyChanged e ogni volta che una modifica viene generata in una proprietà che è associata, aggiornerà l'associazione.
Questa implementazione è abbastanza facile:
public class Action : INotifyPropertyChanged {
private string title;
public virtual string Title {
get { return title; }
set {
title = value;
PropertyChanged(this,
new PropertyChangedEventArgs("Title"));
}
}
public event PropertyChangedEventHandler
PropertyChanged = delegate { };
}
Durante la semplice, è codice piuttosto ripetitivo ed è necessaria solo per soddisfare l'interfaccia utente dell'infrastruttura preoccupazioni.
Creazione di entità intercettazione
Non si desidera scrivere il codice solo per ottenere l'associazione di dati dell'interfaccia utente funziona correttamente. E, in realtà, non è effettivamente necessario.
Uno dei requisiti di NHibernate è apportare tutte le proprietà e metodi sulle classi personali virtuale. NHibernate richiede questa opzione per gestire correttamente il caricamento lazy preoccupazioni, ma si è in grado di sfruttare questo requisito per altri motivi.
È possibile effettuare una cosa è in grado di sfruttare la parola chiave virtual per introdurre il proprio comportamento nella combinazione. A tal fine è utilizzando una tecnica denominata Aspect-Oriented Programming (AOP). In sostanza, si accettano una classe e aggiungere ulteriori comportamenti a questa classe in fase di esecuzione. Il meccanismo di esatto di come implementare questo esula dall'ambito dell'articolo, ma è incapsulata nella classe DataBindingFactory, cui definizione è:
public static class DataBindingFactory {
public static T Create<T>();
public static object Create(Type type);
}
L'intera implementazione della classe è di circa 40 righe, non è poi così complesse. Cosa è richiedere un tipo e produrre un'istanza di questo tipo implementa inoltre completamente il contratto INotifyPropertyChanged. In altre parole, possono essere utilizzate nel test illustrato di seguito:
ToDoAction action = DataBindingFactory.Create<ToDoAction>();
string changedProp = null;
((INotifyPropertyChanged)action).PropertyChanged
+= (sender, args) => changedProp = args.PropertyName;
action.Title = "new val";
Assert.Equal("Title", changedProp);
Dato che, sarà sufficiente è ora utilizzano il DataBindingFactory ogni volta che si crea una nuova classe in Presenter. Il vantaggio principale ottenere da tale sistema è che a questo punto, se si desidera rendere viene utilizzato il modello a dominio in un contesto di presentazione non NHibernate di, è possibile semplicemente non impiegano il DataBindingFactory e ottenere un modello di dominio in modo completamente gratuito da problemi di presentazione.
È ancora presente un problema, anche se. Sebbene sia possibile creare nuove istanze di delle entità utilizzando DataBindingFactory, molto il tempo sarà necessario affrontare le istanze che sono state create da NHibernate. Ovviamente, è in grado di alcuna operazione di DataBindingFactory NHibernate e non può effettuare utilizzare di esso. Ma prima disperare, è possibile rendere utilizzare uno dei più utili punti di estensione con NHibernate, l'intercettore. Intercettore del NHibernate consente di assumere il controllo, nell'essenza, alcune delle funzionalità che esegue internamente NHibernate.
Una delle funzionalità che l'intercettore consente di assumere il controllo sta creando nuove istanze di entità. Nella figura 6 viene illustrato un intercettore che consente di creare istanze di entità utilizzando il DataBindingFactory.
Nella figura 6 intercettazione Entity creazione
public class DataBindingInterceptor : EmptyInterceptor {
public ISessionFactory SessionFactory { set; get; }
public override object Instantiate(string clazz,
EntityMode entityMode, object id) {
if(entityMode == EntityMode.Poco) {
Type type = Type.GetType(clazz);
if (type != null) {
var instance = DataBindingFactory.Create(type);
SessionFactory.GetClassMetadata(clazz)
.SetIdentifier(instance, id, entityMode);
return instance;
}
}
return base.Instantiate(clazz, entityMode, id);
}
public override string GetEntityName(object entity) {
var markerInterface = entity as
DataBindingFactory.IMarkerInterface;
if (markerInterface != null)
return markerInterface.TypeName;
return base.GetEntityName(entity);
}
}
Eseguire l'override del metodo Instantiate e gestire il caso in cui si ottiene un'entità con un tipo che in grado di riconoscere. Procedere quindi per creare un'istanza della classe e impostare la relativa proprietà identificatore. È inoltre necessario insegnare NHibernate come comprendere quali tipo a cui appartiene un'istanza creata tramite DataBindingFactory, eseguire nel metodo GetEntityName del intercepter.
L'unica cosa da sinistra a questo punto è l'impostazione di una NHibernate con nuovo intercettore. Di seguito è tratto dalla classe del programma di avvio automatico, responsabile dell'impostazione dell'applicazione:
public static void Initialize() {
Configuration = LoadConfigurationFromFile();
if(Configuration == null) {
Configuration = new Configuration()
.Configure("hibernate.cfg.xml");
SaveConfigurationToFile(Configuration);
}
var intercepter = new DataBindingIntercepter();
SessionFactory = Configuration
.SetInterceptor(intercepter)
.BuildSessionFactory();
intercepter.SessionFactory = SessionFactory;
}
Per ora, ignorare la semantica di configurazione, ovvero affronterà che in un bit. Il punto importante è creare l'intercettore, impostare la configurazione e generare la factory di sessione. L'ultimo passaggio è l'impostazione sull'intercettore la factory di sessione. È un po' difficile, verrà ammettere, ma che è il modo più semplice per ottenere la factory di sessione appropriata nell'intercettore.
Una volta l'intercettore cablata, ogni istanza di entità NHibernate crea ora supporterà INotifyPropertyChanged notifiche senza che sia necessario eseguire alcuna operazione affatto. È possibile considerare questo piuttosto una soluzione elegante al problema.
Non vi sono alcune che verrebbero pronunciare che scelta di una soluzione di questo tipo è un problema dal punto di vista delle prestazioni sul disco rigido l'implementazione di codifica. In pratica, che risulta per essere un presupposto false. Lo strumento che si sta utilizzando (castello dinamico proxy) per eseguire questa estensione in tempo reale delle classi è stata ampiamente ottimizzato per garantire prestazioni ottimali.
L'indirizzamento delle prestazioni
Una preoccupazione aggiuntiva nelle applicazioni desktop che non si dispone di applicazioni Web a proposito delle prestazioni, è tempo di avvio. Nelle applicazioni Web è piuttosto comune per decidere di preferire più lunghi tempi di avvio per migliorare le prestazioni di richiesta. Le applicazioni desktop, si desidera ridurre il tempo di avvio quanto più possibile. In realtà, un comune imbroglio con applicazione desktop è di mostrare semplicemente una schermata dell'applicazione all'utente fino al termine dell'applicazione in fase di avvio.
Sfortunatamente, tempo di avvio NHibernate è piuttosto lungo. Si tratta principalmente perché NHibernate sta eseguendo una grande quantità di inizializzazione e verifica all'avvio, in modo che possa eseguire più rapidamente durante il normale funzionamento. Esistono due metodi comuni per la gestione di questo problema.
Il primo consiste nell'avviare NHibernate in un thread in background. Mentre ciò significa che l'interfaccia utente verrà visualizzate molte più veloce, viene inoltre creata una complicazione for all'applicazione stessa, poiché non è possibile visualizzare l'utente nulla dal database fino al termine dell'avvio di factory sessione.
L'altra opzione è per serializzare la classe Configuration del NHibernate. Una grande quantità di costo correlata all'avvio di NHibernate è correlata al costo della convalida delle informazioni passate alla classe di configurazione. La classe di configurazione è una classe serializzabile e pertanto è possibile pagare una sola volta, tale prezzo dopo il quale è possibile il costo di collegamento mediante il caricamento di un'istanza già convalidata dall'archivio permanente.
Che è lo scopo di LoadConfigurationFromFile e SaveConfigurationToFile, serializzazione e deserializzazione configurazione della NHibernate. Utilizzo di questi che è necessario creare la configurazione la prima volta avviare l'applicazione. Ma c'è un piccolo problema che dovrebbe essere a conoscenza di: Si dovrebbe invalidare la configurazione memorizzata nella cache se è stato modificato l'assembly di entità o il file di configurazione NHibernate.
Il codice di esempio per questo articolo contiene un'implementazione completa che ‘ s a conoscenza di ciò e invalida il file nella cache se la configurazione o le entità è state modificate.
È presente un altro problema di prestazioni necessario gestire. La chiamata del database è una delle operazioni più costosa che rende l'applicazione. Di conseguenza, non è un'operazione da eseguire sul thread dell'interfaccia utente dell'applicazione.
Tali compiti sono spesso confinati in un thread in background ed è possibile effettuare la stessa operazione con NHibernate, ma tenere presente che la sessione di NHibernate non è thread-safe. Mentre è possibile rendere utilizzare una sessione in più thread (non dispone di alcuna affinità di thread), non è necessario utilizzare una sessione (o le entità) su più thread in parallelo. In altre parole, è perfettamente bene utilizzare la sessione in un thread in background, ma è necessario serializzare l'accesso alla sessione e non consentire accesso concorrente a esso. Utilizza la sessione da parte di più thread in parallelo verrà produrre un comportamento indefinito; in altre parole, deve essere evitato.
Fortunatamente, esistono alcune misure relativamente semplici che è possibile adottare per garantire che l'accesso alla sessione viene serializzato. La classe System.ComponentModel.BackgroundWorker in modo esplicito è stata progettata per gestire questi tipi di attività. Consente di eseguire un task su un thread in background e inviare una notifica quando viene completata, avendo cura del problema di sincronizzazione thread dell'interfaccia utente, che è così importante nelle applicazioni desktop.
Si è visto in precedenza come gestire un'entità esistente, ho fatto direttamente sul thread dell'interfaccia utente di modifica. A questo punto, salvare let’s una nuova entità su un thread in background. Il codice riportato di seguito è l'inizializzazione del relatore Crea nuovo:
private readonly BackgroundWorker saveBackgroundWorker;
public Presenter() {
saveBackgroundWorker = new BackgroundWorker();
saveBackgroundWorker.DoWork +=
(sender, args) => PerformActualSave();
saveBackgroundWorker.RunWorkerCompleted +=
(sender, args) => CompleteSave();
Model = new Model {
Action = DataBindingFactory.Create<ToDoAction>(),
AllowEditing = new Observable<bool>(true)
};
}
BackgroundWorker è utilizzato per eseguire l'effettivo processo, è stata suddivisa in due parti distinte di salvataggio. A prescindere dalla tale divisione, è molto simile al modo in cui che gestita nel caso di modifica. Un altro bit interessante che è necessario prestare attenzione alla proprietà AllowEditing, questa proprietà viene utilizzata per bloccare l'interfaccia utente nel modulo quando si sta eseguendo un salvataggio operazione. In questo modo, è possibile in modo sicuro utilizzare la sessione in un altro thread, sapendo che non saranno accesso simultaneo per la sessione o una delle relative entità da tale modulo.
A questo punto dovrebbe essere familiare all'utente lo stesso processo di salvataggio. Let’s osservare il metodo OnSave prima:
public void OnSave() {
Model.AllowEditing.Value = false;
saveBackgroundWorker.RunWorkerAsync();
}
Questo metodo è responsabile della disattivazione di apportare modifiche nel modulo, quindi kicking disattivare il processo in background. In background, è possibile eseguire l'effettiva operazione di salvataggio. Il codice non devono essere forniti come una sorpresa:
private void PerformActualSave() {
using(var tx = Session.BeginTransaction()) {
Model.Action.CreatedAt = DateTime.Now;
Session.Save(Model.Action);
tx.Commit();
}
}
Quando viene completata l'operazione di salvataggio al database effettivo, BackgroundWorker eseguirà la parte di CompleteSave del processo nel thread dell'interfaccia utente:
private void CompleteSave() {
Model.AllowEditing.Value = true;
EventPublisher.Publish(new ActionUpdated {
Id = Model.Action.Id
}, this);
View.Close();
}
Riattivare il modulo, pubblicare una notifica che un'azione è stata aggiornata (causando schermate rilevanti per l'aggiornamento anche) e infine chiudere il modulo. Supponga che l'attivazione dell'interfaccia utente non è strettamente necessario, ma tale posizione è incluso per ragioni di completamento.
Utilizzando questa tecnica, è possibile sfruttare l'elaborazione senza che violano il contratto di threading su istanze sessione in background. Come sempre, il threading è un ottimo metodo per creare un'applicazione più reattiva, ma la programmazione multithread non è raggiunto lightly, utilizzare questa tecnica con cura di un'attività.
Gestione di concorrenza
Concorrenza è un argomento complesso al meglio di volte e non è limitato al threading da solo. Si consideri il caso in cui si dispone di due utenti modificare la stessa entità nello stesso momento. Uno di essi verrà raggiunto il pulsante di invio in primo luogo, salvare le modifiche al database. La domanda è: cosa dovrebbe accadere quando il salvataggio di riscontri al secondo utente pulsante?
Si tratta di un conflitto di concorrenza e NHibernate ha piuttosto alcuni metodi di rilevamento di un conflitto tra. L'entità di ToDoAction ha un < versione / > campo indica che è necessario eseguire controlli di concorrenza ottimistica in modo esplicito a NHibernate. Per una trattazione completa delle opzioni di concorrenza NHibernate offre, vedere il blog di Stephanie Krieger registrare in ayende.com/Blog/archive/2009/04/15/nhibernate-mapping-concurrency.aspx .
In pratica, le soluzioni di concorrenza rientrano in due ampie categorie:
- Controllo pessimistico della concorrenza, che richiede di tenere i blocchi sul database e mantenere la transazione aperta per un periodo prolungato di tempo. Come già detto, non si tratta di una buona idea in un'applicazione desktop.
- Controllo della concorrenza ottimistica, che significa che è possibile chiudere la connessione al database durante l'utente “ tempo interazione utente. ” La maggior parte delle opzioni che NHibernate ha da offrire è sul lato ottimistico, consentendo di diverse strategie rilevare conflitti.
Poiché il controllo della concorrenza pessimistica comporta un costo di notevole delle prestazioni, in genere non è accettabile. Questo, a sua volta, significa che dovrebbe Ottimizza per il controllo della concorrenza ottimistica. Con la concorrenza ottimistica, si tenta di salvare i dati normalmente, ma sta preparato per gestire il caso in cui i dati è stati modificati da un altro utente.
NHibernate verranno manifesto questa situazione come un StaleObjectStateException durante il salvataggio o eseguire il commit di processi. Le applicazioni devono intercettare tale eccezione e comportarsi di conseguenza. In genere significa che è necessario mostrare una sorta di un messaggio all'utente che spiega che l'entità è stata modificata da un altro utente e che l'utente deve ripetere le proprie modifiche. In alcuni casi, è necessario eseguire operazioni più complesse, ad esempio in cui viene richiesto se si desidera unire le informazioni o consentendo all'utente di stabilire la versione da mantenere.
Poiché la prima opzione, ovvero visualizzando un messaggio e che sia necessario l'utente ripristina tutte le modifiche, ovvero è molto più comune, verrà illustrato come implementare che con NHibernate e quindi illustrare brevemente come implementare altre soluzioni.
Una interessante problema che trovano ad affrontare immediatamente è che un'eccezione generata dalla sessione significa che la sessione non è più utilizzabile. Qualsiasi conflitto di concorrenza visualizzato in NHibernate come un'eccezione. L'unica cosa che può effettuare a una sessione dopo che ha generato un'eccezione consiste nel chiamare il metodo Dispose su di esso; qualsiasi altra operazione verrà produrre un comportamento indefinito.
Sto per tornare alla schermata Modifica e implementare la gestione della concorrenza vi come esempio. Aggiungerò un pulsante Crea conflitti di concorrenza sullo schermo di modifica, eseguirà le operazioni seguenti:
public void OnCreateConcurrencyConflict() {
using(var session = SessionFactory.OpenSession())
using(var tx = session.BeginTransaction()) {
var anotherActionInstance =
session.Get<ToDoAction>(Model.Action.Id);
anotherActionInstance.Title =
anotherActionInstance.Title + " -";
tx.Commit();
}
MessageBox.Show("Concurrency conflict created");
}
In tal modo si crea una nuova sessione e viene modificata la proprietà titolo. Quando si tenta di salvare l'entità nel modulo, poiché la sessione del modulo non è a conoscenza di tali modifiche attiverà un conflitto di concorrenza. Nella figura 7 viene illustrato come sto gestisce che.
Semplicemente il wrapping del codice che salva nel database in un blocco catch try e gestita l'eccezione dello stato non aggiornati da cui si informa l'utente che rilevato un conflitto di concorrenza. È possibile quindi sostituire la sessione.
Figura 7. La gestione dei conflitti di concorrenza
public void OnSave() {
bool successfulSave;
try {
using (var tx = Session.BeginTransaction()) {
Session.Update(Model.Action);
tx.Commit();
}
successfulSave = true;
}
catch (StaleObjectStateException) {
successfulSave = false;
MessageBox.Show(
@"Another user already edited the action before you had a chance to do so. The application will now reload the new data from the database, please retry your changes and save again.");
ReplaceSessionAfterError();
}
EventPublisher.Publish(new ActionUpdated {
Id = Model.Action.Id
}, this);
if (successfulSave)
View.Close();
}
Si noti che ho sempre chiamata ActionUpdated, anche se si riceve un conflitto di concorrenza. Ecco perché: Anche se ho ottenuto un conflitto di concorrenza, resto dell'applicazione non conosce probabilmente su di esso e l'entità è stata modificata nel database, in modo che anche possibile dare il resto dell'applicazione la possibilità di mostrare all'utente i nuovi valori.
Infine, è possibile solo chiudere il modulo se sono stato esito positivo nel salvataggio al database. Finora, che non c'è niente di molto, ma non vi è la sostituzione di sessione e l'entità è necessario prendere in considerazione (vedere di Figura 8).
Nella figura 8 Updating sessioni ed entità
protected void ReplaceSessionAfterError() {
if(session!=null) {
session.Dispose();
session = sessionFactory.OpenSession();
ReplaceEntitiesLoadedByFaultedSession();
}
if(statelessSession!=null) {
statelessSession.Dispose();
statelessSession = sessionFactory.OpenStatelessSession();
}
}
protected override void
ReplaceEntitiesLoadedByFaultedSession() {
Initialize(Model.Action.Id);
}
Come si può vedere, è possibile sostituire la sessione o senza informazioni sullo stato sessione disposing li e aprendo quelli nuovi. In caso di una sessione, chiedere inoltre il relatore per sostituire tutte le entità sono state caricate dalla sessione errata. Le entità di NHibernate sono strettamente alla loro sessione e quando la sessione di diventare inutilizzabile, è in genere consigliabile sostituire anche le entità. Non è obbligatorio , ovvero le entità non si prevede di smettere improvvisamente di funzionare, ma operazioni quali il caricamento lazy non funzioneranno più. Piuttosto sarebbe necessario pagare il costo di sostituzione di entità rispetto a tenta di scoprire se è possibile o non è in grado di attraversare l'oggetto grafico in determinati casi.
L'implementazione sostitutivo dell'entità viene eseguita chiamando semplicemente il metodo Initialize in questo caso. Questo è lo stesso metodo Initialize che ho analizzato nel caso di form di modifica. Questo metodo ottiene l'entità dal database solo e imposta in proprietà del modello, ovvero nulla di interessante. In scenari più complessi, esso potrebbe sostituire diverse istanze di entità vengono utilizzate in un unico modulo.
Parimenti, lo stesso approccio contiene non solo per i conflitti di concorrenza, ma per qualsiasi errore eventualmente ottenuti da sessioni di NHibernate. Una volta visualizzato un messaggio di errore, è necessario sostituire la sessione. E quando si sostituisce la sessione, probabilmente erroneamente ricaricare qualsiasi entità che è stato caricato utilizzando la sessione precedente in quello nuovo solo per essere sul lato provvisoria.
Gestione conflitto
L'ultimo argomento che desidero Accennare al momento in questo articolo è la più complesse tecniche di gestione conflitto di concorrenza. Non vi è fondamentalmente solo un'opzione: consentire all'utente di prendere una decisione tra la versione del database e la versione che l'utente appena modificato.
Nella figura 9 viene illustrato il modello di schermata di unione. Come si può vedere, qui semplicemente vengano visualizzati all'utente entrambe le opzioni e cui viene chiesto di scegliere quale dei due accetterà. Qualsiasi risoluzione dei conflitti di concorrenza in qualche modo si basa su questa idea. È possibile che si desideri presentare in modo diverso, ma che è il funzionamento e possono estrapolare da qui.
Nella figura 9 interfaccia utente per la gestione dei conflitti Cambia
Nella schermata modifica modificare la risoluzione dei conflitti al seguente:
catch (StaleObjectStateException) {
var mergeResult =
Presenters.ShowDialog<MergeResult?>(
"Merge", Model.Action);
successfulSave = mergeResult != null;
ReplaceSessionAfterError();
}
È possibile visualizzare la finestra di dialogo di stampa unione e l'utente ha stabilito come impostare la stampa unione, si decide che è stato un successo salvataggio (che verrà chiuso il modulo di modifica). Nota è possibile passare azione correntemente modificato alla finestra di dialogo di tipo merge in modo che conosce lo stato corrente dell'entità.
Il relatore della finestra di dialogo Stampa unione è semplice:
public void Initialize(ToDoAction userVersion) {
using(var tx = Session.BeginTransaction()) {
Model = new Model {
UserVersion = userVersion,
DatabaseVersion =
Session.Get<ToDoAction>(userVersion.Id),
AllowEditing = new Observable<bool>(false)
};
tx.Commit();
}
}
All'avvio, è possibile ottenere la versione corrente dal database e mostrare questa e la versione che l'utente ha modificato. Se l'utente accetta la versione del database, non ho molto da fare, in modo semplicemente di chiudere il modulo:
public void OnAcceptDatabaseVersion() {
// nothing to do
Result = MergeResult.AcceptDatabaseVersion;
View.Close();
}
Se l'utente desidera forzare la propria versione, è solo leggermente più complicato:
public void OnForceUserVersion() {
using(var tx = Session.BeginTransaction()) {
//updating the object version to the current one
Model.UserVersion.Version =
Model.DatabaseVersion.Version;
Session.Merge(Model.UserVersion);
tx.Commit();
}
Result = MergeResult.ForceDatabaseVersion;
View.Close();
}
Utilizzare le funzionalità di stampa unione di NHibernate di prendere tutti i valori persistenti versione dell'utente e li copia l'istanza di entità all'interno della sessione corrente. In effetti, esso unisce le due istanze, forzando i valori di utente nella parte superiore della valore database.
Questo è effettivamente sicuro a scopo anche con l'altra sessione inattiva e cancellata, perché il contratto di metodo Merge garantisce che il non tentare di attraversare le associazioni caricate lazy.
Si noti che prima tenta di unione, è possibile impostare la proprietà version dell'utente per la proprietà del database versione. Ciò avviene perché in questo caso si desidera sovrascrivere in modo esplicito tale versione.
Questo codice non tenta di gestire i conflitti di concorrenza ricorsiva (che è, durante il recupero di un conflitto di concorrenza di risoluzione primo). Che rimane come un esercizio per l'utente.
Nonostante il numero di verifiche illustrate in questo articolo, la generazione di un'applicazione desktop NHibernate non più difficile rispetto alla generazione di un'applicazione NHibernate Web. E in entrambi gli scenari, credo che utilizzando NHibernate renderà più facile la vita, l'applicazione più affidabile e l'intero sistema più semplice da modificare e lavorare con.
Oren Eini (chi lavora con lo pseudonimo di ayende Rahien) è un membro attivo di diversi progetti open source (NHibernate e castello tra di essi) ed è il fondatore di molti altri (Rhino Mocks, NHibernate Query Analyzer e Commons Rhino tra di essi). Eini è inoltre responsabile del profiler di NHibernate ( nhprof.com), un debugger visual per NHibernate. È possibile seguire lavoro del Eini in ayende.com/Blog.
Grazie ai seguenti esperti tecnici per la revisione di questo articolo: Howard Dierking