Proprietà di navigazione a caricamento asincrono di entità auto-trattanti distaccate attraverso un servizio WCF?

StackOverflow https://stackoverflow.com/questions/5875271

Domanda

Ho un client WCF che trasmette entità auto-tracking a un'applicazione WPF costruita con MVVM. L'applicazione stessa ha un'interfaccia dinamica. Gli utenti possono selezionare quali oggetti desiderano visibili nella loro area di lavoro a seconda del ruolo o in quale compito stanno svolgendo.

Le mie entità auto-tracking hanno alcune proprietà di navigazione e molte di esse non sono necessarie. Poiché alcuni di questi oggetti possono essere piuttosto grandi, vorrei caricare solo queste proprietà su richiesta.

La mia applicazione sembra così:

[WCF] <---> [ClientSide Repository] <---> [ViewModel] <---> [View]

I miei modelli sono entità auto-track. Il repository lato client collega un metodo Lazyload (se necessario) prima di restituire il modello al ViewModel che lo richiedeva. Tutte le chiamate di servizio WCF sono asincronose, il che significa che i metodi di Lazyload sono anche asincrono.

L'effettiva implementazione del Lazyload mi sta dando qualche problema. Ecco le opzioni che ho escogitato.

Modifica: ho rimosso i campioni di codice per provare a rendere questo più facile da leggere e capire. Vedi la versione precedente della domanda se vuoi vederlo

Opzione A.

In modo asincrono lazyload le proprietà del modello dal server WCF nel getter

Bene: Il caricamento dei dati su richiesta è estremamente semplice. L'associazione in XAML carica i dati, quindi se il controllo è sullo schermo, i dati si carica in modo asincroni e notifica l'interfaccia utente quando è lì. In caso contrario, nulla si carica. Per esempio, <ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" /> caricherà i dati, tuttavia se la sezione dei documenti dell'interfaccia non è lì, non viene caricato nulla.

Male: Non è possibile utilizzare questa proprietà in nessun altro codice prima che sia stato avviato perché restituirà un elenco vuoto. Ad esempio, la seguente chiamata restituirà sempre false se i documenti non sono stati caricati.

public bool HasDocuments 
{ 
    get { return ConsumerDocuments.Count > 0; }
}

Opzione b

Effettuare manualmente una chiamata per caricare i dati quando necessario

Bene: Semplice da implementare - basta aggiungere LoadConsumerDocumentsSync() e LoadConsumerDocumentsAsync() metodi

Male: Deve ricordare di caricare i dati prima di provare ad accedervi, anche quando è utilizzato nei binding. Potrebbe sembrare semplice, ma può sfuggire rapidamente. Ad esempio, ogni ConsumerDocument ha un utente creato da utente e UserLastModificato. Esiste un datatemplate che definisce l'utente model con un titoli che mostra ulteriori dati utente come estensione, e -mail, team, ruoli, ecc. Quindi nel mio ViewModel che visualizza i documenti che dovrei chiamare LoadDocuments, quindi tirali attraverso di loro e chiama LoadConsumerModified e LoadConsumerCreated. Potrebbe continuare anche LoadUserGroups e LoadUserSupervisor. Corre anche il rischio di circuiti circolari in cui qualcosa di simile a User ha un Groups[] proprietà e a Group ha un Users[] proprietà

Opzione c

La mia opzione preferita finora ... crea due modi per accedere alla proprietà. Una sincronizzazione e un asincrone. I legami verrebbero eseguiti alla proprietà Async e qualsiasi codice utilizzerebbe la proprietà di sincronizzazione.

Bene: I dati vengono caricati in modo asincrono se necessario, esattamente quello che voglio. Non c'è molto codifica extra poiché tutto ciò che dovrei fare è modificare il modello T4 per generare queste proprietà/metodi extra.

Male: Avere due modi per accedere agli stessi dati sembra inefficiente e confuso. Dovresti ricordare quando dovresti usare Consumer.ConsumerDocumentsAsync invece di Consumer.ConsumerDocumentsSync. C'è anche la possibilità che la chiamata di servizio WCF venga eseguita più volte, e ciò richiede una proprietà extra è stata caricata per ogni proprietà di navigazione, come iSconsumerDocuments caricata.

Opzione d

Salta il caricamento asincrono e carica tutto in modo sincrono nei setter.

Bene: Molto semplice, nessun lavoro extra necessario

Male: Bloccherebbe l'interfaccia utente quando si carica i dati. Non voglio questo.

Opzione e

Avere qualcuno, quindi dimmi che c'è un altro modo per farlo e indicarmi per codificare i campioni :)

Altre note

Alcune delle proprietà di navigazione verranno caricate sul server WCF prima di restituire l'oggetto al client, tuttavia altre sono troppo costose per farlo.

Con l'eccezione di chiamare manualmente gli eventi di caricamento nell'opzione C, tutti possono essere eseguiti attraverso il modello T4, quindi c'è pochissima codifica da fare. Tutto quello che devo fare è collegare l'evento Lazyload nel repository lato client e indicarlo alle giuste chiamate di servizio.

È stato utile?

Soluzione 3

La soluzione che mi è venuta in mente è stata quella di modificare il modello T4 per le entità auto-tracking per apportare le modifiche mostrate di seguito. L'implementazione effettiva è stata omessa per rendere questo più facile da leggere, ma i nomi della proprietà/metodo dovrebbero chiarire cosa fa tutto.

Old T4 Generated Navigation Properties

[DataMember]
public MyClass MyProperty { get; set;}

private MyClass _myProperty;

Nuove proprietà di navigazione generate da T4

[DataMember]
internal MyClass MyProperty {get; set;}
public MyClass MyPropertySync {get; set;}
public MyClass MyPropertyAsync {get; set;}

private MyClass _myProperty;
private bool _isMyPropertyLoaded;

private async void LoadMyPropertyAsync();
private async Task<MyClass> GetMyPropertyAsync();
private MyClass GetMyPropertySync();

Ho creato tre copie della proprietà, che indicano la stessa proprietà privata. La copia interna è per EF. Probabilmente potrei sbarazzarmi di esso, ma è più facile lasciarlo entrare poiché EF si aspetta una proprietà con quel nome ed è più facile lasciarla che sistemare EF per utilizzare un nuovo nome di proprietà. È interno dal momento che non voglio nulla al di fuori dello spazio dei nomi di classe per usarlo.

Le altre due copie della proprietà eseguono esattamente lo stesso modo una volta che il valore è stato caricato, tuttavia caricano la proprietà in modo diverso.

La versione asincrone funziona LoadMyPropertyAsync(), che funziona semplicemente GetMyPropertyAsync(). Avevo bisogno di due metodi per questo perché non posso mettere il async Modificatore su un getter e devo restituire un vuoto se si chiama da un metodo non asincrone.

La versione di sincronizzazione funziona GetMyPropertySync() che a sua volta corre GetMyPropertyAsync() sincrono

Dal momento che questo è tutto generato da T4, non ho bisogno di fare nulla tranne collegare il delegato del carico pigro asincrone quando l'entità è ottenuta dal servizio WCF.

I miei legami indicano la versione asincrimale della proprietà e qualsiasi altro codice indica la versione di sincronizzazione della proprietà ed entrambi funzionano correttamente senza alcuna codifica aggiuntiva.

<ItemsControl ItemsSource="{Binding CurrentConsumer.DocumentsAsync}" />

CurrentConsumer.DocumentsSync.Clear();

Altri suggerimenti

Ci ha pensato, prima di tutto devo dire che devi fornire una soluzione chiara al lettore a questo problema, le dipendenze di dipendenza vengono caricate in asincronizza soluzione. Se diciamo che tale comportamento in vista è OK, dobbiamo mantenere il nostro codice di riposo molto chiaro sulle sue intenzioni, quindi possiamo vedere come stiamo cercando di accedere ai dati - asincroni o sincronizzati tramite qualche denominazione verbosa di qualcosa (metodo, classe, nome altro).

Quindi penso che potremmo usare una soluzione vicina al vecchio approccio .Assyncronized (), creare una classe Decoratore e fornire a ciascuna proprietà un metodo AsynCload & Syncload privato/protetto e una classe decoratore sarebbe sincronizzata o versione asincrima classe, qualunque cosa sia più appropriata.

Quando decori la tua classe con decoratore di sincronizzazione avvolge ogni classe lazyloadibile all'interno con un decoratore di sincronizzazione in modo da poter utilizzare Synchuser (utente). Documents.count on Sync Class Versi ) .SynCdocuments (documenti). Contratto dietro nella versione sovraccarica della proprietà dei documenti e chiamerebbe la funzione di sincronizzazione GETTER.

Poiché entrambe le versioni di sincronizzazione e async operano sullo stesso oggetto, questo approccio non porterà a modificare un altro oggetto non referenziato se si desidera modificare qualsiasi proprietà.

Il tuo compito può sembrare come uno che può essere risolto in un modo magico "bello e semplice", ma non penso che possa, o che non sarà più semplice di questo.

Se questo non funziona, sono ancora al 100% sicuro di aver bisogno di un modo chiaro per differire nel codice se viene utilizzata una versione di classe o asincroni di classe o avrai una base di codice molto difficile da mantenere.

Opzione A. dovrebbe essere la soluzione.

Crea una proprietà denominata Caricamento Indicare i dati vengono caricati o caricando non ancora caricati. Caricare i dati in modo asincrono e impostare di conseguenza la proprietà LoadingStatus.

Controllare lo stato di caricamento in ciascuna proprietà e se i dati non caricati, quindi la funzione chiamata per caricare i dati e viceversa.

Potrebbe il Binding.IsAsync Proprietà della biblioteca essere utile qui?

EDIT: espandere un po '.. Avere una proprietà sincrona carica che chiamerà il servizio WCF al primo utilizzo. Quindi l'associazione asincrona impedirà all'interfaccia utente di bloccare.

Mentre questa domanda è stata posta qualche tempo fa, è vicino alla cima dell'elenco di parole chiave asincroni e penso che avrebbe risposto in modo molto diverso in .NET 4.5.

Credo che questo sarebbe un caso d'uso perfetto per il AsyncLazy<T> Tipo descritto su diversi siti:

http://blogs.msdn.com/b/pfxteam/archive/2011/01/15/10116210.aspx http://blog.stephencleary.com/2012/08/asynchronous-lazyinitialization.html http://blog.stephencleary.com/2013/01/async-3-properties.html

Ho due pensieri nella mia testa.

1) Implementare un IQueryable<> risposta sul WCF Servizio. E seguire fino al DB con un IQueryable<> modello.

2) Nel repository client impostare il getter su ConsumerDocuments Proprietà per recuperare i dati.

private IEnumerable<ConsumerDocuments> _consumerDocuments;

public IEnumerable<ConsumerDocuments> ConsumerDocuments
{
    get
    {
        return _consumerDocuments ?? (_consumerDocuments = GetConsumerDocuments() );
    }
}

Nel modo in cui lo vedo, ViewModel deve essere consapevole se ci sono dati disponibili o meno. È possibile nascondere o disabilitare elementi dell'interfaccia utente che non hanno senso senza dati mentre i dati vengono recuperati, quindi mostrarli quando arrivano i dati.

Si rileva che devi caricare alcuni dati, quindi imposti l'interfaccia utente in modalità "in attesa", dà il via al recupero dell'asincronizzazione, quindi quando i dati entrano in modalità di attesa. Forse facendo iscriversi a ViewModel a un evento "LoadCompleted" sull'oggetto che è interessato.

(Modifica) È possibile evitare carichi eccessivi o dipendenze circolari tenendo traccia dello stato di ciascun modello: scarico/caricamento/caricato.

Ecco un'opzione E per te.

Caricano asincroni dati. Avere le cose iniziali di Fetch in coda in un thread di sfondo AA che riempie lentamente gli oggetti completi. E fare in modo che i metodi che richiedono i dati siano caricati dietro le quinte bloccano sulla finitura del carico. (Blocco e Chiedi loro di avvisare il thread di sfondo che i dati di cui hanno bisogno è di alta priorità, ottenerli in modo da poter sbloccare al più presto.)

Questo ti dà un'interfaccia utente che è immediatamente reattiva quando può essere, la capacità di scrivere il tuo codice e non pensare a ciò che è stato caricato, e funzionerà principalmente. L'unico gotcha è che occasionalmente effettuerai una chiamata di blocco mentre i dati si stanno caricando, tuttavia speriamo che non lo faccia troppo spesso. Se lo fai, nel peggiore dei casi degradi a qualcosa come l'opzione C in cui hai sia un recupero di dati di blocco, sia la possibilità di sondaggio per vedere se è lì. Tuttavia, il più delle volte non dovresti preoccuparti troppo.

Disclaimer: personalmente non uso Windows e trascorro la maggior parte del mio tempo a lavorare su Back Ends da UI. Se ti piace l'idea, sentiti libero di provarlo. Ma in realtà non ho seguito questa strategia per nulla di più complicato di alcune chiamate Ajax dietro le quinte in una pagina web dinamica.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top