servicio consumir c# wcf lazy-loading self-tracking-entities async-await

c# - consumir - Navegación asíncrona de carga lenta Propiedades de autoasignación de entidades separadas a través de un servicio WCF?



wcf web service (8)

Tengo un cliente WCF que pasa Entidades de auto-seguimiento a una aplicación WPF creada con MVVM. La aplicación en sí tiene una interfaz dinámica. Los usuarios pueden seleccionar qué objetos quieren que sean visibles en su área de trabajo, dependiendo de en qué rol estén o qué tarea estén haciendo.

Mis entidades de seguimiento automático tienen bastantes propiedades de navegación, y muchas de ellas no son necesarias. Como algunos de estos objetos pueden ser bastante grandes, me gustaría cargar solo estas propiedades a pedido.

Mi aplicación se ve así:

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

Mis modelos son entidades de auto-seguimiento. El Repositorio del cliente conecta un método LazyLoad (si es necesario) antes de devolver el Modelo al ViewModel que lo solicitó. Todas las llamadas al Servicio WCF son asincrónicas, lo que significa que los métodos LazyLoad también son poco claros.

La implementación real de LazyLoad me está dando algunos problemas. Estas son las opciones que he propuesto.

EDITAR - Eliminé las muestras del código para intentar y hacer esto más fácil de leer y entender. Ver la versión anterior de la pregunta si quieres verla

Opción A

Asincrónicamente LazyLoad las propiedades del Modelo desde el servidor WCF en el Getter

Bueno: cargar datos a pedido es extremadamente simple. El enlace en el XAML carga los datos, por lo que si el control está en la pantalla, los datos se cargan de manera asincrónica y notifican a la UI cuando está allí. Si no, nada carga. Por ejemplo, <ItemsControl ItemsSource="{Binding CurrentConsumer.ConsumerDocuments}" /> cargará los datos; sin embargo, si la sección Documentos de la interfaz no está allí, entonces no se carga nada.

Malo: no se puede usar esta propiedad en ningún otro código antes de que se haya iniciado porque devolverá una lista vacía. Por ejemplo, la siguiente llamada siempre devolverá falso si los documentos no se han cargado.

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

OPCIÓN B

Hacer una llamada manualmente para cargar datos cuando sea necesario

Bueno: Fácil de implementar: solo agregue los LoadConsumerDocumentsSync() y LoadConsumerDocumentsAsync()

Malo: debe recordar cargar los datos antes de intentar acceder a ellos, incluso cuando se usan en Vinculaciones. Esto puede parecer simple, pero puede irse de las manos rápidamente. Por ejemplo, cada ConsumerDocument tiene un UserCreated y UserLastModified. Hay un DataTemplate que define el UserModel con una información sobre herramientas que muestra datos de usuario adicionales como extensión, correo electrónico, equipos, roles, etc. Por lo tanto, en mi ViewModel que muestra documentos, debería llamar a LoadDocuments , recorrerlos y llamar a LoadConsumerModified y LoadConsumerCreated . Podría seguir también ... después de eso tendría que LoadUserGroups y LoadUserSupervisor . También corre el riesgo de bucles circulares donde algo así como un User tiene una propiedad de Groups[] y un Group tiene una propiedad de Users[]

OPCIÓN C

Mi opción favorita hasta ahora ... crear dos formas de acceder a la propiedad. One Sync y One Async. Se realizarán enlaces a la propiedad Async y cualquier código usaría la propiedad Sync.

Bueno: los datos se cargan de manera asíncrona según sea necesario: exactamente lo que quiero. Tampoco hay mucha codificación adicional, ya que todo lo que tendría que hacer es modificar la plantilla T4 para generar estas propiedades / métodos adicionales.

Malo: tener dos formas de acceder a los mismos datos parece ineficaz y confuso. Tendrá que recordar cuándo debería usar Consumer.ConsumerDocumentsAsync lugar de Consumer.ConsumerDocumentsSync . También existe la posibilidad de que la llamada al Servicio WCF se ejecute varias veces, y esto requiere una propiedad IsLoaded adicional para cada propiedad de navegación, como IsConsumerDocumentsLoaded.

OPCIÓN D

Omita la carga asincrónica y simplemente cargue todo sincrónicamente en los incubadores.

Bueno: muy simple, no se necesita trabajo adicional

Malo: bloquearía la interfaz de usuario cuando se carguen datos. No quiero esto

OPCIÓN E

Pídale a alguien de SO que me diga que hay otra forma de hacerlo y apúnteme a ejemplos de código :)

Otras notas

Algunas de las propiedades de navegación se cargarán en el servidor WCF antes de devolver el objeto al cliente, sin embargo, otras son demasiado caras para hacerlo.

Con la excepción de invocar manualmente los eventos de carga en la opción C, todo se puede hacer a través de la plantilla T4, por lo que hay muy poca codificación para mí. Todo lo que tengo que hacer es conectar el evento LazyLoad en el repositorio del lado del cliente y dirigirlo hacia las llamadas de servicio correctas.


¿Podría la propiedad de la biblioteca Binding.IsAsync ser útil aquí?

Editar: expandir un poco ... Tener una propiedad síncrona con carga diferida que llamará al servicio WCF en el primer uso. Luego, el enlace asincrónico evitará que la IU se bloquee.


Aquí hay una opción E para ti.

Carga de forma asíncrona los datos. Haga que la búsqueda inicial ponga cosas en cola en un hilo de fondo que llena lentamente los objetos completos. Y haga que cualquier método que requiera que los datos se carguen entre bastidores sea bloqueado en el acabado de la carga. (Bloquee y pídales que notifiquen al hilo de fondo que los datos que necesitan son de alta prioridad, obténgalos a continuación, para poder desbloquearlos lo antes posible).

Esto le proporciona una IU que responde inmediatamente cuando puede, la capacidad de escribir su código y no pensar en lo que se ha cargado, y en su mayor parte solo funcionará. El único problema es que de vez en cuando haces una llamada de bloqueo mientras se cargan los datos, aunque afortunadamente no lo hará con demasiada frecuencia. Si lo haces, en el peor de los casos te degradarás a algo como la opción C, donde tienes una búsqueda de datos bloqueante y la posibilidad de sondear para ver si está allí. Sin embargo, la mayoría de las veces no tendrías que preocuparte demasiado por eso.

Descargo de responsabilidad: personalmente no uso Windows, y paso la mayor parte del tiempo trabajando en backend lejos de UI. Si te gusta la idea, no dudes en probarla. Pero no he seguido esta estrategia para nada más complicado que algunos detrás de las escenas que AJAX llama en una página web dinámica.


La solución que se me ocurrió fue modificar la plantilla T4 para las entidades de seguimiento automático para realizar los cambios que se muestran a continuación. La implementación real se ha omitido para que sea más fácil de leer, pero los nombres de propiedad / método deben dejar en claro qué hace todo.

Propiedades de navegación generadas en T4 antiguas

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

Nuevas propiedades de navegación generadas 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();

Creé tres copias de la propiedad, que apuntan a la misma propiedad privada. La copia interna es para EF. Probablemente podría deshacerme de él, pero es más fácil dejarlo puesto que EF espera una propiedad con ese nombre y es más fácil dejarlo que arreglar EF para usar un nuevo nombre de propiedad. Es interno ya que no quiero usar nada fuera del espacio de nombres de la clase.

Las otras dos copias de la propiedad se ejecutan exactamente de la misma manera una vez que se ha cargado el valor, sin embargo, cargan la propiedad de manera diferente.

La versión de Async ejecuta LoadMyPropertyAsync() , que simplemente ejecuta GetMyPropertyAsync() . Necesitaba dos métodos para esto porque no puedo poner el modificador async en un getter, y necesito devolver un vacío si estoy llamando desde un método no asincrónico.

La versión Sync ejecuta GetMyPropertySync() que a su vez ejecuta GetMyPropertyAsync() sincrónica

Como todo esto es generado por T4, no necesito hacer nada excepto conectar el delegado de carga lenta asincrónica cuando la entidad se obtiene del servicio WCF.

Mis enlaces apuntan a la versión Async de la propiedad y cualquier otro punto de código a la versión Sync de la propiedad y ambos funcionan correctamente sin ninguna codificación adicional.

<ItemsControl ItemsSource="{Binding CurrentConsumer.DocumentsAsync}" /> CurrentConsumer.DocumentsSync.Clear();


Pensándolo bien, antes que nada tengo que decir que debe proporcionar una solución clara al lector para este problema, DependecyProperties se carga de manera sincronizada cuando se vincula a la propiedad User.Documents puede estar bien, pero está muy cerca de un efecto secundario basado en solución. Si decimos que dicho comportamiento en View es correcto, debemos mantener nuestro código de descanso muy claro sobre sus intenciones, para que podamos ver cómo intentamos acceder a los datos: asincrónicos o sincronizados a través de un nombre detallado de algo (method, classname, smth más).

Así que creo que podríamos usar una solución cercana al viejo enfoque .AsSynchronized (), crear una clase de decorador y proporcionar a cada propiedad un método privado / protegido AsyncLoad & SyncLoad, y una clase de decorador sería la versión Sync o Async de cada lazyloadable clase, lo que sea más apropiado.

Cuando decora su clase con el decorador Sync, envuelve cada clase de lazyloadable dentro con un decorador Sync para que pueda usar SynchUser (User) .Documents.Count en la versión de la clase sync sin problemas, ya que será muy similar al SynchUser (usuario ) .SyncDocuments (Documentos) .Consigue atrás en la versión sobrecargada de la propiedad Documents y llamaría a la función getter de sincronización.

Debido a que tanto las versiones sincronizadas como asíncronas funcionarán en el mismo objeto, este enfoque no llevará a modificar algún objeto no referenciado en ningún otro lugar si desea modificar cualquier propiedad.

Su tarea puede sonar como una que se puede resolver de alguna manera mágica "bella y simple", pero no creo que sí, o que no será más simple que esta.

Si esto no funciona, estoy 100% seguro de que necesita una forma clara de diferenciar el código, ya sea que se use una versión sincronizada o asíncrona de la clase o tendrá una base de código muy difícil de mantener.


Si bien esta pregunta fue formulada hace un tiempo, está cerca de la parte superior de la lista de palabras clave async-await y creo que sería respondida de manera bastante diferente en .NET 4.5.

Creo que este sería un caso de uso perfecto para el tipo AsyncLazy<T> descrito en varios sitios:

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


Tal como lo veo, ViewModel necesita saber si hay datos disponibles o no. Puede ocultar o deshabilitar los elementos de la IU que no tienen sentido sin datos mientras se están obteniendo los datos, y luego mostrarlos cuando lleguen los datos.

Detecta que necesita cargar algunos datos, por lo que establece la interfaz de usuario en el modo "espera", inicia la búsqueda asincrónica y luego, cuando los datos entran, lo saca del modo de espera. Quizás haciendo que ViewModel se suscriba a un evento "LoadCompleted" en el objeto que le interesa.

(edit) Puede evitar cargas excesivas o dependencias circulares si realiza un seguimiento del estado de cada objeto del modelo: descargado / cargado / cargado.


Tengo dos pensamientos en mi cabeza.

1) Implementar una respuesta IQueryable<> en el servicio WCF . Y siga hasta el DB con un patrón IQueryable<> .

2) En el repositorio del cliente configure el getter en la propiedad ConsumerDocuments para recuperar los datos.

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


La opción A debería ser la solución.

Cree una propiedad llamada LoadingStatus que indique que los datos están cargados o que la carga aún no se ha cargado. Cargue los datos de forma asíncrona y establezca la propiedad LoadingStatus en consecuencia.

Verifique el estado de carga en cada propiedad y si los datos no están cargados, llame a la función para cargar datos y viceversa .